From 70fdcc9be04b97b187d59eb0c625c8b45d017e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Fri, 15 Jul 2022 13:12:29 +0200 Subject: [PATCH 01/91] Added nightly build trigger --- .../workflows/nightly_build_on_dispatch.yml | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 .github/workflows/nightly_build_on_dispatch.yml diff --git a/.github/workflows/nightly_build_on_dispatch.yml b/.github/workflows/nightly_build_on_dispatch.yml new file mode 100644 index 0000000000..191cf36e9d --- /dev/null +++ b/.github/workflows/nightly_build_on_dispatch.yml @@ -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_includes.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 From 0912627e1f1a305042a4b2f4047b548d6e4a8328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 15 Jul 2022 14:17:58 +0300 Subject: [PATCH 02/91] Update cosmrs in lock file, so it points to a valid git hash (#1459) --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e190bb6131..bd88b49e4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,7 +783,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cosmos-sdk-proto" version = "0.12.3" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#484f3b8bd39e00c2db567e754d4b7337b7c635a6" +source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" dependencies = [ "prost 0.10.3", "prost-types 0.10.1", @@ -793,7 +793,7 @@ dependencies = [ [[package]] name = "cosmrs" version = "0.7.1" -source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#484f3b8bd39e00c2db567e754d4b7337b7c635a6" +source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" dependencies = [ "bip32", "cosmos-sdk-proto", From f29200431fad2bedb2de9d2fc818508b7b38dfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Walther?= Date: Fri, 15 Jul 2022 13:35:45 +0200 Subject: [PATCH 03/91] Amended nightly build trigger --- .../nightly_build_matrix_on_dispatch.json | 50 +++++++++++++++++++ .../workflows/nightly_build_on_dispatch.yml | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/nightly_build_matrix_on_dispatch.json diff --git a/.github/workflows/nightly_build_matrix_on_dispatch.json b/.github/workflows/nightly_build_matrix_on_dispatch.json new file mode 100644 index 0000000000..c292817b6a --- /dev/null +++ b/.github/workflows/nightly_build_matrix_on_dispatch.json @@ -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" + } +] diff --git a/.github/workflows/nightly_build_on_dispatch.yml b/.github/workflows/nightly_build_on_dispatch.yml index 191cf36e9d..d45808b3fd 100644 --- a/.github/workflows/nightly_build_on_dispatch.yml +++ b/.github/workflows/nightly_build_on_dispatch.yml @@ -12,7 +12,7 @@ jobs: - id: set-matrix uses: JoshuaTheMiller/conditional-build-matrix@main with: - inputFile: '.github/workflows/nightly_build_matrix_includes.json' + inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json' filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]' build: needs: matrix_prep From 47f7a5f795c362f946c5a2fc8f1d201467f0b69a Mon Sep 17 00:00:00 2001 From: Fouad Date: Fri, 15 Jul 2022 15:30:40 +0100 Subject: [PATCH 04/91] Feature/changing wallet currency types frontend work (#1455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .../src/nymd/cosmwasm_client/client.rs | 4 +- .../nymd/cosmwasm_client/signing_client.rs | 8 +- .../validator-client/src/nymd/fee/mod.rs | 67 +- .../validator-client/src/nymd/mod.rs | 2 +- .../mixnet-contract/Cargo.toml | 1 + .../mixnet-contract/src/delegation.rs | 2 +- .../mixnet-contract/src/mixnode.rs | 10 + .../vesting-contract/src/lib.rs | 10 +- common/network-defaults/src/lib.rs | 2 +- common/types/src/account.rs | 27 +- common/types/src/currency.rs | 796 +++++++++--------- common/types/src/delegation.rs | 147 +--- common/types/src/error.rs | 7 + common/types/src/fees.rs | 15 +- common/types/src/gas.rs | 86 +- common/types/src/gateway.rs | 43 +- common/types/src/mixnode.rs | 80 +- common/types/src/transaction.rs | 80 +- common/types/src/vesting.rs | 80 +- .../mixnet/src/delegations/transactions.rs | 2 +- contracts/mixnet/src/rewards/transactions.rs | 3 +- contracts/vesting/src/vesting/account/mod.rs | 10 +- nym-wallet/.storybook/mocks/tauri/index.js | 4 +- nym-wallet/Cargo.lock | 10 +- nym-wallet/nym-wallet-types/src/network.rs | 25 +- nym-wallet/src-tauri/src/error.rs | 5 + nym-wallet/src-tauri/src/main.rs | 6 +- nym-wallet/src-tauri/src/network_config.rs | 23 +- .../src/operations/mixnet/account.rs | 72 +- .../src-tauri/src/operations/mixnet/admin.rs | 19 +- .../src-tauri/src/operations/mixnet/bond.rs | 138 +-- .../src/operations/mixnet/delegate.rs | 184 ++-- .../src-tauri/src/operations/mixnet/epoch.rs | 6 +- .../src/operations/mixnet/rewards.rs | 56 +- .../src-tauri/src/operations/mixnet/send.rs | 43 +- .../src/operations/simulate/admin.rs | 9 +- .../src/operations/simulate/cosmos.rs | 16 +- .../src/operations/simulate/mixnet.rs | 68 +- .../src-tauri/src/operations/simulate/mod.rs | 32 +- .../src/operations/simulate/vesting.rs | 79 +- .../src/operations/validator_api/status.rs | 16 +- .../src-tauri/src/operations/vesting/bond.rs | 117 +-- .../src/operations/vesting/delegate.rs | 56 +- .../src/operations/vesting/queries.rs | 185 ++-- .../src/operations/vesting/rewards.rs | 54 +- nym-wallet/src-tauri/src/state.rs | 116 ++- nym-wallet/src-tauri/src/utils.rs | 23 +- nym-wallet/src/components/AppBar.tsx | 3 +- nym-wallet/src/components/AppBar/AppBar.tsx | 2 +- .../src/components/ConfirmTX.stories.tsx | 2 +- nym-wallet/src/components/ConfirmTX.tsx | 2 +- .../components/Delegation/DelegateBlocker.tsx | 2 +- .../components/Delegation/DelegateModal.tsx | 14 +- .../Delegation/DelegationList.stories.tsx | 16 +- .../components/Delegation/DelegationList.tsx | 6 +- .../components/Delegation/DelegationModal.tsx | 15 +- .../components/Delegation/Modals.stories.tsx | 8 +- .../components/Delegation/PendingEvents.tsx | 2 +- nym-wallet/src/components/Fee.tsx | 29 - .../src/components/Modals/ModalListItem.tsx | 6 +- .../Rewards/RedeemModal.stories.tsx | 8 +- .../src/components/Rewards/RewardsSummary.tsx | 4 +- .../components/Send/SendDetails.stories.tsx | 4 +- .../src/components/Send/SendDetailsModal.tsx | 6 +- .../src/components/Send/SendInputModal.tsx | 8 +- nym-wallet/src/components/Send/SendModal.tsx | 10 +- nym-wallet/src/components/SuccessResponse.tsx | 8 +- .../src/components/TokenPoolSelector.tsx | 5 +- nym-wallet/src/components/index.ts | 1 - nym-wallet/src/context/delegations.tsx | 6 +- nym-wallet/src/context/main.tsx | 11 +- nym-wallet/src/context/mocks/delegations.tsx | 34 +- nym-wallet/src/context/mocks/main.tsx | 7 +- nym-wallet/src/context/mocks/rewards.tsx | 40 +- nym-wallet/src/hooks/useGetBalance.ts | 11 +- .../balance/components/TransferModal.tsx | 21 +- .../components/TransferModalSuccess.tsx | 4 +- nym-wallet/src/pages/balance/vesting.tsx | 24 +- .../bond/components/ConfirmationModal.tsx | 4 +- .../src/pages/bond/components/GatewayForm.tsx | 10 +- .../src/pages/bond/components/MixnodeForm.tsx | 10 +- .../src/pages/bond/components/SuccessView.tsx | 11 +- .../src/pages/delegate/DelegateForm.tsx | 142 ---- nym-wallet/src/pages/delegate/SuccessView.tsx | 37 - nym-wallet/src/pages/delegate/index.tsx | 89 -- .../src/pages/delegate/validationSchema.ts | 38 - nym-wallet/src/pages/delegation/index.tsx | 24 +- nym-wallet/src/pages/index.ts | 3 - nym-wallet/src/pages/receive/index.tsx | 4 +- .../src/pages/send/SendConfirmation.tsx | 65 -- nym-wallet/src/pages/send/SendError.tsx | 36 - nym-wallet/src/pages/send/SendForm.tsx | 47 -- nym-wallet/src/pages/send/SendReview.tsx | 57 -- nym-wallet/src/pages/send/SendWizard.tsx | 177 ---- nym-wallet/src/pages/send/index.tsx | 16 - nym-wallet/src/pages/send/validationSchema.ts | 12 - .../src/pages/settings/system-variables.tsx | 3 +- .../src/pages/undelegate/PendingEvents.tsx | 31 - .../src/pages/undelegate/UndelegateForm.tsx | 165 ---- nym-wallet/src/pages/undelegate/index.tsx | 170 ---- .../src/pages/undelegate/validationSchema.ts | 8 - nym-wallet/src/requests/actions.ts | 4 +- nym-wallet/src/requests/delegation.ts | 4 +- nym-wallet/src/requests/queries.ts | 9 +- nym-wallet/src/requests/simulate.ts | 20 +- nym-wallet/src/requests/vesting.ts | 26 +- nym-wallet/src/theme/mui-theme.d.ts | 10 +- nym-wallet/src/types/global.ts | 10 +- nym-wallet/src/utils/index.ts | 6 +- tools/ts-rs-cli/src/main.rs | 7 +- .../components/currency/Currency.stories.tsx | 24 +- .../src/components/currency/Currency.tsx | 4 +- .../currency/CurrencyAmount.stories.tsx | 20 +- .../components/currency/CurrencyAmount.tsx | 4 +- .../components/currency/CurrencyFormField.tsx | 14 +- .../currency/CurrencyWithCoinMark.tsx | 6 +- ts-packages/types/src/types/global.ts | 8 +- ts-packages/types/src/types/rust/Account.ts | 4 +- ts-packages/types/src/types/rust/Balance.ts | 4 +- ts-packages/types/src/types/rust/Coin.ts | 6 +- ts-packages/types/src/types/rust/CosmosFee.ts | 9 +- ts-packages/types/src/types/rust/Currency.ts | 7 - .../types/src/types/rust/CurrencyDenom.ts | 2 +- .../types/rust/CurrencyStringMajorAmount.ts | 1 - ts-packages/types/src/types/rust/DecCoin.ts | 3 + .../types/src/types/rust/Delegation.ts | 4 +- .../types/src/types/rust/DelegationEvent.ts | 4 +- .../types/src/types/rust/DelegationRecord.ts | 4 +- .../types/src/types/rust/DelegationResult.ts | 4 +- .../types/rust/DelegationSummaryResponse.ts | 6 +- .../types/rust/DelegationWithEverything.ts | 10 +- .../types/src/types/rust/FeeDetails.ts | 4 +- ts-packages/types/src/types/rust/GasInfo.ts | 7 +- .../types/src/types/rust/GatewayBond.ts | 4 +- .../types/src/types/rust/MixNodeBond.ts | 8 +- ts-packages/types/src/types/rust/Operation.ts | 34 - .../src/types/rust/OriginalVestingResponse.ts | 4 +- .../types/src/types/rust/PledgeData.ts | 4 +- .../src/types/rust/RpcTransactionResponse.ts | 7 +- .../types/src/types/rust/SendTxResult.ts | 7 +- .../types/src/types/rust/TauriTxResult.ts | 10 - .../src/types/rust/TransactionDetails.ts | 4 +- .../types/rust/TransactionExecuteResult.ts | 4 +- .../src/types/rust/VestingAccountInfo.ts | 4 +- ts-packages/types/src/types/rust/index.ts | 4 +- validator-api/src/coconut/mod.rs | 4 +- 146 files changed, 1809 insertions(+), 2866 deletions(-) delete mode 100644 nym-wallet/src/components/Fee.tsx delete mode 100644 nym-wallet/src/pages/delegate/DelegateForm.tsx delete mode 100644 nym-wallet/src/pages/delegate/SuccessView.tsx delete mode 100644 nym-wallet/src/pages/delegate/index.tsx delete mode 100644 nym-wallet/src/pages/delegate/validationSchema.ts delete mode 100644 nym-wallet/src/pages/send/SendConfirmation.tsx delete mode 100644 nym-wallet/src/pages/send/SendError.tsx delete mode 100644 nym-wallet/src/pages/send/SendForm.tsx delete mode 100644 nym-wallet/src/pages/send/SendReview.tsx delete mode 100644 nym-wallet/src/pages/send/SendWizard.tsx delete mode 100644 nym-wallet/src/pages/send/index.tsx delete mode 100644 nym-wallet/src/pages/send/validationSchema.ts delete mode 100644 nym-wallet/src/pages/undelegate/PendingEvents.tsx delete mode 100644 nym-wallet/src/pages/undelegate/UndelegateForm.tsx delete mode 100644 nym-wallet/src/pages/undelegate/index.tsx delete mode 100644 nym-wallet/src/pages/undelegate/validationSchema.ts delete mode 100644 ts-packages/types/src/types/rust/Currency.ts delete mode 100644 ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts create mode 100644 ts-packages/types/src/types/rust/DecCoin.ts delete mode 100644 ts-packages/types/src/types/rust/Operation.ts delete mode 100644 ts-packages/types/src/types/rust/TauriTxResult.ts diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs index 08a3ce22a2..8da9d3192c 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs @@ -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, NymdError> { let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap()); diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index cc6bc9ce17..b1646f663e 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -574,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 } }; diff --git a/common/client-libs/validator-client/src/nymd/fee/mod.rs b/common/client-libs/validator-client/src/nymd/fee/mod.rs index dfa869469e..411cac2365 100644 --- a/common/client-libs/validator-client/src/nymd/fee/mod.rs +++ b/common/client-libs/validator-client/src/nymd/fee/mod.rs @@ -1,9 +1,11 @@ // Copyright 2021 - Nym Technologies SA // 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, + pub payer: Option, + pub granter: Option, +} + +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), - PayerGranterAuto(Option, Option, Option), + 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, + payer: Option, + granter: Option, + ) -> Self { + Fee::PayerGranterAuto(AutoFeeGrant { + gas_adjustment, + payer, + granter, + }) + } + pub fn try_get_manual_amount(&self) -> Option> { + match self { + Fee::Manual(tx_fee) => Some(tx_fee.amount.iter().cloned().map(Into::into).collect()), + _ => None, + } + } } impl From for Fee { diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index ceeabd5133..ba4c53cd6c 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -389,7 +389,7 @@ impl NymdClient { pub async fn get_balance( &self, address: &AccountId, - denom: Denom, + denom: String, ) -> Result, NymdError> where C: CosmWasmClient + Sync, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 33439678c5..0f6e5d9756 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -3,6 +3,7 @@ name = "mixnet-contract-common" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" +rust-version = "1.62" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs index a6258e3ea6..d0d4372fa4 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs @@ -33,7 +33,7 @@ pub struct Delegation { pub node_identity: IdentityKey, pub amount: Coin, pub block_height: u64, - pub proxy: Option, // proxy address used to delegate the funds on behalf of anouther address + pub proxy: Option, // proxy address used to delegate the funds on behalf of another address } impl Eq for Delegation {} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index 4772ca1a2e..752719e9c9 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -37,6 +37,16 @@ pub enum DelegationEvent { Undelegate(PendingUndelegate), } +impl DelegationEvent { + pub fn delegation_amount(&self) -> Option { + 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, diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index 346162420d..f995c241fd 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -28,8 +28,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 +48,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 { diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 5487899696..6281527910 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -267,7 +267,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, diff --git a/common/types/src/account.rs b/common/types/src/account.rs index 00ec29a703..47c6fc0c6d 100644 --- a/common/types/src/account.rs +++ b/common/types/src/account.rs @@ -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, + } + } +} diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index bad9ace0e6..be58e48d44 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -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 { - 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); + +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 { + self.0.insert(denom, metadata) + } + + pub fn remove(&mut self, denom: &Denom) -> Option { + self.0.remove(denom) + } + + pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result { + // 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, ®istered_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 { + 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(®istered_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, + ®istered_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, + ®istered_coin.display, + display_exponent - exponent, + )?) + } + }; + } } + + Err(TypesError::UnknownCoinDenom(coin.denom)) } } -impl TryFrom 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, +} - fn try_from(value: CosmosDenom) -> Result { - 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, + 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, base: Denom, display: Denom) -> Self { + CoinMetadata { + denom_units, + base, + display, + } + } + + pub fn get_exponent(&self, denom: &str) -> Option { + self.denom_units + .iter() + .find(|denom_unit| denom_unit.denom == denom) + .map(|denom_unit| denom_unit.exponent) + } +} + +impl From 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 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 { + vec![Dependency::from_ty::() + .expect("TS was incorrectly defined on `CurrencyDenom`")] + } + + fn transparent() -> bool { + false + } +} + +impl DecCoin { + pub fn new_base>(amount: impl Into, 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>(denom: S) -> Self { + DecCoin { + denom: denom.into(), + amount: Decimal::zero(), + } } - // - // pub fn from_cosmrs_coin(coin: &CosmosCoin) -> Result { - // 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::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 { - // 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 { - // 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 { - // 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 { - // 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::() { - // 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>( + base_amount: impl Into, + denom: S, + exponent: u32, + ) -> Result { + 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>( + base_amount: impl Into, + denom: S, + exponent: u32, + ) -> Result { + 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 { + try_scale_down_decimal(self.amount, exponent) + } + + pub fn try_scale_up_value(&self, exponent: u32) -> Result { + 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 { + 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 { + 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 { + 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 for MajorCurrencyAmount { - fn from(c: CosmosCoin) -> Self { - MajorCurrencyAmount::from(Coin::from(c)) - } -} - -impl From for MajorCurrencyAmount { - fn from(c: CosmWasmCoin) -> Self { - MajorCurrencyAmount::from(Coin::from(c)) - } -} - -impl From for MajorCurrencyAmount { +impl From 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 for CosmosCoin { - fn from(c: MajorCurrencyAmount) -> CosmosCoin { - let c: Coin = c.into(); - c.into() - } -} +// this conversion assumes same denomination +impl TryFrom for Coin { + type Error = TypesError; -impl From for CosmWasmCoin { - fn from(c: MajorCurrencyAmount) -> CosmWasmCoin { - let c: Coin = c.into(); - c.into() - } -} - -impl From 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 { + 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::(nym).unwrap(); - let nymt_amount = serde_json::from_value::(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::(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::().unwrap(), + dec.try_scale_down_value(0).unwrap() ); assert_eq!( - MajorCurrencyAmount::new("1", CurrencyDenom::Nymt).to_string(), - "1 NYMT" + "123400700".parse::().unwrap(), + dec.try_scale_down_value(1).unwrap() ); assert_eq!( - MajorCurrencyAmount::new("1000000000000", CurrencyDenom::Nym).to_string(), - "1000000000000 NYM" + "12340070".parse::().unwrap(), + dec.try_scale_down_value(2).unwrap() + ); + assert_eq!( + "123400.7".parse::().unwrap(), + dec.try_scale_down_value(4).unwrap() + ); + + let dec = DecCoin { + denom: "foo".to_string(), + amount: "10000000000".parse().unwrap(), + }; + + assert_eq!( + "100".parse::().unwrap(), + dec.try_scale_down_value(8).unwrap() + ); + assert_eq!( + "1".parse::().unwrap(), + dec.try_scale_down_value(10).unwrap() + ); + assert_eq!( + "0.01".parse::().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::().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::().unwrap(), + dec.try_scale_up_value(1).unwrap() + ); + assert_eq!( + "123456".parse::().unwrap(), + dec.try_scale_up_value(2).unwrap() + ); + assert_eq!( + "1234560".parse::().unwrap(), + dec.try_scale_up_value(3).unwrap() + ); + assert_eq!( + "12345600".parse::().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::().unwrap(), + dec.try_scale_up_value(1).unwrap() + ); + assert_eq!( + "0.000123".parse::().unwrap(), + dec.try_scale_up_value(2).unwrap() + ); + assert_eq!( + "123".parse::().unwrap(), + dec.try_scale_up_value(8).unwrap() + ); + assert_eq!( + "1230".parse::().unwrap(), + dec.try_scale_up_value(9).unwrap() + ); + assert_eq!( + "12300".parse::().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); + } } } diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index d8302ff3b9..0f5c5c0c96 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -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, // proxy address used to delegate the funds on behalf of another address } -impl TryFrom for Delegation { - type Error = TypesError; - - fn try_from(value: MixnetContractDelegation) -> Result { - 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 { 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 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, - pub pledge_amount: Option, + pub amount: DecCoin, + pub total_delegation: Option, + pub pledge_amount: Option, pub block_height: u64, pub delegated_on_iso_datetime: String, pub profit_margin_percent: Option, pub avg_uptime_percent: Option, pub stake_saturation: Option, pub uses_vesting_contract_tokens: bool, - pub accumulated_rewards: Option, + pub accumulated_rewards: Option, pub pending_events: Vec, pub history: Vec, } @@ -92,34 +80,7 @@ pub struct DelegationWithEverything { pub struct DelegationResult { source_address: String, target_address: String, - amount: Option, -} - -impl DelegationResult { - pub fn new( - source_address: &str, - target_address: &str, - amount: Option, - ) -> DelegationResult { - DelegationResult { - source_address: source_address.to_string(), - target_address: target_address.to_string(), - amount, - } - } -} - -impl TryFrom for DelegationResult { - type Error = TypesError; - - fn try_from(delegation: MixnetContractDelegation) -> Result { - 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, } #[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, + pub amount: Option, pub block_height: u64, pub proxy: Option, } -impl TryFrom for DelegationEvent { - type Error = TypesError; - - fn try_from(event: ContractDelegationEvent) -> Result { - 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 { + 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 for PendingUndelegate { } } -pub fn from_contract_delegation_events( - events: Vec, -) -> Result, 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::>(); - return Ok(events); - } - let errors = errors - .into_iter() - .filter_map(|e| e.err()) - .collect::>(); - - 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, - pub total_delegations: MajorCurrencyAmount, - pub total_rewards: MajorCurrencyAmount, + pub total_delegations: DecCoin, + pub total_rewards: DecCoin, } diff --git a/common/types/src/error.rs b/common/types/src/error.rs index 1bc8323e4a..20c3736152 100644 --- a/common/types/src/error.rs +++ b/common/types/src/error.rs @@ -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 { diff --git a/common/types/src/fees.rs b/common/types/src/fees.rs index 95045fcc70..747693ce9c 100644 --- a/common/types/src/fees.rs +++ b/common/types/src/fees.rs @@ -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, + pub amount: Option, pub fee: Fee, } +impl FeeDetails { + pub fn new(amount: Option, 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 { vec![ - Dependency::from_ty::() - .expect("TS was incorrectly defined on `CurrencyDenom`"), + Dependency::from_ty::().expect("TS was incorrectly defined on `DecCoin`"), Dependency::from_ty::() .expect("TS was incorrectly defined on `ts_type_helpers::Fee`"), ] diff --git a/common/types/src/gas.rs b/common/types/src/gas.rs index 02a2807a7e..9441008569 100644 --- a/common/types/src/gas.rs +++ b/common/types/src/gas.rs @@ -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 { - 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 { - 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 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 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 { - // 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 { - // 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), + } } } diff --git a/common/types/src/gateway.rs b/common/types/src/gateway.rs index 577c738378..8895a69761 100644 --- a/common/types/src/gateway.rs +++ b/common/types/src/gateway.rs @@ -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 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, - ) -> Result, TypesError> { - match bond { - Some(bond) => { - let bond: GatewayBond = bond.try_into()?; - Ok(Some(bond)) - } - None => Ok(None), - } - } -} - -impl TryFrom for GatewayBond { - type Error = TypesError; - - fn try_from(value: MixnetContractGatewayBond) -> Result { - let MixnetContractGatewayBond { - pledge_amount, - owner, - block_height, - gateway, - proxy, - } = value; - - let pledge_amount: MajorCurrencyAmount = pledge_amount.into(); - + bond: MixnetContractGatewayBond, + reg: &RegisteredCoins, + ) -> Result { 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()), }) } } diff --git a/common/types/src/mixnode.rs b/common/types/src/mixnode.rs index 402062dbb9..d1a302d489 100644 --- a/common/types/src/mixnode.rs +++ b/common/types/src/mixnode.rs @@ -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 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, - pub accumulated_rewards: Option, + pub accumulated_rewards: Option, } impl MixNodeBond { pub fn from_mixnet_contract_mixnode_bond( - bond: Option, - ) -> Result, TypesError> { - match bond { - Some(bond) => { - let bond: MixNodeBond = bond.try_into()?; - Ok(Some(bond)) - } - None => Ok(None), - } - } -} - -impl TryFrom for MixNodeBond { - type Error = TypesError; - - fn try_from(value: MixnetContractMixNodeBond) -> Result { - 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 = - accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into()); - + bond: MixnetContractMixNodeBond, + reg: &RegisteredCoins, + ) -> Result { + 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()?, }) } } diff --git a/common/types/src/transaction.rs b/common/types/src/transaction.rs index 58ee9b1cca..b5dd50a4ae 100644 --- a/common/types/src/transaction.rs +++ b/common/types/src/transaction.rs @@ -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, } impl SendTxResult { - pub fn new( - t: TxResponse, - details: TransactionDetails, - _denom_minor: &str, - ) -> Result { - Ok(SendTxResult { + pub fn new(t: TxResponse, details: TransactionDetails, fee: Option) -> 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, } impl TransactionExecuteResult { pub fn from_execute_result( value: ExecuteResult, - denom_minor: &str, + fee: Option, ) -> Result { - 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, } impl RpcTransactionResponse { pub fn from_tx_response( - value: &TxResponse, - denom_minor: &str, + t: &TxResponse, + fee: Option, ) -> Result { 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, }) } } diff --git a/common/types/src/vesting.rs b/common/types/src/vesting.rs index c20b0845e0..0c6b6a4c20 100644 --- a/common/types/src/vesting.rs +++ b/common/types/src/vesting.rs @@ -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 for PledgeData { - type Error = TypesError; - - fn try_from(data: VestingPledgeData) -> Result { - let amount: MajorCurrencyAmount = data.amount().into(); - Ok(Self { - amount, - block_time: data.block_time().seconds(), - }) - } -} - impl PledgeData { - pub fn and_then(data: VestingPledgeData) -> Option { - data.try_into().ok() + pub fn from_vesting_contract( + pledge: ContractPledgeData, + reg: &RegisteredCoins, + ) -> Result { + 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 for OriginalVestingResponse { - type Error = TypesError; - - fn try_from(data: VestingOriginalVestingResponse) -> Result { - 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 { + 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, start_time: u64, periods: Vec, - amount: MajorCurrencyAmount, + amount: DecCoin, } -impl TryFrom for VestingAccountInfo { - type Error = TypesError; - - fn try_from(account: VestingAccount) -> Result { - 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 { + 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 for VestingPeriod { - fn from(period: VestingVestingPeriod) -> Self { +impl From for VestingPeriod { + fn from(period: ContractVestingPeriod) -> Self { Self { start_time: period.start_time, period_seconds: period.period_seconds, diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 4d817f683f..bad70e9b8b 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -1195,7 +1195,7 @@ mod tests { // _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - // let delegation = query_mixnode_delegation( + // let _delegation = query_mixnode_delegation( // &deps.storage, // &deps.api, // identity.clone(), diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index e3ba1b96a0..927390e8fd 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -1225,7 +1225,6 @@ pub mod tests { ) .unwrap(); - let info = mock_info(rewarding_validator_address.as_ref(), &[]); env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING; let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_1) @@ -1531,7 +1530,7 @@ pub mod tests { assert_eq!(mix_2_reward_result.lambda(), U128::from_num(0.0001f64)); assert_eq!(mix_2_reward_result.reward().int(), 974456u128); - let mix_3_reward_result = mix_3.reward(¶ms3); + let _mix_3_reward_result = mix_3.reward(¶ms3); // assert_eq!(mix_3_reward_result.reward().int(), mix_1_reward_result.reward().int() + mix_2_reward_result.reward().int()); } diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index b25628deea..5418619cfe 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -25,11 +25,11 @@ fn generate_storage_key(storage: &mut dyn Storage) -> Result #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Account { - owner_address: Addr, - staking_address: Option, - start_time: Timestamp, - periods: Vec, - coin: Coin, + pub owner_address: Addr, + pub staking_address: Option, + pub start_time: Timestamp, + pub periods: Vec, + pub coin: Coin, storage_key: u32, } diff --git a/nym-wallet/.storybook/mocks/tauri/index.js b/nym-wallet/.storybook/mocks/tauri/index.js index 10ebffe42f..e2bf50b00d 100644 --- a/nym-wallet/.storybook/mocks/tauri/index.js +++ b/nym-wallet/.storybook/mocks/tauri/index.js @@ -9,7 +9,7 @@ module.exports = { return { amount: { amount: '100', - denom: 'NYMT', + denom: 'nymt', }, printable_balance: '100 NYMT', }; @@ -25,7 +25,7 @@ module.exports = { return { amount: { amount: '0.01', - denom: 'NYM', + denom: 'nym', }, }; } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 790959615e..3de7d067c8 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -844,9 +844,8 @@ dependencies = [ [[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", "prost-types", @@ -855,9 +854,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", diff --git a/nym-wallet/nym-wallet-types/src/network.rs b/nym-wallet/nym-wallet-types/src/network.rs index 5a41cd7b95..19bf580b47 100644 --- a/nym-wallet/nym-wallet-types/src/network.rs +++ b/nym-wallet/nym-wallet-types/src/network.rs @@ -1,15 +1,12 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmrs::Denom; +use config::defaults::all::Network as ConfigNetwork; +use config::defaults::{mainnet, qa, sandbox, DenomDetails}; use serde::{Deserialize, Serialize}; use std::fmt; -use std::str::FromStr; use strum::EnumIter; -use config::defaults::all::Network as ConfigNetwork; -use config::defaults::{mainnet, qa, sandbox}; - #[allow(clippy::upper_case_acronyms)] #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( @@ -28,13 +25,19 @@ impl Network { self.to_string().to_lowercase() } - // this should be returning just a `&str`, but don't want to cause too many conflicts just yet... - pub fn base_mix_denom(&self) -> Denom { + pub fn mix_denom(&self) -> DenomDetails { match self { - // network defaults should be correctly formatted - Network::QA => Denom::from_str(qa::MIX_DENOM.base).unwrap(), - Network::SANDBOX => Denom::from_str(sandbox::MIX_DENOM.base).unwrap(), - Network::MAINNET => Denom::from_str(mainnet::MIX_DENOM.base).unwrap(), + Network::QA => qa::MIX_DENOM, + Network::SANDBOX => sandbox::MIX_DENOM, + Network::MAINNET => mainnet::MIX_DENOM, + } + } + + pub fn base_mix_denom(&self) -> &str { + match self { + Network::QA => qa::MIX_DENOM.base, + Network::SANDBOX => sandbox::MIX_DENOM.base, + Network::MAINNET => mainnet::MIX_DENOM.base, } } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 265563947d..5c5b1ea5a3 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -1,4 +1,5 @@ use nym_types::error::TypesError; +use nym_wallet_types::network::Network; use serde::{Serialize, Serializer}; use std::io; use std::num::ParseIntError; @@ -106,6 +107,10 @@ pub enum BackendError { FailedToDeriveAddress, #[error("{0}")] ValueParseError(#[from] ParseIntError), + #[error("The provided coin has an unknown denomination - {0}")] + UnknownCoinDenom(String), + #[error("Network {network} doesn't have any associated registered coin denoms")] + NoCoinsRegistered { network: Network }, } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 01ac86b95c..ddc2830c2e 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -4,9 +4,7 @@ )] use mixnet_contract_common::{Gateway, MixNode}; -use std::sync::Arc; use tauri::Menu; -use tokio::sync::RwLock; mod config; mod error; @@ -24,7 +22,7 @@ use crate::operations::simulate; use crate::operations::validator_api; use crate::operations::vesting; -use crate::state::State; +use crate::state::WalletState; #[allow(clippy::too_many_lines)] fn main() { @@ -32,7 +30,7 @@ fn main() { setup_logging(); tauri::Builder::default() - .manage(Arc::new(RwLock::new(State::default()))) + .manage(WalletState::default()) .invoke_handler(tauri::generate_handler![ mixnet::account::add_account_for_password, mixnet::account::archive_wallet_file, diff --git a/nym-wallet/src-tauri/src/network_config.rs b/nym-wallet/src-tauri/src/network_config.rs index 2a621922d5..43e872b717 100644 --- a/nym-wallet/src-tauri/src/network_config.rs +++ b/nym-wallet/src-tauri/src/network_config.rs @@ -1,20 +1,15 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; - -use tokio::sync::RwLock; - +use crate::error::BackendError; +use crate::state::WalletState; use nym_wallet_types::network::Network as WalletNetwork; use nym_wallet_types::network_config::{Validator, ValidatorUrl, ValidatorUrls}; -use crate::error::BackendError; -use crate::state::State; - #[tauri::command] pub async fn get_validator_nymd_urls( network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let state = state.read().await; let urls: Vec = state.get_nymd_urls(network).collect(); @@ -24,7 +19,7 @@ pub async fn get_validator_nymd_urls( #[tauri::command] pub async fn get_validator_api_urls( network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let state = state.read().await; let urls: Vec = state.get_api_urls(network).collect(); @@ -35,7 +30,7 @@ pub async fn get_validator_api_urls( pub async fn select_validator_nymd_url( url: &str, network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::debug!("Selecting new validator nymd_url for {network}: {url}"); state @@ -49,7 +44,7 @@ pub async fn select_validator_nymd_url( pub async fn select_validator_api_url( url: &str, network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::debug!("Selecting new validator api_url for {network}: {url}"); state.write().await.select_validator_api_url(url, network)?; @@ -60,7 +55,7 @@ pub async fn select_validator_api_url( pub async fn add_validator( validator: Validator, network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::debug!("Add validator for {network}: {validator}"); let url = validator.try_into()?; @@ -72,7 +67,7 @@ pub async fn add_validator( pub async fn remove_validator( validator: Validator, network: WalletNetwork, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::debug!("Remove validator for {network}: {validator}"); let url = validator.try_into()?; @@ -83,7 +78,7 @@ pub async fn remove_validator( // Update the list of validators by fecthing additional ones remotely. If it fails, just ignore. #[tauri::command] pub async fn update_validator_urls( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { let mut w_state = state.write().await; let _r = w_state.fetch_updated_validator_urls().await; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 07e52cbdb9..0f02c58f12 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -1,8 +1,7 @@ use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER}; use crate::error::BackendError; use crate::network_config; -use crate::nymd_client; -use crate::state::{State, WalletAccountIds}; +use crate::state::{WalletAccountIds, WalletState}; use crate::wallet_storage::{self, DEFAULT_LOGIN_ID}; use bip39::{Language, Mnemonic}; use config::defaults::all::Network; @@ -10,15 +9,11 @@ use config::defaults::COSMOS_DERIVATION_PATH; use cosmrs::bip32::DerivationPath; use itertools::Itertools; use nym_types::account::{Account, AccountEntry, Balance}; -use nym_types::currency::MajorCurrencyAmount; use nym_wallet_types::network::Network as WalletNetwork; use rand::seq::SliceRandom; use std::collections::HashMap; -use std::convert::TryInto; use std::str::FromStr; -use std::sync::Arc; use strum::IntoEnumIterator; -use tokio::sync::RwLock; use url::Url; use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet}; use validator_client::{nymd::SigningNymdClient, Client}; @@ -26,33 +21,30 @@ use validator_client::{nymd::SigningNymdClient, Client}; #[tauri::command] pub async fn connect_with_mnemonic( mnemonic: String, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let mnemonic = Mnemonic::from_str(&mnemonic)?; _connect_with_mnemonic(mnemonic, state).await } #[tauri::command] -pub async fn get_balance( - state: tauri::State<'_, Arc>>, -) -> Result { - let denom = state.read().await.current_network().base_mix_denom(); - match nymd_client!(state) - .get_balance(nymd_client!(state).address(), denom) - .await +pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result { + let guard = state.read().await; + let client = guard.current_client()?; + let address = client.nymd.address(); + let network = guard.current_network(); + let base_mix_denom = network.base_mix_denom(); + + match client + .nymd + .get_balance(address, base_mix_denom.to_string()) + .await? { - Ok(Some(coin)) => { - let amount = MajorCurrencyAmount::from(coin); - let printable_balance = amount.to_string(); - Ok(Balance { - amount, - printable_balance, - }) + Some(coin) => { + let amount = guard.attempt_convert_to_display_dec_coin(coin)?; + Ok(Balance::new(amount)) } - Ok(None) => Err(BackendError::NoBalance( - nymd_client!(state).address().to_string(), - )), - Err(e) => Err(BackendError::from(e)), + None => Err(BackendError::NoBalance(address.to_string())), } } @@ -68,19 +60,15 @@ pub fn validate_mnemonic(mnemonic: &str) -> bool { #[tauri::command] pub async fn switch_network( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, network: WalletNetwork, ) -> Result { let account = { let r_state = state.read().await; let client = r_state.client(network)?; - let denom = network.base_mix_denom(); + let denom = network.mix_denom(); - Account::new( - client.nymd.mixnet_contract_address().to_string(), - client.nymd.address().to_string(), - denom.try_into()?, - ) + Account::new(client.nymd.address().to_string(), denom) }; let mut w_state = state.write().await; @@ -90,7 +78,7 @@ pub async fn switch_network( } #[tauri::command] -pub async fn logout(state: tauri::State<'_, Arc>>) -> Result<(), BackendError> { +pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendError> { state.write().await.logout(); Ok(()) } @@ -102,7 +90,7 @@ fn random_mnemonic() -> Mnemonic { async fn _connect_with_mnemonic( mnemonic: Mnemonic, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { { let mut w_state = state.write().await; @@ -160,9 +148,8 @@ async fn _connect_with_mnemonic( .find(|client| WalletNetwork::from(client.network.clone()) == default_network); let account_for_default_network = match client_for_default_network { Some(client) => Ok(Account::new( - client.nymd.mixnet_contract_address().to_string(), client.nymd.address().to_string(), - default_network.base_mix_denom().try_into()?, + default_network.mix_denom(), )), None => Err(BackendError::NetworkNotSupported( config::defaults::DEFAULT_NETWORK, @@ -178,6 +165,7 @@ async fn _connect_with_mnemonic( let network: WalletNetwork = client.network.clone().into(); let mut w_state = state.write().await; w_state.add_client(network, client); + w_state.register_default_denoms(network); } account_for_default_network @@ -328,7 +316,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr #[tauri::command] pub async fn sign_in_with_password( password: String, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Signing in with password"); @@ -368,7 +356,7 @@ fn extract_first_mnemonic( pub async fn sign_in_with_password_and_account_id( account_id: &str, password: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Signing in with password"); @@ -420,7 +408,7 @@ pub async fn add_account_for_password( mnemonic: &str, password: &str, account_id: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!("Adding account for the current password: {account_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; @@ -462,7 +450,7 @@ pub async fn add_account_for_password( async fn set_state_with_all_accounts( stored_login: wallet_storage::StoredLogin, first_id_when_converting: wallet_storage::AccountId, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::trace!("Set state with accounts:"); let all_accounts: Vec<_> = stored_login @@ -503,7 +491,7 @@ async fn set_state_with_all_accounts( pub async fn remove_account_for_password( password: &str, account_id: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::info!("Removing account: {account_id}"); // Currently we only support a single, default, id in the wallet @@ -534,7 +522,7 @@ fn derive_address( #[tauri::command] pub async fn list_accounts( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::trace!("Listing accounts"); let state = state.read().await; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs index abfa3a48b5..faced61bce 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs @@ -1,19 +1,14 @@ -use std::convert::TryInto; -use std::sync::Arc; - -use tokio::sync::RwLock; - -use mixnet_contract_common::ContractStateParams; -use nym_wallet_types::admin::TauriContractStateParams; -use validator_client::nymd::Fee; - use crate::error::BackendError; use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; +use mixnet_contract_common::ContractStateParams; +use nym_wallet_types::admin::TauriContractStateParams; +use std::convert::TryInto; +use validator_client::nymd::Fee; #[tauri::command] pub async fn get_contract_settings( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Getting contract settings"); let res = nymd_client!(state).get_contract_settings().await?.into(); @@ -25,7 +20,7 @@ pub async fn get_contract_settings( pub async fn update_contract_settings( params: TauriContractStateParams, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; log::info!( diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index 099ea073cf..c9b38ca102 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -1,56 +1,56 @@ use crate::error::BackendError; -use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; use crate::{Gateway, MixNode}; -use nym_types::currency::MajorCurrencyAmount; +use nym_types::currency::DecCoin; use nym_types::gateway::GatewayBond; use nym_types::mixnode::MixNodeBond; use nym_types::transaction::TransactionExecuteResult; -use std::sync::Arc; -use tokio::sync::RwLock; -use validator_client::nymd::{CosmWasmCoin, Fee}; +use validator_client::nymd::{Coin, Fee}; #[tauri::command] pub async fn bond_gateway( gateway: Gateway, - pledge: MajorCurrencyAmount, + pledge: DecCoin, owner_signature: String, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let pledge_minor = pledge.clone().into(); + let guard = state.read().await; + let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Bond gateway: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}", - &gateway.identity_key, + ">>> Bond gateway: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}", + gateway.identity_key, pledge, - &pledge_minor, + pledge_base, fee, ); - let res = nymd_client!(state) - .bond_gateway(gateway, owner_signature, pledge_minor, fee) + let res = guard + .current_client()? + .nymd + .bond_gateway(gateway, owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn unbond_gateway( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!(">>> Unbond gateway, fee = {:?}", fee); - let res = nymd_client!(state).unbond_gateway(fee).await?; + let res = guard.current_client()?.nymd.unbond_gateway(fee).await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -58,43 +58,46 @@ pub async fn unbond_gateway( pub async fn bond_mixnode( mixnode: MixNode, owner_signature: String, - pledge: MajorCurrencyAmount, + pledge: DecCoin, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let pledge_minor = pledge.clone().into(); + let guard = state.read().await; + let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Bond mixnode: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}", + ">>> Bond mixnode: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}", mixnode.identity_key, pledge, - pledge_minor, + pledge_base, fee, ); - let res = nymd_client!(state) - .bond_mixnode(mixnode, owner_signature, pledge_minor, fee) + let res = guard + .current_client()? + .nymd + .bond_mixnode(mixnode, owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn unbond_mixnode( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!(">>> Unbond mixnode, fee = {:?}", fee); - let res = nymd_client!(state).unbond_mixnode(fee).await?; + let res = guard.current_client()?.nymd.unbond_mixnode(fee).await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -102,34 +105,43 @@ pub async fn unbond_mixnode( pub async fn update_mixnode( profit_margin_percent: u8, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Update mixnode: profit_margin_percent = {}, fee {:?}", profit_margin_percent, fee, ); - let res = nymd_client!(state) + let res = guard + .current_client()? + .nymd .update_mixnode_config(profit_margin_percent, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn mixnode_bond_details( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get mixnode bond details"); let guard = state.read().await; let client = guard.current_client()?; let bond = client.nymd.owns_mixnode(client.nymd.address()).await?; - let res = MixNodeBond::from_mixnet_contract_mixnode_bond(bond)?; + let res = bond + .map(|bond| { + guard + .registered_coins() + .map(|reg| MixNodeBond::from_mixnet_contract_mixnode_bond(bond, reg)) + }) + .transpose()? + .transpose()?; log::info!( "<<< identity_key = {:?}", res.as_ref().map(|r| r.mix_node.identity_key.to_string()) @@ -140,13 +152,21 @@ pub async fn mixnode_bond_details( #[tauri::command] pub async fn gateway_bond_details( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get gateway bond details"); let guard = state.read().await; let client = guard.current_client()?; let bond = client.nymd.owns_gateway(client.nymd.address()).await?; - let res = GatewayBond::from_mixnet_contract_gateway_bond(bond)?; + let res = bond + .map(|bond| { + guard + .registered_coins() + .map(|reg| GatewayBond::from_mixnet_contract_gateway_bond(bond, reg)) + }) + .transpose()? + .transpose()?; + log::info!( "<<< identity_key = {:?}", res.as_ref().map(|r| r.gateway.identity_key.to_string()) @@ -158,17 +178,23 @@ pub async fn gateway_bond_details( #[tauri::command] pub async fn get_operator_rewards( address: String, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Get operator rewards for {}", address); - let denom = state.read().await.current_network().base_mix_denom(); - let rewards_as_minor = nymd_client!(state).get_operator_rewards(address).await?; - let coin = CosmWasmCoin::new(rewards_as_minor.u128(), denom.as_ref()); - let amount: MajorCurrencyAmount = coin.into(); + let guard = state.read().await; + let network = guard.current_network(); + let denom = network.base_mix_denom(); + let reward_amount = guard + .current_client()? + .nymd + .get_operator_rewards(address) + .await?; + let base_coin = Coin::new(reward_amount.u128(), denom); + let display_coin: DecCoin = guard.attempt_convert_to_display_dec_coin(base_coin.clone())?; log::info!( - "<<< rewards_as_minor = {}, amount = {}", - rewards_as_minor, - amount + "<<< rewards_base = {}, rewards_display = {}", + base_coin, + display_coin ); - Ok(amount) + Ok(display_coin) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index fa81c32ab0..7e6f27ae8f 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -1,63 +1,66 @@ use crate::error::BackendError; -use crate::state::State; +use crate::state::WalletState; use crate::vesting::delegate::{ get_pending_vesting_delegation_events, vesting_undelegate_from_mixnode, }; use crate::{api_client, nymd_client}; -use cosmwasm_std::Coin as CosmWasmCoin; use mixnet_contract_common::IdentityKey; -use nym_types::currency::{CurrencyDenom, MajorCurrencyAmount}; +use nym_types::currency::DecCoin; use nym_types::delegation::{ - from_contract_delegation_events, Delegation, DelegationEvent, DelegationRecord, - DelegationWithEverything, DelegationsSummaryResponse, + Delegation, DelegationEvent, DelegationRecord, DelegationWithEverything, + DelegationsSummaryResponse, }; use nym_types::transaction::TransactionExecuteResult; use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; -use validator_client::nymd::Fee; +use validator_client::nymd::{Coin, Fee}; #[tauri::command] pub async fn get_pending_delegation_events( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get pending delegation events"); - let events = nymd_client!(state) - .get_pending_delegation_events(nymd_client!(state).address().to_string(), None) + let guard = state.read().await; + let reg = guard.registered_coins()?; + let client = guard.current_client()?; + + let events = client + .nymd + .get_pending_delegation_events(client.nymd.address().to_string(), None) .await?; log::info!("<<< {} pending delegation events", events.len()); log::trace!("<<< pending delegation events = {:?}", events); - match from_contract_delegation_events(events) { - Ok(res) => Ok(res), - Err(e) => Err(e.into()), - } + Ok(events + .into_iter() + .map(|event| DelegationEvent::from_mixnet_contract(event, reg)) + .collect::>()?) } #[tauri::command] pub async fn delegate_to_mixnode( identity: &str, - amount: MajorCurrencyAmount, + amount: DecCoin, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let delegation = amount.clone().into(); + let guard = state.read().await; + let delegation_base = guard.attempt_convert_to_base_coin(amount.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Delegate to mixnode: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}", + ">>> Delegate to mixnode: identity_key = {}, display_amount = {}, base_amount = {}, fee = {:?}", identity, amount, - delegation, + delegation_base, fee, ); let res = nymd_client!(state) - .delegate_to_mixnode(identity, delegation, fee) + .delegate_to_mixnode(identity, delegation_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -65,22 +68,25 @@ pub async fn delegate_to_mixnode( pub async fn undelegate_from_mixnode( identity: &str, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( ">>> Undelegate from mixnode: identity_key = {}, fee = {:?}", identity, fee ); - let res = nymd_client!(state) + let res = guard + .current_client()? + .nymd .remove_mixnode_delegation(identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -90,7 +96,7 @@ pub async fn undelegate_all_from_mixnode( uses_vesting_contract_tokens: bool, fee: Option, fee2: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!( ">>> Undelegate all from mixnode: identity_key = {}, uses_vesting_contract_tokens = {}, fee = {:?}", @@ -110,30 +116,31 @@ pub async fn undelegate_all_from_mixnode( struct DelegationWithHistory { pub delegation: Delegation, - pub amount_sum: MajorCurrencyAmount, + pub amount_sum: DecCoin, pub history: Vec, pub uses_vesting_contract_tokens: bool, } #[tauri::command] pub async fn get_all_mix_delegations( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get all mixnode delegations"); + let guard = state.read().await; + let client = guard.current_client()?; + let reg = guard.registered_coins()?; + // TODO: add endpoint to validator API to get a single mix node bond - let mixnodes = api_client!(state).get_mixnodes().await?; + let mixnodes = client.validator_api.get_mixnodes().await?; - let address = nymd_client!(state).address().to_string(); - - let denom_minor = state.read().await.current_network().base_mix_denom(); - let denom: CurrencyDenom = denom_minor.clone().try_into()?; + let address = client.nymd.address(); + let network = guard.current_network(); + let display_mix_denom = network.display_mix_denom(); + let base_mix_denom = network.base_mix_denom(); log::info!(" >>> Get delegations"); - let delegations = nymd_client!(state) - .get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging - .await? - .delegations; + let delegations = client.get_all_delegator_delegations(address).await?; log::info!(" <<< {} delegations", delegations.len()); // first get pending events from the mixnet contract (operations made with unlocked tokens) @@ -152,20 +159,21 @@ pub async fn get_all_mix_delegations( let mut map: HashMap = HashMap::new(); - for pending_event in pending_events_for_account.clone() { + for pending_event in &pending_events_for_account { if delegations .iter() .any(|d| d.node_identity == pending_event.node_identity) { let amount = pending_event .amount - .unwrap_or_else(|| MajorCurrencyAmount::zero(&denom)); + .clone() + .unwrap_or_else(|| DecCoin::zero(display_mix_denom)); let delegation = DelegationWithHistory { delegation: Delegation { amount: amount.clone(), - node_identity: pending_event.node_identity, - proxy: pending_event.proxy, - owner: pending_event.address, + node_identity: pending_event.node_identity.clone(), + proxy: pending_event.proxy.clone(), // TODO: ask @MS about delegations via vesting contract => surely we'd have proxy there? + owner: pending_event.address.clone(), block_height: pending_event.block_height, }, amount_sum: amount, @@ -178,11 +186,13 @@ pub async fn get_all_mix_delegations( for d in delegations { // create record of delegation - let delegated_on_iso_datetime = nymd_client!(state) + let delegated_on_iso_datetime = client + .nymd .get_block_timestamp(Some(d.block_height as u32)) .await? .to_rfc3339(); - let amount: MajorCurrencyAmount = d.amount.clone().into(); + let amount = guard.attempt_convert_to_display_dec_coin(d.amount.clone().into())?; + let record = DelegationRecord { amount: amount.clone(), block_height: d.block_height, @@ -193,14 +203,16 @@ pub async fn get_all_mix_delegations( let entry = map .entry(d.node_identity.clone()) .or_insert(DelegationWithHistory { - delegation: d.try_into()?, + delegation: Delegation::from_mixnet_contract(d, reg)?, history: vec![], - amount_sum: MajorCurrencyAmount::zero(&amount.denom), + amount_sum: DecCoin::zero(display_mix_denom), uses_vesting_contract_tokens: false, }); + debug_assert_eq!(entry.amount_sum.denom, amount.denom); + entry.history.push(record); - entry.amount_sum = entry.amount_sum.clone() + amount; + entry.amount_sum.amount += amount.amount; entry.uses_vesting_contract_tokens = entry.uses_vesting_contract_tokens || entry.delegation.proxy.is_some(); } @@ -229,25 +241,25 @@ pub async fn get_all_mix_delegations( .iter() .find(|m| m.mix_node.identity_key == node_identity); - let pledge_amount: Option = - mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok()); + let pledge_amount = mixnode + .map(|m| guard.attempt_convert_to_display_dec_coin(m.pledge_amount.clone().into())) + .transpose()?; - let total_delegation: Option = - mixnode.and_then(|m| m.total_delegation.clone().try_into().ok()); + let total_delegation = mixnode + .map(|m| guard.attempt_convert_to_display_dec_coin(m.total_delegation.clone().into())) + .transpose()?; let profit_margin_percent: Option = mixnode.map(|m| m.mix_node.profit_margin_percent); log::trace!(" >>> Get accumulated rewards: address = {}", address); - let accumulated_rewards = match nymd_client!(state) - .get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone()) + let accumulated_rewards = match client + .nymd + .get_delegator_rewards(address.to_string(), node_identity.clone(), proxy.clone()) .await { Ok(rewards) => { - let reward = CosmWasmCoin { - denom: denom_minor.to_string(), - amount: rewards, - }; - let amount = MajorCurrencyAmount::from(reward); + let reward = Coin::new(rewards.u128(), base_mix_denom); + let amount = guard.attempt_convert_to_display_dec_coin(reward)?; log::trace!(" <<< rewards = {}, amount = {}", rewards, amount); Some(amount) } @@ -338,40 +350,52 @@ pub async fn get_delegator_rewards( address: String, mix_identity: IdentityKey, proxy: Option, - state: tauri::State<'_, Arc>>, -) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + state: tauri::State<'_, WalletState>, +) -> Result { log::info!( ">>> Get delegator rewards: mix_identity = {}, proxy = {:?}", mix_identity, proxy ); - let res = nymd_client!(state) + let guard = state.read().await; + let network = guard.current_network(); + let denom = network.base_mix_denom(); + let reward_amount = guard + .current_client()? + .nymd .get_delegator_rewards(address, mix_identity, proxy) .await?; - let coin = CosmWasmCoin::new(res.u128(), denom_minor.as_ref()); - let amount = coin.into(); - log::info!(">>> res = {}, amount = {}", res, amount); - Ok(amount) + let base_coin = Coin::new(reward_amount.u128(), denom); + let display_coin: DecCoin = guard.attempt_convert_to_display_dec_coin(base_coin.clone())?; + + log::info!( + "<<< rewards_base = {}, rewards_display = {}", + base_coin, + display_coin + ); + Ok(display_coin) } #[tauri::command] pub async fn get_delegation_summary( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Get delegation summary"); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let denom: CurrencyDenom = denom_minor.clone().try_into()?; + let guard = state.read().await; + let network = guard.current_network(); + let display_mix_denom = network.display_mix_denom(); let delegations = get_all_mix_delegations(state.clone()).await?; - let mut total_delegations = MajorCurrencyAmount::zero(&denom); - let mut total_rewards = MajorCurrencyAmount::zero(&denom); + let mut total_delegations = DecCoin::zero(display_mix_denom); + let mut total_rewards = DecCoin::zero(display_mix_denom); - for d in delegations.clone() { - total_delegations = total_delegations + d.amount; - if let Some(rewards) = d.accumulated_rewards { - total_rewards = total_rewards + rewards; + for d in &delegations { + debug_assert_eq!(d.amount.denom, display_mix_denom); + total_delegations.amount += d.amount.amount; + if let Some(rewards) = &d.accumulated_rewards { + debug_assert_eq!(rewards.denom, display_mix_denom); + total_rewards.amount += rewards.amount; } } log::info!( @@ -391,7 +415,7 @@ pub async fn get_delegation_summary( #[tauri::command] pub async fn get_all_pending_delegation_events( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get all pending delegation events"); diff --git a/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs b/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs index 5203e5bc62..2ca8fe0c05 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/epoch.rs @@ -1,13 +1,11 @@ use crate::error::BackendError; use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; use nym_wallet_types::epoch::Epoch; -use std::sync::Arc; -use tokio::sync::RwLock; #[tauri::command] pub async fn get_current_epoch( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Get curren epoch"); let interval = nymd_client!(state).get_current_epoch().await?; diff --git a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs index c335ed0624..983c870c61 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs @@ -1,48 +1,50 @@ use crate::error::BackendError; use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; use crate::vesting::rewards::{vesting_claim_delegator_reward, vesting_compound_delegator_reward}; use mixnet_contract_common::IdentityKey; use nym_types::transaction::TransactionExecuteResult; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::Fee; #[tauri::command] pub async fn claim_operator_reward( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { // TODO: handle operator bonding with vesting contract log::info!(">>> Claim operator reward"); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_claim_operator_reward(fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn compound_operator_reward( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { // TODO: handle operator bonding with vesting contract log::info!(">>> Compound operator reward"); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_compound_operator_reward(fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -50,21 +52,23 @@ pub async fn compound_operator_reward( pub async fn claim_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!( ">>> Claim delegator reward: identity_key = {}", mix_identity ); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_claim_delegator_reward(mix_identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -72,21 +76,23 @@ pub async fn claim_delegator_reward( pub async fn compound_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!( ">>> Compound delegator reward: identity_key = {}", mix_identity ); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_compound_delegator_reward(mix_identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -94,7 +100,7 @@ pub async fn compound_delegator_reward( pub async fn claim_locked_and_unlocked_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!( ">>> Claim delegator reward (locked and unlocked): identity_key = {}", @@ -140,7 +146,7 @@ pub async fn claim_locked_and_unlocked_delegator_reward( pub async fn compound_locked_and_unlocked_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!( ">>> Compound delegator reward (locked and unlocked): identity_key = {}", diff --git a/nym-wallet/src-tauri/src/operations/mixnet/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs index f29d4b834c..0e4cbdd6a3 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -1,46 +1,43 @@ use crate::error::BackendError; -use crate::nymd_client; -use crate::state::State; -use nym_types::currency::MajorCurrencyAmount; +use crate::state::WalletState; +use nym_types::currency::DecCoin; use nym_types::transaction::{SendTxResult, TransactionDetails}; use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::{AccountId, Fee}; #[tauri::command] pub async fn send( address: &str, - amount: MajorCurrencyAmount, + amount: DecCoin, memo: String, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let address = AccountId::from_str(address)?; - let from_address = nymd_client!(state).address().to_string(); - let amount2 = amount.clone().into(); + let guard = state.read().await; + let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?; + + let to_address = AccountId::from_str(address)?; + let from_address = guard.current_client()?.nymd.address().to_string(); + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( - ">>> Send: amount = {}, minor_amount = {:?}, from = {}, to = {}, fee = {:?}", + ">>> Send: display_amount = {}, base_amount = {}, from = {}, to = {}, fee = {:?}", amount, - amount2, + amount_base, from_address, - address.as_ref(), + to_address, fee, ); - let raw_res = nymd_client!(state) - .send(&address, vec![amount2], memo, fee) + let raw_res = guard + .current_client()? + .nymd + .send(&to_address, vec![amount_base], memo, fee) .await?; log::info!("<<< tx hash = {}", raw_res.hash.to_string()); let res = SendTxResult::new( raw_res, - TransactionDetails { - from_address, - to_address: address.to_string(), - amount, - }, - denom_minor.as_ref(), - )?; + TransactionDetails::new(amount, from_address, to_address.to_string()), + fee_amount, + ); log::trace!("<<< {:?}", res); Ok(res) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index 053f61b35c..50a559105b 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -3,17 +3,14 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; -use crate::simulate::detailed_fee; -use crate::State; +use crate::WalletState; use mixnet_contract_common::{ContractStateParams, ExecuteMsg}; use nym_wallet_types::admin::TauriContractStateParams; -use std::sync::Arc; -use tokio::sync::RwLock; #[tauri::command] pub async fn simulate_update_contract_settings( params: TauriContractStateParams, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; @@ -28,5 +25,5 @@ pub async fn simulate_update_contract_settings( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index 282ff5b0fb..2796e02097 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -3,24 +3,22 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; -use crate::simulate::detailed_fee; -use crate::state::State; -use nym_types::currency::MajorCurrencyAmount; +use crate::state::WalletState; +use nym_types::currency::DecCoin; use std::str::FromStr; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::{AccountId, MsgSend}; #[tauri::command] pub async fn simulate_send( address: &str, - amount: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + amount: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; + let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?; let to_address = AccountId::from_str(address)?; - let amount = vec![amount.into()]; + let amount = vec![amount_base.into()]; let client = guard.current_client()?; let from_address = client.nymd.address().clone(); @@ -33,5 +31,5 @@ pub async fn simulate_send( }; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index d00d8c68be..794a1f09fe 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -3,23 +3,20 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; -use crate::simulate::detailed_fee; -use crate::State; +use crate::WalletState; use mixnet_contract_common::IdentityKey; use mixnet_contract_common::{ExecuteMsg, Gateway, MixNode}; -use nym_types::currency::MajorCurrencyAmount; -use std::sync::Arc; -use tokio::sync::RwLock; +use nym_types::currency::DecCoin; #[tauri::command] pub async fn simulate_bond_gateway( gateway: Gateway, - pledge: MajorCurrencyAmount, + pledge: DecCoin, owner_signature: String, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let pledge = pledge.into(); + let pledge = guard.attempt_convert_to_base_coin(pledge)?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address(); @@ -35,12 +32,12 @@ pub async fn simulate_bond_gateway( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_unbond_gateway( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; @@ -53,18 +50,18 @@ pub async fn simulate_unbond_gateway( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_bond_mixnode( mixnode: MixNode, owner_signature: String, - pledge: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + pledge: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let pledge = pledge.into(); + let pledge = guard.attempt_convert_to_base_coin(pledge)?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address(); @@ -79,12 +76,12 @@ pub async fn simulate_bond_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_unbond_mixnode( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; @@ -97,13 +94,13 @@ pub async fn simulate_unbond_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_update_mixnode( profit_margin_percent: u8, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; @@ -118,17 +115,17 @@ pub async fn simulate_update_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_delegate_to_mixnode( identity: &str, - amount: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + amount: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let delegation = amount.into(); + let delegation = guard.attempt_convert_to_base_coin(amount)?; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address(); @@ -142,15 +139,14 @@ pub async fn simulate_delegate_to_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_undelegate_from_mixnode( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - println!("Called"); let guard = state.read().await; let client = guard.current_client()?; let mixnet_contract = client.nymd.mixnet_contract_address(); @@ -164,53 +160,57 @@ pub async fn simulate_undelegate_from_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_claim_operator_reward( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client.nymd.simulate_claim_operator_reward(None).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_compound_operator_reward( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client.nymd.simulate_compound_operator_reward(None).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_claim_delegator_reward( mix_identity: IdentityKey, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_claim_delegator_reward(mix_identity, None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_compound_delegator_reward( mix_identity: IdentityKey, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_compound_delegator_reward(mix_identity, None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/mod.rs b/nym-wallet/src-tauri/src/operations/simulate/mod.rs index 61e4179687..256f6f9a84 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mod.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mod.rs @@ -3,29 +3,15 @@ use cosmrs::tx; use cosmrs::tx::Gas; -use nym_types::currency::MajorCurrencyAmount; use nym_types::fees::FeeDetails; -use validator_client::nymd::cosmwasm_client::types::{GasInfo, SimulateResponse}; -use validator_client::nymd::{ - CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice, SigningNymdClient, -}; -use validator_client::Client; +use validator_client::nymd::cosmwasm_client::types::GasInfo; +use validator_client::nymd::{CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice}; pub mod admin; pub mod cosmos; pub mod mixnet; pub mod vesting; -pub(crate) fn detailed_fee( - client: &Client, - simulate_response: SimulateResponse, -) -> FeeDetails { - let gas_price = client.nymd.gas_price().clone(); - let gas_adjustment = client.nymd.gas_adjustment(); - - SimulateResult::new(simulate_response.gas_info, gas_price, gas_adjustment).detailed_fee() -} - // technically we could have also exposed a result: Option field from the SimulateResponse, // but in the context of the wallet it's really irrelevant and useless for the time being pub(crate) struct SimulateResult { @@ -50,24 +36,16 @@ impl SimulateResult { } } - pub fn detailed_fee(&self) -> FeeDetails { - let amount = self.to_fee_amount().map(MajorCurrencyAmount::from); - FeeDetails { - amount, - fee: self.to_fee(), - } - } - - fn adjusted_gas(&self) -> Option { + pub(crate) fn adjusted_gas(&self) -> Option { self.gas_info .map(|gas_info| gas_info.gas_used.adjust_gas(self.gas_adjustment)) } - fn to_fee_amount(&self) -> Option { + pub(crate) fn to_fee_amount(&self) -> Option { self.adjusted_gas().map(|gas| &self.gas_price * gas) } - fn to_fee(&self) -> Fee { + pub(crate) fn to_fee(&self) -> Fee { self.adjusted_gas() .map(|gas| { let fee_amount = &self.gas_price * gas; diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index fc9779ddf0..d8850d202c 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -3,24 +3,21 @@ use crate::error::BackendError; use crate::operations::simulate::FeeDetails; -use crate::simulate::detailed_fee; -use crate::State; +use crate::WalletState; use mixnet_contract_common::IdentityKey; use mixnet_contract_common::{Gateway, MixNode}; -use nym_types::currency::MajorCurrencyAmount; -use std::sync::Arc; -use tokio::sync::RwLock; +use nym_types::currency::DecCoin; use vesting_contract_common::ExecuteMsg; #[tauri::command] pub async fn simulate_vesting_bond_gateway( gateway: Gateway, - pledge: MajorCurrencyAmount, + pledge: DecCoin, owner_signature: String, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let pledge = pledge.into(); + let pledge = guard.attempt_convert_to_base_coin(pledge)?; let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address(); @@ -30,18 +27,18 @@ pub async fn simulate_vesting_bond_gateway( &ExecuteMsg::BondGateway { gateway, owner_signature, - amount: pledge, + amount: pledge.into(), }, vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_unbond_gateway( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; @@ -55,18 +52,18 @@ pub async fn simulate_vesting_unbond_gateway( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_bond_mixnode( mixnode: MixNode, owner_signature: String, - pledge: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + pledge: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let pledge = pledge.into(); + let pledge = guard.attempt_convert_to_base_coin(pledge)?; let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address(); @@ -76,18 +73,18 @@ pub async fn simulate_vesting_bond_mixnode( &ExecuteMsg::BondMixnode { mix_node: mixnode, owner_signature, - amount: pledge, + amount: pledge.into(), }, vec![], )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_unbond_mixnode( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; @@ -101,13 +98,13 @@ pub async fn simulate_vesting_unbond_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_update_mixnode( profit_margin_percent: u8, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; @@ -123,17 +120,17 @@ pub async fn simulate_vesting_update_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_delegate_to_mixnode( identity: &str, - amount: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + amount: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let amount = amount.into(); + let amount = guard.attempt_convert_to_base_coin(amount)?.into(); let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address(); @@ -148,13 +145,13 @@ pub async fn simulate_vesting_delegate_to_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_undelegate_from_mixnode( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; @@ -170,16 +167,16 @@ pub async fn simulate_vesting_undelegate_from_mixnode( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_withdraw_vested_coins( - amount: MajorCurrencyAmount, - state: tauri::State<'_, Arc>>, + amount: DecCoin, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; - let amount = amount.into(); + let amount = guard.attempt_convert_to_base_coin(amount)?.into(); let client = guard.current_client()?; let vesting_contract = client.nymd.vesting_contract_address(); @@ -191,59 +188,63 @@ pub async fn simulate_withdraw_vested_coins( )?; let result = client.nymd.simulate(vec![msg]).await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_claim_operator_reward( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_vesting_claim_operator_reward(None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_compound_operator_reward( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_vesting_compound_operator_reward(None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_claim_delegator_reward( mix_identity: IdentityKey, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_vesting_claim_delegator_reward(mix_identity, None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } #[tauri::command] pub async fn simulate_vesting_compound_delegator_reward( mix_identity: IdentityKey, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; let client = guard.current_client()?; + let result = client .nymd .simulate_vesting_compound_delegator_reward(mix_identity, None) .await?; - Ok(detailed_fee(client, result)) + guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/validator_api/status.rs index 721c39bf82..7ff60d3e6b 100644 --- a/nym-wallet/src-tauri/src/operations/validator_api/status.rs +++ b/nym-wallet/src-tauri/src/operations/validator_api/status.rs @@ -3,9 +3,7 @@ use crate::api_client; use crate::error::BackendError; -use crate::state::State; -use std::sync::Arc; -use tokio::sync::RwLock; +use crate::state::WalletState; use validator_client::models::{ CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, @@ -15,7 +13,7 @@ use validator_client::models::{ pub async fn mixnode_core_node_status( identity: &str, since: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state) .get_mixnode_core_status_count(identity, since) @@ -26,7 +24,7 @@ pub async fn mixnode_core_node_status( pub async fn gateway_core_node_status( identity: &str, since: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state) .get_gateway_core_status_count(identity, since) @@ -36,7 +34,7 @@ pub async fn gateway_core_node_status( #[tauri::command] pub async fn mixnode_status( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state).get_mixnode_status(identity).await?) } @@ -44,7 +42,7 @@ pub async fn mixnode_status( #[tauri::command] pub async fn mixnode_reward_estimation( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state) .get_mixnode_reward_estimation(identity) @@ -54,7 +52,7 @@ pub async fn mixnode_reward_estimation( #[tauri::command] pub async fn mixnode_stake_saturation( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state) .get_mixnode_stake_saturation(identity) @@ -64,7 +62,7 @@ pub async fn mixnode_stake_saturation( #[tauri::command] pub async fn mixnode_inclusion_probability( identity: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { Ok(api_client!(state) .get_mixnode_inclusion_probability(identity) diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index b76a442708..c567e4e908 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/bond.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -1,48 +1,50 @@ use crate::error::BackendError; use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; use crate::{Gateway, MixNode}; -use nym_types::currency::MajorCurrencyAmount; +use nym_types::currency::DecCoin; use nym_types::transaction::TransactionExecuteResult; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::{Fee, VestingSigningClient}; #[tauri::command] pub async fn vesting_bond_gateway( gateway: Gateway, - pledge: MajorCurrencyAmount, + pledge: DecCoin, owner_signature: String, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let pledge_minor = pledge.clone().into(); + let guard = state.read().await; + let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Bond gateway with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}", - gateway.identity_key, - pledge, - pledge_minor, - fee, - ); - let res = nymd_client!(state) - .vesting_bond_gateway(gateway, &owner_signature, pledge_minor, fee) + ">>> Bond gateway with locked tokens: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}", + gateway.identity_key, + pledge, + pledge_base, + fee, + ); + let res = guard + .current_client()? + .nymd + .vesting_bond_gateway(gateway, &owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn vesting_unbond_gateway( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Unbond gateway bonded with locked tokens, fee = {:?}", fee @@ -51,8 +53,7 @@ pub async fn vesting_unbond_gateway( log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -60,71 +61,81 @@ pub async fn vesting_unbond_gateway( pub async fn vesting_bond_mixnode( mixnode: MixNode, owner_signature: String, - pledge: MajorCurrencyAmount, + pledge: DecCoin, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let pledge_minor = pledge.clone().into(); + let guard = state.read().await; + let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Bond mixnode with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}", + ">>> Bond mixnode with locked tokens: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}", mixnode.identity_key, pledge, - pledge_minor, + pledge_base, fee ); - let res = nymd_client!(state) - .vesting_bond_mixnode(mixnode, &owner_signature, pledge_minor, fee) + let res = guard + .current_client()? + .nymd + .vesting_bond_mixnode(mixnode, &owner_signature, pledge_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn vesting_unbond_mixnode( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Unbond mixnode bonded with locked tokens, fee = {:?}", fee ); - let res = nymd_client!(state).vesting_unbond_mixnode(fee).await?; + let res = guard + .current_client()? + .nymd + .vesting_unbond_mixnode(fee) + .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn withdraw_vested_coins( - amount: MajorCurrencyAmount, + amount: DecCoin, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let amount_minor = amount.clone().into(); + let guard = state.read().await; + let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Withdraw vested liquid coins: amount = {}, amount_minor = {}, fee = {:?}", + ">>> Withdraw vested liquid coins: amount_base = {}, amount_base = {}, fee = {:?}", amount, - amount_minor, + amount_base, fee ); - let res = nymd_client!(state) - .withdraw_vested_coins(amount_minor, fee) + let res = guard + .current_client()? + .nymd + .withdraw_vested_coins(amount_base, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -132,21 +143,23 @@ pub async fn withdraw_vested_coins( pub async fn vesting_update_mixnode( profit_margin_percent: u8, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Update mixnode bonded with locked tokens: profit_margin_percent = {}, fee = {:?}", profit_margin_percent, fee, ); - let res = nymd_client!(state) + let res = guard + .current_client()? + .nymd .vesting_update_mixnode_config(profit_margin_percent, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index ab6fba6a59..9ddde467a8 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -1,23 +1,18 @@ -use std::sync::Arc; - -use tokio::sync::RwLock; - -use nym_types::currency::MajorCurrencyAmount; -use nym_types::delegation::{from_contract_delegation_events, DelegationEvent}; +use crate::error::BackendError; +use crate::state::WalletState; +use nym_types::currency::DecCoin; +use nym_types::delegation::DelegationEvent; use nym_types::transaction::TransactionExecuteResult; use validator_client::nymd::{Fee, VestingSigningClient}; -use crate::error::BackendError; -use crate::nymd_client; -use crate::state::State; - #[tauri::command] pub async fn get_pending_vesting_delegation_events( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Get pending delegations from vesting contract"); let guard = state.read().await; + let reg = guard.registered_coins()?; let client = &guard.current_client()?.nymd; let vesting_contract = client.vesting_contract_address(); @@ -31,36 +26,39 @@ pub async fn get_pending_vesting_delegation_events( log::info!("<<< {} events", events.len()); log::trace!("<<< {:?}", events); - match from_contract_delegation_events(events) { - Ok(res) => Ok(res), - Err(e) => Err(e.into()), - } + Ok(events + .into_iter() + .map(|event| DelegationEvent::from_mixnet_contract(event, reg)) + .collect::>()?) } #[tauri::command] pub async fn vesting_delegate_to_mixnode( identity: &str, - amount: MajorCurrencyAmount, + amount: DecCoin, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); - let delegation = amount.clone().into(); + let guard = state.read().await; + let delegation = guard.attempt_convert_to_base_coin(amount.clone())?; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + log::info!( - ">>> Delegate to mixnode with locked tokens: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}", + ">>> Delegate to mixnode with locked tokens: identity_key = {}, amount_display = {}, amount_base = {}, fee = {:?}", identity, amount, delegation, fee ); - let res = nymd_client!(state) + let res = guard + .current_client()? + .nymd .vesting_delegate_to_mixnode(identity, delegation, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -68,21 +66,23 @@ pub async fn vesting_delegate_to_mixnode( pub async fn vesting_undelegate_from_mixnode( identity: &str, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { - let denom_minor = state.read().await.current_network().base_mix_denom(); + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); log::info!( ">>> Undelegate from mixnode delegated with locked tokens: identity_key = {}, fee = {:?}", identity, fee, ); - let res = nymd_client!(state) + let res = guard + .current_client()? + .nymd .vesting_undelegate_from_mixnode(identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs index 72c85f69d7..7d0d726a1c 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/queries.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -1,92 +1,106 @@ -use std::sync::Arc; - +use crate::error::BackendError; +use crate::nymd_client; +use crate::state::WalletState; use cosmwasm_std::Timestamp; -use tokio::sync::RwLock; - -use nym_types::currency::MajorCurrencyAmount; +use nym_types::currency::DecCoin; use nym_types::vesting::VestingAccountInfo; use nym_types::vesting::{OriginalVestingResponse, PledgeData}; use validator_client::nymd::VestingQueryClient; use vesting_contract_common::Period; -use crate::error::BackendError; -use crate::nymd_client; -use crate::state::State; - #[tauri::command] pub async fn locked_coins( block_time: Option, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query locked coins"); - let res = nymd_client!(state) + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nymd .locked_coins( - nymd_client!(state).address().as_ref(), + client.nymd.address().as_ref(), block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< locked coins = {}", res); - Ok(res) + .await?; + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< locked coins = {}", display); + Ok(display) } #[tauri::command] pub async fn spendable_coins( block_time: Option, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query spendable coins"); - let res = nymd_client!(state) + let guard = state.read().await; + let client = guard.current_client()?; + + let res = client + .nymd .spendable_coins( - nymd_client!(state).address().as_ref(), + client.nymd.address().as_ref(), block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< spendable coins = {}", res); - Ok(res) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< spendable coins = {}", display); + Ok(display) } #[tauri::command] pub async fn vested_coins( vesting_account_address: &str, block_time: Option, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query vested coins"); - let res = nymd_client!(state) + let guard = state.read().await; + + let res = guard + .current_client()? + .nymd .vested_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< vested coins = {}", res); - Ok(res) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< vested coins = {}", display); + Ok(display) } #[tauri::command] pub async fn vesting_coins( vesting_account_address: &str, block_time: Option, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query vesting coins"); - let res = nymd_client!(state) + let guard = state.read().await; + + let res = guard + .current_client()? + .nymd .vesting_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< vesting coins = {}", res); - Ok(res) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< vesting coins = {}", display); + Ok(display) } #[tauri::command] pub async fn vesting_start_time( vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query vesting start time"); let res = nymd_client!(state) @@ -100,7 +114,7 @@ pub async fn vesting_start_time( #[tauri::command] pub async fn vesting_end_time( vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query vesting end time"); let res = nymd_client!(state) @@ -114,13 +128,19 @@ pub async fn vesting_end_time( #[tauri::command] pub async fn original_vesting( vesting_account_address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query original vesting"); - let res = nymd_client!(state) + let guard = state.read().await; + let reg = guard.registered_coins()?; + + let res = guard + .current_client()? + .nymd .original_vesting(vesting_account_address) - .await? - .try_into()?; + .await?; + + let res = OriginalVestingResponse::from_vesting_contract(res, reg)?; log::info!("<<< {:?}", res); Ok(res) } @@ -129,18 +149,23 @@ pub async fn original_vesting( pub async fn delegated_free( vesting_account_address: &str, block_time: Option, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query delegated free"); - let res = nymd_client!(state) + let guard = state.read().await; + + let res = guard + .current_client()? + .nymd .delegated_free( vesting_account_address, block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< delegated free = {}", res); - Ok(res) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< delegated free = {}", display); + Ok(display) } /// Returns the total amount of delegated tokens that have vested @@ -148,30 +173,42 @@ pub async fn delegated_free( pub async fn delegated_vesting( block_time: Option, vesting_account_address: &str, - state: tauri::State<'_, Arc>>, -) -> Result { + state: tauri::State<'_, WalletState>, +) -> Result { log::info!(">>> Query delegated vesting"); - let res = nymd_client!(state) + let guard = state.read().await; + + let res = guard + .current_client()? + .nymd .delegated_vesting( vesting_account_address, block_time.map(Timestamp::from_seconds), ) - .await? - .into(); - log::info!("<<< delegated_vesting = {}", res); - Ok(res) + .await?; + + let display = guard.attempt_convert_to_display_dec_coin(res)?; + log::info!("<<< delegated_vesting = {}", display); + Ok(display) } #[tauri::command] pub async fn vesting_get_mixnode_pledge( address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Query vesting get mixnode pledge"); - let res = nymd_client!(state) + let guard = state.read().await; + let reg = guard.registered_coins()?; + + let res = guard + .current_client()? + .nymd .get_mixnode_pledge(address) .await? - .and_then(PledgeData::and_then); + .map(|pledge| PledgeData::from_vesting_contract(pledge, reg)) + .transpose()?; + log::info!("<<< {:?}", res); Ok(res) } @@ -179,13 +216,20 @@ pub async fn vesting_get_mixnode_pledge( #[tauri::command] pub async fn vesting_get_gateway_pledge( address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { log::info!(">>> Query vesting get gateway pledge"); - let res = nymd_client!(state) + let guard = state.read().await; + let reg = guard.registered_coins()?; + + let res = guard + .current_client()? + .nymd .get_gateway_pledge(address) .await? - .and_then(PledgeData::and_then); + .map(|pledge| PledgeData::from_vesting_contract(pledge, reg)) + .transpose()?; + log::info!("<<< {:?}", res); Ok(res) } @@ -193,7 +237,7 @@ pub async fn vesting_get_gateway_pledge( #[tauri::command] pub async fn get_current_vesting_period( address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query current vesting period"); let res = nymd_client!(state) @@ -206,10 +250,15 @@ pub async fn get_current_vesting_period( #[tauri::command] pub async fn get_account_info( address: &str, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Query account info"); - let res = nymd_client!(state).get_account(address).await?.try_into()?; + let guard = state.read().await; + let res = guard.registered_coins()?; + + let vesting_account = guard.current_client()?.nymd.get_account(address).await?; + let res = VestingAccountInfo::from_vesting_contract(vesting_account, res)?; + log::info!("<<< {:?}", res); Ok(res) } diff --git a/nym-wallet/src-tauri/src/operations/vesting/rewards.rs b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs index 5912c95f2e..b3a190c1ff 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/rewards.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/rewards.rs @@ -1,44 +1,46 @@ use crate::error::BackendError; -use crate::nymd_client; -use crate::state::State; +use crate::state::WalletState; use mixnet_contract_common::IdentityKey; use nym_types::transaction::TransactionExecuteResult; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::Fee; #[tauri::command] pub async fn vesting_claim_operator_reward( - state: tauri::State<'_, Arc>>, + fee: Option, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Vesting account: claim operator reward"); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_vesting_claim_operator_reward(None) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } #[tauri::command] pub async fn vesting_compound_operator_reward( fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!(">>> Vesting account: compound operator reward"); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_vesting_compound_operator_reward(fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -46,21 +48,23 @@ pub async fn vesting_compound_operator_reward( pub async fn vesting_claim_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!( ">>> Vesting account: claim delegator reward: identity_key = {}", mix_identity ); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_vesting_claim_delegator_reward(mix_identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } @@ -68,20 +72,22 @@ pub async fn vesting_claim_delegator_reward( pub async fn vesting_compound_delegator_reward( mix_identity: IdentityKey, fee: Option, - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result { log::info!( ">>> Vesting account: compound delegator reward: identity_key = {}", mix_identity ); - let denom_minor = state.read().await.current_network().base_mix_denom(); - let res = nymd_client!(state) + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd .execute_vesting_compound_delegator_reward(mix_identity, fee) .await?; log::info!("<<< tx hash = {}", res.transaction_hash); log::trace!("<<< {:?}", res); Ok(TransactionExecuteResult::from_execute_result( - res, - denom_minor.as_ref(), + res, fee_amount, )?) } diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 1095d872b6..3abcb29324 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,20 +1,22 @@ use crate::config; use crate::error::BackendError; +use crate::simulate::SimulateResult; +use itertools::Itertools; +use log::warn; +use nym_types::currency::{DecCoin, RegisteredCoins}; +use nym_types::fees::FeeDetails; use nym_wallet_types::network::Network; use nym_wallet_types::network_config; - -use strum::IntoEnumIterator; -use validator_client::nymd::{AccountId as CosmosAccountId, SigningNymdClient}; -use validator_client::Client; - -use itertools::Itertools; use once_cell::sync::Lazy; -use tokio::sync::RwLock; -use url::Url; - use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; +use strum::IntoEnumIterator; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use url::Url; +use validator_client::nymd::cosmwasm_client::types::SimulateResponse; +use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, SigningNymdClient}; +use validator_client::Client; // Some hardcoded metadata overrides static METADATA_OVERRIDES: Lazy> = Lazy::new(|| { @@ -28,7 +30,7 @@ static METADATA_OVERRIDES: Lazy> = Lazy::new(|| { #[tauri::command] pub async fn load_config_from_files( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { state.write().await.load_config_files(); Ok(()) @@ -36,13 +38,30 @@ pub async fn load_config_from_files( #[tauri::command] pub async fn save_config_to_files( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { state.read().await.save_config_files() } +#[derive(Default, Clone)] +pub struct WalletState { + inner: Arc>, +} + +impl WalletState { + // not the best API, but those are exposed here for backwards compatibility with the existing + // state type assumptions so that we wouldn't need to fix it up everywhere at once + pub(crate) async fn read(&self) -> RwLockReadGuard<'_, WalletStateInner> { + self.inner.read().await + } + + pub(crate) async fn write(&self) -> RwLockWriteGuard<'_, WalletStateInner> { + self.inner.write().await + } +} + #[derive(Default)] -pub struct State { +pub struct WalletStateInner { config: config::Config, signing_clients: HashMap>, current_network: Network, @@ -56,6 +75,7 @@ pub struct State { /// We fetch (and cache) some metadata, such as names, when available validator_metadata: HashMap, + registered_coins: HashMap, } pub(crate) struct WalletAccountIds { @@ -65,7 +85,70 @@ pub(crate) struct WalletAccountIds { pub addresses: HashMap, } -impl State { +impl WalletStateInner { + // note that `Coin` is ALWAYS the base coin + pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result { + let registered_coins = self + .registered_coins + .get(&self.current_network) + .ok_or_else(|| BackendError::UnknownCoinDenom(coin.denom.clone()))?; + + Ok(registered_coins.attempt_convert_to_base_coin(coin)?) + } + + pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result { + let registered_coins = self + .registered_coins + .get(&self.current_network) + .ok_or_else(|| BackendError::UnknownCoinDenom(coin.denom.clone()))?; + + Ok(registered_coins.attempt_convert_to_display_dec_coin(coin)?) + } + + pub(crate) fn registered_coins(&self) -> Result<&RegisteredCoins, BackendError> { + self.registered_coins + .get(&self.current_network) + .ok_or(BackendError::NoCoinsRegistered { + network: self.current_network, + }) + } + + pub(crate) fn convert_tx_fee(&self, fee: Option<&Fee>) -> Option { + let mut fee_amount = fee?.try_get_manual_amount()?; + if fee_amount.len() > 1 { + warn!( + "our tx fee contained more than a single denomination. using the first one for display" + ) + } + if fee_amount.is_empty() { + warn!("our tx has had an unknown fee set"); + None + } else { + self.attempt_convert_to_display_dec_coin(fee_amount.pop().unwrap()) + .ok() + } + } + + // this one is rather gnarly and I'm not 100% sure how to feel about existence of it + pub(crate) fn create_detailed_fee( + &self, + simulate_response: SimulateResponse, + ) -> Result { + // this MUST succeed as we just used it before + let client = self.current_client()?; + let gas_price = client.nymd.gas_price().clone(); + let gas_adjustment = client.nymd.gas_adjustment(); + + let res = SimulateResult::new(simulate_response.gas_info, gas_price, gas_adjustment); + + let amount = res + .to_fee_amount() + .map(|amount| self.attempt_convert_to_display_dec_coin(amount.into())) + .transpose()?; + + Ok(FeeDetails::new(amount, res.to_fee())) + } + pub fn client(&self, network: Network) -> Result<&Client, BackendError> { self.signing_clients .get(&network) @@ -112,6 +195,11 @@ impl State { self.signing_clients.insert(network, client); } + pub fn register_default_denoms(&mut self, network: Network) { + self.registered_coins + .insert(network, RegisteredCoins::default_denoms(network.into())); + } + pub fn set_network(&mut self, network: Network) { self.current_network = network; } @@ -390,7 +478,7 @@ mod tests { #[test] fn adding_validators_urls_prepends() { - let mut state = State::default(); + let mut state = WalletStateInner::default(); let _api_urls = state.get_api_urls(Network::MAINNET).collect::>(); state.add_validator_url( diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index ff8f6c2c85..304004a6ef 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -1,11 +1,9 @@ use crate::error::BackendError; use crate::nymd_client; -use crate::state::State; -use nym_types::currency::MajorCurrencyAmount; +use crate::state::WalletState; +use nym_types::currency::DecCoin; use nym_wallet_types::app::AppEnv; use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tokio::sync::RwLock; use validator_client::nymd::{tx, Coin, CosmosCoin, Gas, GasPrice}; fn get_env_as_option(key: &str) -> Option { @@ -25,9 +23,7 @@ pub fn get_env() -> AppEnv { } #[tauri::command] -pub async fn owns_mixnode( - state: tauri::State<'_, Arc>>, -) -> Result { +pub async fn owns_mixnode(state: tauri::State<'_, WalletState>) -> Result { Ok(nymd_client!(state) .owns_mixnode(nymd_client!(state).address()) .await? @@ -35,9 +31,7 @@ pub async fn owns_mixnode( } #[tauri::command] -pub async fn owns_gateway( - state: tauri::State<'_, Arc>>, -) -> Result { +pub async fn owns_gateway(state: tauri::State<'_, WalletState>) -> Result { Ok(nymd_client!(state) .owns_gateway(nymd_client!(state).address()) .await? @@ -145,13 +139,14 @@ impl Operation { #[tauri::command] pub async fn get_old_and_incorrect_hardcoded_fee( - state: tauri::State<'_, Arc>>, + state: tauri::State<'_, WalletState>, operation: Operation, -) -> Result { - let mut approximate_fee = operation.default_fee(nymd_client!(state).gas_price()); +) -> Result { + let guard = state.read().await; + let mut approximate_fee = operation.default_fee(guard.current_client()?.nymd.gas_price()); // on all our chains it should only ever contain a single type of currency assert_eq!(approximate_fee.amount.len(), 1); let coin: Coin = approximate_fee.amount.pop().unwrap().into(); log::info!("hardcoded fee for {:?} is {:?}", operation, coin); - Ok(coin.into()) + guard.attempt_convert_to_display_dec_coin(coin) } diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 74ed63fbf3..3bc8d6bebb 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; -import { styled } from '@mui/material/styles'; -import { AppBar as MuiAppBar, Grid, IconButton, Toolbar, FormGroup, FormControlLabel, Switch } from '@mui/material'; +import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { Logout } from '@mui/icons-material'; import TerminalIcon from '@mui/icons-material/Terminal'; diff --git a/nym-wallet/src/components/AppBar/AppBar.tsx b/nym-wallet/src/components/AppBar/AppBar.tsx index f0a7249c01..a52ff78fce 100644 --- a/nym-wallet/src/components/AppBar/AppBar.tsx +++ b/nym-wallet/src/components/AppBar/AppBar.tsx @@ -3,7 +3,7 @@ import { Logout } from '@mui/icons-material'; import TerminalIcon from '@mui/icons-material/Terminal'; import ModeNightOutlinedIcon from '@mui/icons-material/ModeNightOutlined'; import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'; -import { AppBar as MuiAppBar, Grid, IconButton, Toolbar, FormGroup, FormControlLabel, Switch } from '@mui/material'; +import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material'; import { Node } from 'src/svg-icons/node'; import { config } from '../../config'; import { AppContext } from '../../context/main'; diff --git a/nym-wallet/src/components/ConfirmTX.stories.tsx b/nym-wallet/src/components/ConfirmTX.stories.tsx index 9d0a86ba5a..2eec07b7e3 100644 --- a/nym-wallet/src/components/ConfirmTX.stories.tsx +++ b/nym-wallet/src/components/ConfirmTX.stories.tsx @@ -21,7 +21,7 @@ Default.args = { open: true, header: 'Confirm transaction', subheader: 'Confirm and proceed or cancel transaction', - fee: { amount: { amount: '0.001', denom: 'NYM' }, fee: { Auto: null } }, + fee: { amount: { amount: '0.001', denom: 'nym' }, fee: { Auto: null } }, onClose: () => {}, onConfirm: async () => {}, onPrev: () => {}, diff --git a/nym-wallet/src/components/ConfirmTX.tsx b/nym-wallet/src/components/ConfirmTX.tsx index de301caaea..01c5ff0743 100644 --- a/nym-wallet/src/components/ConfirmTX.tsx +++ b/nym-wallet/src/components/ConfirmTX.tsx @@ -8,7 +8,7 @@ import { ModalDivider } from './Modals/ModalDivider'; import { backDropStyles, modalStyles } from '../../.storybook/storiesStyles'; const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) => - !!isStorybook + isStorybook ? { backdropProps: { ...backDropStyles(theme), ...backdropProps }, sx: modalStyles(theme), diff --git a/nym-wallet/src/components/Delegation/DelegateBlocker.tsx b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx index 8e21d81989..fc5f5144eb 100644 --- a/nym-wallet/src/components/Delegation/DelegateBlocker.tsx +++ b/nym-wallet/src/components/Delegation/DelegateBlocker.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { SimpleModal } from '../Modals/SimpleModal'; import { SxProps } from '@mui/material'; +import { SimpleModal } from '../Modals/SimpleModal'; export const OverSaturatedBlockerModal: React.FC<{ open: boolean; diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index be05a9c2a7..f9503e25e8 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -3,7 +3,7 @@ import { Box, Typography } from '@mui/material'; import { SxProps } from '@mui/system'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { CurrencyDenom, FeeDetails, MajorCurrencyAmount } from '@nymproject/types'; +import { CurrencyDenom, FeeDetails, DecCoin } from '@nymproject/types'; import { Console } from 'src/utils/console'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode } from 'src/requests'; @@ -20,7 +20,7 @@ const MIN_AMOUNT_TO_DELEGATE = 10; export const DelegateModal: React.FC<{ open: boolean; onClose?: () => void; - onOk?: (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption, fee?: FeeDetails) => Promise; + onOk?: (identityKey: string, amount: DecCoin, tokenPool: TPoolOption, fee?: FeeDetails) => Promise; identityKey?: string; onIdentityKeyChanged?: (identityKey: string) => void; onAmountChanged?: (amount: string) => void; @@ -31,7 +31,7 @@ export const DelegateModal: React.FC<{ estimatedReward?: number; profitMarginPercentage?: number | null; nodeUptimePercentage?: number | null; - currency: CurrencyDenom; + currency: string; initialAmount?: string; hasVestingContract: boolean; sx?: SxProps; @@ -118,11 +118,11 @@ export const DelegateModal: React.FC<{ const handleOk = async () => { if (onOk && amount && identityKey) { - onOk(identityKey, { amount, denom: currency }, tokenPool, fee); + onOk(identityKey, { amount, denom: currency as CurrencyDenom }, tokenPool, fee); } }; - const handleConfirm = async ({ identity, value }: { identity: string; value: MajorCurrencyAmount }) => { + const handleConfirm = async ({ identity, value }: { identity: string; value: DecCoin }) => { const hasEnoughTokens = await checkTokenBalance(tokenPool, value.amount); if (!hasEnoughTokens) { @@ -147,7 +147,7 @@ export const DelegateModal: React.FC<{ } }; - const handleAmountChanged = (newAmount: MajorCurrencyAmount) => { + const handleAmountChanged = (newAmount: DecCoin) => { setAmount(newAmount.amount); if (onAmountChanged) { @@ -181,7 +181,7 @@ export const DelegateModal: React.FC<{ onClose={onClose} onOk={async () => { if (identityKey && amount) { - handleConfirm({ identity: identityKey, value: { amount, denom: currency } }); + handleConfirm({ identity: identityKey, value: { amount, denom: currency as CurrencyDenom } }); } }} header={header || 'Delegate'} diff --git a/nym-wallet/src/components/Delegation/DelegationList.stories.tsx b/nym-wallet/src/components/Delegation/DelegationList.stories.tsx index 0c59ff0bc6..22e7a526e2 100644 --- a/nym-wallet/src/components/Delegation/DelegationList.stories.tsx +++ b/nym-wallet/src/components/Delegation/DelegationList.stories.tsx @@ -15,31 +15,31 @@ export const items: DelegationWithEverything[] = [ { node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z', delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(), - accumulated_rewards: { amount: '0.05', denom: 'NYM' }, - amount: { amount: '10', denom: 'NYM' }, + accumulated_rewards: { amount: '0.05', denom: 'nym' }, + amount: { amount: '10', denom: 'nym' }, profit_margin_percent: 0.1122323949234, owner: '', block_height: BigInt(100), stake_saturation: 0.5, avg_uptime_percent: 0.5, - total_delegation: { amount: '0', denom: 'NYM' }, - pledge_amount: { amount: '0', denom: 'NYM' }, + total_delegation: { amount: '0', denom: 'nym' }, + pledge_amount: { amount: '0', denom: 'nym' }, pending_events: [], history: [], uses_vesting_contract_tokens: false, }, { node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8', - accumulated_rewards: { amount: '0.1', denom: 'NYM' }, - amount: { amount: '100', denom: 'NYM' }, + accumulated_rewards: { amount: '0.1', denom: 'nym' }, + amount: { amount: '100', denom: 'nym' }, delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(), profit_margin_percent: 0.89, owner: '', block_height: BigInt(4000), stake_saturation: 0.5, avg_uptime_percent: 0.1, - total_delegation: { amount: '0', denom: 'NYM' }, - pledge_amount: { amount: '0', denom: 'NYM' }, + total_delegation: { amount: '0', denom: 'nym' }, + pledge_amount: { amount: '0', denom: 'nym' }, pending_events: [], history: [], uses_vesting_contract_tokens: true, diff --git a/nym-wallet/src/components/Delegation/DelegationList.tsx b/nym-wallet/src/components/Delegation/DelegationList.tsx index 79fe7f6537..465cd04dd5 100644 --- a/nym-wallet/src/components/Delegation/DelegationList.tsx +++ b/nym-wallet/src/components/Delegation/DelegationList.tsx @@ -193,10 +193,12 @@ export const DelegationList: React.FC<{ } arrow > - {`${item.amount.amount} ${item.amount.denom}`} + {`${item.amount.amount} ${item.amount.denom}`} - + {!item.accumulated_rewards ? '-' : `${item.accumulated_rewards.amount} ${item.accumulated_rewards.denom}`} diff --git a/nym-wallet/src/components/Delegation/DelegationModal.tsx b/nym-wallet/src/components/Delegation/DelegationModal.tsx index ea5c4efb30..13846a67a2 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Box, Button, Modal, Typography, SxProps } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; +import { Console } from 'src/utils/console'; import { modalStyle } from '../Modals/styles'; import { LoadingModal } from '../Modals/LoadingModal'; @@ -67,7 +68,7 @@ export const DelegationModal: React.FC< theme.palette.error.main} mb={1}> Oh no! Something went wrong... - + {message} {children} @@ -78,15 +79,15 @@ export const DelegationModal: React.FC< ); } - transactions && - transactions.map((transaction) => console.log('action', action, 'status', status, 'key', transaction.hash)); + + transactions?.map((transaction) => Console.log('action', action, 'status', status, 'key', transaction.hash)); return ( theme.palette.success.main} mb={1}> {actionToHeader(action)} - + {message} @@ -98,15 +99,15 @@ export const DelegationModal: React.FC< {balanceVested ? ( <> theme.palette.text.secondary}> - Your current balance: {balance} + Your current balance: {balance?.toUpperCase()} theme.palette.text.secondary}> - ({balanceVested} is unlocked in your vesting account) + ({balanceVested.toUpperCase()} is unlocked in your vesting account) ) : ( theme.palette.text.secondary}> - Your current balance: {balance} + Your current balance: {balance?.toUpperCase()} )} {transactions && ( diff --git a/nym-wallet/src/components/Delegation/Modals.stories.tsx b/nym-wallet/src/components/Delegation/Modals.stories.tsx index 0207965e4c..a45a0cfd7e 100644 --- a/nym-wallet/src/components/Delegation/Modals.stories.tsx +++ b/nym-wallet/src/components/Delegation/Modals.stories.tsx @@ -61,7 +61,7 @@ export const Delegate = () => { open={open} onClose={() => setOpen(false)} onOk={async () => setOpen(false)} - currency="NYM" + currency="nym" estimatedReward={50.423} accountBalance="425.2345053" nodeUptimePercentage={99.28394} @@ -84,7 +84,7 @@ export const DelegateBelowMinimum = () => { open={open} onClose={() => setOpen(false)} onOk={async () => setOpen(false)} - currency="NYM" + currency="nym" estimatedReward={425.2345053} nodeUptimePercentage={99.28394} profitMarginPercentage={11.12334234} @@ -109,7 +109,7 @@ export const DelegateMore = () => { onOk={async () => setOpen(false)} header="Delegate more" buttonText="Delegate more" - currency="NYM" + currency="nym" estimatedReward={50.423} accountBalance="425.2345053" nodeUptimePercentage={99.28394} @@ -132,7 +132,7 @@ export const Undelegate = () => { open={open} onClose={() => setOpen(false)} onOk={async () => setOpen(false)} - currency="NYM" + currency="nym" amount={150} identityKey="AA6RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujyxx" usesVestingContractTokens={false} diff --git a/nym-wallet/src/components/Delegation/PendingEvents.tsx b/nym-wallet/src/components/Delegation/PendingEvents.tsx index 6e9263b03f..ac3afa1be5 100644 --- a/nym-wallet/src/components/Delegation/PendingEvents.tsx +++ b/nym-wallet/src/components/Delegation/PendingEvents.tsx @@ -142,7 +142,7 @@ export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl: /> - {!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom}`} + {!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom.toUpperCase()}`} {item.kind === 'Delegate' ? 'Delegation' : 'Undelegation'} {item.proxy && ( diff --git a/nym-wallet/src/components/Fee.tsx b/nym-wallet/src/components/Fee.tsx deleted file mode 100644 index 950cbf9e20..0000000000 --- a/nym-wallet/src/components/Fee.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { Typography } from '@mui/material'; -import { Operation } from '@nymproject/types'; -import { getGasFee } from '../requests'; -import { AppContext } from '../context/main'; - -export const Fee = ({ feeType }: { feeType: Operation }) => { - const [fee, setFee] = useState(); - const { clientDetails } = useContext(AppContext); - - const getFee = async () => { - const res = await getGasFee(feeType); - setFee(res.amount); - }; - - useEffect(() => { - getFee(); - }, []); - - if (fee) { - return ( - - Est.fee for this transaction: {`${fee} ${clientDetails?.denom}`}{' '} - - ); - } - - return null; -}; diff --git a/nym-wallet/src/components/Modals/ModalListItem.tsx b/nym-wallet/src/components/Modals/ModalListItem.tsx index 3eab5f1a80..f0458a05ec 100644 --- a/nym-wallet/src/components/Modals/ModalListItem.tsx +++ b/nym-wallet/src/components/Modals/ModalListItem.tsx @@ -14,7 +14,11 @@ export const ModalListItem: React.FC<{ {label}: - + {value} diff --git a/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx b/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx index d405262f3c..81d65fb978 100644 --- a/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx +++ b/nym-wallet/src/components/Rewards/RedeemModal.stories.tsx @@ -61,7 +61,7 @@ export const RedeemAllRewards = () => { onClose={() => setOpen(false)} onOk={async () => setOpen(false)} message="Redeem all rewards" - currency="NYM" + currency="nym" identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa" amount={425.65843} {...storybookStyles(theme)} @@ -82,7 +82,7 @@ export const RedeemRewardForMixnode = () => { onClose={() => setOpen(false)} onOk={async () => setOpen(false)} message="Redeem rewards" - currency="NYM" + currency="nym" identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa" amount={425.65843} {...storybookStyles(theme)} @@ -103,7 +103,7 @@ export const FeeIsMoreThanAllRewards = () => { onClose={() => setOpen(false)} onOk={() => setOpen(false)} message="Redeem all rewards" - currency="NYM" + currency="nym" identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa" amount={0.001} {...storybookStyles(theme)} @@ -125,7 +125,7 @@ export const FeeIsMoreThanMixnodeReward = () => { onOk={async () => setOpen(false)} identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa" message="Redeem rewards" - currency="NYM" + currency="nym" amount={0.001} {...storybookStyles(theme)} usesVestingTokens={false} diff --git a/nym-wallet/src/components/Rewards/RewardsSummary.tsx b/nym-wallet/src/components/Rewards/RewardsSummary.tsx index 4ef518739f..1ad2f2108a 100644 --- a/nym-wallet/src/components/Rewards/RewardsSummary.tsx +++ b/nym-wallet/src/components/Rewards/RewardsSummary.tsx @@ -13,13 +13,13 @@ export const RewardsSummary: React.FC<{ Total delegations: - + {isLoading ? : totalDelegation || '-'} New rewards: - + {isLoading ? : totalRewards || '-'} diff --git a/nym-wallet/src/components/Send/SendDetails.stories.tsx b/nym-wallet/src/components/Send/SendDetails.stories.tsx index 856a37199d..a9434aa47c 100644 --- a/nym-wallet/src/components/Send/SendDetails.stories.tsx +++ b/nym-wallet/src/components/Send/SendDetails.stories.tsx @@ -45,8 +45,8 @@ export const SendDetails = () => { {}} onSend={() => {}} onClose={() => {}} diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index 0fd00bec09..38da2fd143 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Stack } from '@mui/material'; import { SxProps } from '@mui/system'; -import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types'; +import { FeeDetails, DecCoin } from '@nymproject/types'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from '../Modals/ModalListItem'; @@ -19,10 +19,10 @@ export const SendDetailsModal = ({ fromAddress?: string; toAddress: string; fee?: FeeDetails; - amount?: MajorCurrencyAmount; + amount?: DecCoin; onClose: () => void; onPrev: () => void; - onSend: (data: { val: MajorCurrencyAmount; to: string }) => void; + onSend: (data: { val: DecCoin; to: string }) => void; sx?: SxProps; backdropProps?: object; }) => ( diff --git a/nym-wallet/src/components/Send/SendInputModal.tsx b/nym-wallet/src/components/Send/SendInputModal.tsx index 7d967a5702..55c72519fe 100644 --- a/nym-wallet/src/components/Send/SendInputModal.tsx +++ b/nym-wallet/src/components/Send/SendInputModal.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Stack, TextField, Typography } from '@mui/material'; import { SxProps } from '@mui/system'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { MajorCurrencyAmount } from '@nymproject/types'; +import { DecCoin } from '@nymproject/types'; import { validateAmount } from 'src/utils'; import { SimpleModal } from '../Modals/SimpleModal'; import { ModalListItem } from '../Modals/ModalListItem'; @@ -22,19 +22,19 @@ export const SendInputModal = ({ }: { fromAddress?: string; toAddress: string; - amount?: MajorCurrencyAmount; + amount?: DecCoin; balance?: string; error?: string; onNext: () => void; onClose: () => void; - onAmountChange: (value: MajorCurrencyAmount) => void; + onAmountChange: (value: DecCoin) => void; onAddressChange: (value: string) => void; sx?: SxProps; backdropProps?: object; }) => { const [isValid, setIsValid] = useState(false); - const validate = async (value: MajorCurrencyAmount) => { + const validate = async (value: DecCoin) => { const isValidAmount = await validateAmount(value.amount, '0'); setIsValid(isValidAmount); }; diff --git a/nym-wallet/src/components/Send/SendModal.tsx b/nym-wallet/src/components/Send/SendModal.tsx index 0e21afa6d5..ec7f83dc45 100644 --- a/nym-wallet/src/components/Send/SendModal.tsx +++ b/nym-wallet/src/components/Send/SendModal.tsx @@ -1,5 +1,5 @@ import React, { useContext, useState } from 'react'; -import { MajorCurrencyAmount } from '@nymproject/types'; +import { DecCoin } from '@nymproject/types'; import { AppContext, urls } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; import { send } from 'src/requests'; @@ -14,14 +14,14 @@ import { TTransactionDetails } from './types'; export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void; hasStorybookStyles?: {} }) => { const [toAddress, setToAddress] = useState(''); - const [amount, setAmount] = useState(); + const [amount, setAmount] = useState(); const [modal, setModal] = useState<'send' | 'send details'>('send'); const [error, setError] = useState(); const [sendError, setSendError] = useState(false); const [isLoading, setIsLoading] = useState(false); const [txDetails, setTxDetails] = useState(); - const { clientDetails, userBalance, network } = useContext(AppContext); + const { clientDetails, userBalance, network, denom } = useContext(AppContext); const { fee, getFee } = useGetFee(); const handleOnNext = async () => { @@ -41,13 +41,13 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void } }; - const handleSend = async ({ val, to }: { val: MajorCurrencyAmount; to: string }) => { + const handleSend = async ({ val, to }: { val: DecCoin; to: string }) => { setIsLoading(true); setError(undefined); try { const txResponse = await send({ amount: val, address: to, memo: '', fee: fee?.fee }); setTxDetails({ - amount: `${amount?.amount} ${clientDetails?.denom}`, + amount: `${amount?.amount} ${denom}`, txUrl: `${urls(network).blockExplorer}/transaction/${txResponse.tx_hash}`, }); } catch (e) { diff --git a/nym-wallet/src/components/SuccessResponse.tsx b/nym-wallet/src/components/SuccessResponse.tsx index c94ad1d4e7..2f3e2d4075 100644 --- a/nym-wallet/src/components/SuccessResponse.tsx +++ b/nym-wallet/src/components/SuccessResponse.tsx @@ -7,7 +7,13 @@ export const SuccessReponse: React.FC<{ caption: string | React.ReactNode; }> = ({ title, subtitle, caption }) => ( - + {title} {subtitle} diff --git a/nym-wallet/src/components/TokenPoolSelector.tsx b/nym-wallet/src/components/TokenPoolSelector.tsx index 2879f95d87..4b19ec2fb9 100644 --- a/nym-wallet/src/components/TokenPoolSelector.tsx +++ b/nym-wallet/src/components/TokenPoolSelector.tsx @@ -11,7 +11,7 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T const [value, setValue] = useState('balance'); const { userBalance: { tokenAllocation, balance, fetchBalance, fetchTokenAllocation }, - clientDetails, + denom, } = useContext(AppContext); useEffect(() => { @@ -48,7 +48,8 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T {tokenAllocation && ( )} diff --git a/nym-wallet/src/components/index.ts b/nym-wallet/src/components/index.ts index aa28677e08..690f9c9c86 100644 --- a/nym-wallet/src/components/index.ts +++ b/nym-wallet/src/components/index.ts @@ -3,7 +3,6 @@ export * from './ClientAddress'; export * from './ConfirmPassword'; export * from './CopyToClipboard'; export * from './ErrorFallback'; -export * from './Fee'; export * from './InfoToolTip'; export * from './LoadingPage'; export * from './Mnemonic'; diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 48435e6c67..cb1110db16 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -4,7 +4,7 @@ import { DelegationEvent, DelegationWithEverything, FeeDetails, - MajorCurrencyAmount, + DecCoin, TransactionExecuteResult, } from '@nymproject/types'; import type { Network } from 'src/types'; @@ -20,7 +20,7 @@ export type TDelegationContext = { totalRewards?: string; refresh: () => Promise; addDelegation: ( - data: { identity: string; amount: MajorCurrencyAmount }, + data: { identity: string; amount: DecCoin }, tokenPool: TPoolOption, fee?: FeeDetails, ) => Promise; @@ -57,7 +57,7 @@ export const DelegationContextProvider: FC<{ const [pendingDelegations, setPendingDelegations] = useState(); const addDelegation = async ( - data: { identity: string; amount: MajorCurrencyAmount }, + data: { identity: string; amount: DecCoin }, tokenPool: TPoolOption, fee?: FeeDetails, ) => { diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 25c75987f1..b03318ba0c 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -2,7 +2,7 @@ import React, { createContext, useEffect, useMemo, useState } from 'react'; import { forage } from '@tauri-apps/tauri-forage'; import { useNavigate } from 'react-router-dom'; import { useSnackbar } from 'notistack'; -import { Account, AccountEntry, MixNodeBond } from '@nymproject/types'; +import { Account, AccountEntry, CurrencyDenom, MixNodeBond } from '@nymproject/types'; import { getVersion } from '@tauri-apps/api/app'; import { AppEnv, Network } from '../types'; import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'; @@ -49,6 +49,7 @@ export type TAppContext = { loginType?: TLoginType; showSettings: boolean; showSendModal: boolean; + denom: Uppercase; handleShowSettings: () => void; handleShowSendModal: () => void; setIsLoading: (isLoading: boolean) => void; @@ -67,6 +68,7 @@ export const AppContext = createContext({} as TAppContext); export const AppProvider = ({ children }: { children: React.ReactNode }) => { const [clientDetails, setClientDetails] = useState(); + const [denom, setDenom] = useState>('NYM'); const [storedAccounts, setStoredAccounts] = useState(); const [mixnodeDetails, setMixnodeDetails] = useState(null); const [network, setNetwork] = useState(); @@ -99,6 +101,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { try { const client = await selectNetwork(n); setClientDetails(client); + setDenom(client.display_mix_denom.toUpperCase() as Uppercase); } catch (e) { enqueueSnackbar('Error loading account', { variant: 'error' }); Console.error(e as string); @@ -136,10 +139,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } }; - const setModeInStorage = async (mode: 'light' | 'dark') => { + const setModeInStorage = async (newMode: 'light' | 'dark') => { await forage.setItem({ key: 'nym-wallet-mode', - value: mode, + value: newMode, })(); }; @@ -253,6 +256,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { storedAccounts, mixnodeDetails, userBalance, + denom, showAdmin, showTerminal, showSettings, @@ -290,6 +294,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { showTerminal, showSettings, showSendModal, + denom, ], ); diff --git a/nym-wallet/src/context/mocks/delegations.tsx b/nym-wallet/src/context/mocks/delegations.tsx index c0af45d9a5..b6aea6efc8 100644 --- a/nym-wallet/src/context/mocks/delegations.tsx +++ b/nym-wallet/src/context/mocks/delegations.tsx @@ -1,5 +1,5 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { DelegationWithEverything, MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types'; +import { DelegationWithEverything, DecCoin, TransactionExecuteResult } from '@nymproject/types'; import { DelegationContext, TDelegationTransaction } from '../delegations'; import { mockSleep } from './utils'; @@ -10,31 +10,31 @@ let mockDelegations: DelegationWithEverything[] = [ { node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z', delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(), - accumulated_rewards: { amount: '0.05', denom: 'NYM' }, - amount: { amount: '10', denom: 'NYM' }, + accumulated_rewards: { amount: '0.05', denom: 'nym' }, + amount: { amount: '10', denom: 'nym' }, profit_margin_percent: 0.1122323949234, owner: '', block_height: BigInt(100), stake_saturation: 0.5, avg_uptime_percent: 0.5, - total_delegation: { amount: '0', denom: 'NYM' }, - pledge_amount: { amount: '0', denom: 'NYM' }, + total_delegation: { amount: '0', denom: 'nym' }, + pledge_amount: { amount: '0', denom: 'nym' }, pending_events: [], history: [], uses_vesting_contract_tokens: false, }, { node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8', - accumulated_rewards: { amount: '0.1', denom: 'NYM' }, - amount: { amount: '100', denom: 'NYM' }, + accumulated_rewards: { amount: '0.1', denom: 'nym' }, + amount: { amount: '100', denom: 'nym' }, delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(), profit_margin_percent: 0.89, owner: '', block_height: BigInt(4000), stake_saturation: 0.5, avg_uptime_percent: 0.1, - total_delegation: { amount: '0', denom: 'NYM' }, - pledge_amount: { amount: '0', denom: 'NYM' }, + total_delegation: { amount: '0', denom: 'nym' }, + pledge_amount: { amount: '0', denom: 'nym' }, pending_events: [], history: [], uses_vesting_contract_tokens: true, @@ -64,7 +64,7 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => { identity, }: { identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; }): Promise => { await mockSleep(SLEEP_MS); // mockDelegations.push({ ...newDelegation }); @@ -86,12 +86,11 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => { logs_json: '', data_json: '', gas_info: { - gas_wanted: BigInt(1), - gas_used: BigInt(1), - fee: { amount: '1', denom: 'NYM' }, + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', - fee: { amount: '1', denom: 'NYM' }, + fee: { amount: '1', denom: 'nym' }, }; }; @@ -164,11 +163,10 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => { data_json: '', transaction_hash: '', gas_info: { - gas_wanted: BigInt(1), - gas_used: BigInt(1), - fee: { amount: '1', denom: 'NYM' }, + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, - fee: { amount: '1', denom: 'NYM' }, + fee: { amount: '1', denom: 'nym' }, }, ]; }; diff --git a/nym-wallet/src/context/mocks/main.tsx b/nym-wallet/src/context/mocks/main.tsx index cd06f7c71c..62b53da136 100644 --- a/nym-wallet/src/context/mocks/main.tsx +++ b/nym-wallet/src/context/mocks/main.tsx @@ -16,15 +16,15 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => { isAdminAddress: false, isLoading: false, clientDetails: { - denom: 'NYMT', + display_mix_denom: 'nymt', + base_mix_denom: 'unymt', client_address: '', - contract_address: '', }, userBalance: { balance: { amount: { amount: '100', - denom: 'NYMT', + denom: 'nymt', }, printable_balance: '100 NYMT', }, @@ -35,6 +35,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => { fetchTokenAllocation: async () => undefined, refreshBalances: async () => {}, }, + denom: 'NYM', showAdmin: false, showTerminal: false, showSettings: false, diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index 33df5f8dba..a71ee958ea 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -61,34 +61,26 @@ export const MockRewardsContextProvider: FC = ({ children }) => { transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', fee: { amount: '1', - denom: 'NYM', + denom: 'nym', }, data_json: '[]', logs_json: '[]', gas_info: { - gas_wanted: BigInt(1), - fee: { - amount: '1', - denom: 'NYM', - }, - gas_used: BigInt(1), + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, }, { transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', fee: { amount: '1', - denom: 'NYM', + denom: 'nym', }, data_json: '[]', logs_json: '[]', gas_info: { - gas_wanted: BigInt(1), - fee: { - amount: '1', - denom: 'NYM', - }, - gas_used: BigInt(1), + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, }, ]; @@ -112,34 +104,26 @@ export const MockRewardsContextProvider: FC = ({ children }) => { transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', fee: { amount: '1', - denom: 'NYM', + denom: 'nym', }, data_json: '[]', logs_json: '[]', gas_info: { - gas_wanted: BigInt(1), - fee: { - amount: '1', - denom: 'NYM', - }, - gas_used: BigInt(1), + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, }, { transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', fee: { amount: '1', - denom: 'NYM', + denom: 'nym', }, data_json: '[]', logs_json: '[]', gas_info: { - gas_wanted: BigInt(1), - fee: { - amount: '1', - denom: 'NYM', - }, - gas_used: BigInt(1), + gas_wanted: { gas_units: BigInt(1) }, + gas_used: { gas_units: BigInt(1) }, }, }, ]; diff --git a/nym-wallet/src/hooks/useGetBalance.ts b/nym-wallet/src/hooks/useGetBalance.ts index 56118351e5..0da3ca7476 100644 --- a/nym-wallet/src/hooks/useGetBalance.ts +++ b/nym-wallet/src/hooks/useGetBalance.ts @@ -1,13 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { invoke } from '@tauri-apps/api'; -import { - Account, - Balance, - MajorCurrencyAmount, - OriginalVestingResponse, - Period, - VestingAccountInfo, -} from '@nymproject/types'; +import { Account, Balance, DecCoin, OriginalVestingResponse, Period, VestingAccountInfo } from '@nymproject/types'; import { getVestingCoins, getVestedCoins, @@ -20,7 +13,7 @@ import { import { Console } from '../utils/console'; type TTokenAllocation = { - [key in 'vesting' | 'vested' | 'locked' | 'spendable']: MajorCurrencyAmount['amount']; + [key in 'vesting' | 'vested' | 'locked' | 'spendable']: DecCoin['amount']; }; export type TUseuserBalance = { diff --git a/nym-wallet/src/pages/balance/components/TransferModal.tsx b/nym-wallet/src/pages/balance/components/TransferModal.tsx index 8312b31ad0..f2236b3c39 100644 --- a/nym-wallet/src/pages/balance/components/TransferModal.tsx +++ b/nym-wallet/src/pages/balance/components/TransferModal.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { Alert, Box, CircularProgress } from '@mui/material'; -import { FeeDetails } from '@nymproject/types'; +import { CurrencyDenom, FeeDetails } from '@nymproject/types'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { AppContext, urls } from 'src/context'; @@ -16,17 +16,20 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => { const [fee, setFee] = useState(); const [tx, setTx] = useState(); - const { userBalance, clientDetails, network } = useContext(AppContext); + const { userBalance, denom, network } = useContext(AppContext); const getFee = async () => { - if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) { + if (userBalance.tokenAllocation?.spendable && denom) { try { const simulatedFee = await simulateWithdrawVestedCoins({ - amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.denom }, + amount: { amount: userBalance.tokenAllocation?.spendable, denom }, }); setFee(simulatedFee); } catch (e) { - setFee({ amount: { amount: 'n/a', denom: clientDetails.denom }, fee: { Auto: null } }); + setFee({ + amount: { amount: 'n/a', denom: denom as CurrencyDenom }, + fee: { Auto: null }, + }); Console.error(e); } } @@ -37,16 +40,16 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => { }, []); const handleTransfer = async () => { - if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) { + if (userBalance.tokenAllocation?.spendable && denom) { setState('loading'); try { const txResponse = await withdrawVestedCoins({ amount: userBalance.tokenAllocation?.spendable, - denom: clientDetails.denom, + denom: denom as CurrencyDenom, }); setState('success'); setTx({ - amount: `${userBalance.tokenAllocation?.spendable} ${clientDetails?.denom}`, + amount: `${userBalance.tokenAllocation?.spendable} ${denom}`, url: `${urls(network).blockExplorer}/transaction/${txResponse.transaction_hash}`, }); await userBalance.refreshBalances(); @@ -81,7 +84,7 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => { <> {tx && ( <> - {tx.amount} + + {tx.amount} + )} diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx index 552290096a..0175e9d02c 100644 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ b/nym-wallet/src/pages/balance/vesting.tsx @@ -35,7 +35,7 @@ const vestingPeriod = (current?: Period, original?: number) => { }; const VestingSchedule = () => { - const { userBalance, clientDetails } = useContext(AppContext); + const { userBalance, denom } = useContext(AppContext); const [vestedPercentage, setVestedPercentage] = useState(0); const calculatePercentage = () => { @@ -65,9 +65,8 @@ const VestingSchedule = () => { ))} - - {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.denom} + + {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom} {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} @@ -78,9 +77,8 @@ const VestingSchedule = () => { - - {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} - {clientDetails?.denom} + + {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom} @@ -90,7 +88,7 @@ const VestingSchedule = () => { }; const TokenTransfer = () => { - const { userBalance, clientDetails } = useContext(AppContext); + const { userBalance, denom } = useContext(AppContext); const icon = useCallback( () => ( @@ -109,8 +107,14 @@ const TokenTransfer = () => { Transferable tokens - - {userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.denom} + + {userBalance.tokenAllocation?.spendable || 'n/a'} {denom} diff --git a/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx b/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx index 1d905a2e36..32c6634c5c 100644 --- a/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx +++ b/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Box } from '@mui/material'; -import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types'; +import { FeeDetails, DecCoin } from '@nymproject/types'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalFee } from 'src/components/Modals/ModalFee'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; @@ -13,7 +13,7 @@ export const ConfirmationModal = ({ onConfirm, }: { identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; fee: FeeDetails; onPrev: () => void; onConfirm: () => Promise; diff --git a/nym-wallet/src/pages/bond/components/GatewayForm.tsx b/nym-wallet/src/pages/bond/components/GatewayForm.tsx index 87a93a1c1c..e8a1520e69 100644 --- a/nym-wallet/src/pages/bond/components/GatewayForm.tsx +++ b/nym-wallet/src/pages/bond/components/GatewayForm.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types'; +import { CurrencyDenom, DecCoin } from '@nymproject/types'; import { useForm } from 'react-hook-form'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { useGetFee } from 'src/hooks/useGetFee'; @@ -19,7 +19,7 @@ type TBondFormFields = { ownerSignature: string; identityKey: string; sphinxKey: string; - amount: MajorCurrencyAmount; + amount: DecCoin; host: string; version: string; location: string; @@ -33,7 +33,7 @@ const defaultValues = { identityKey: '', sphinxKey: '', ownerSignature: '', - amount: { amount: '', denom: 'NYM' as CurrencyDenom }, + amount: { amount: '', denom: 'nym' as CurrencyDenom }, host: '', version: '', location: '', @@ -63,7 +63,7 @@ export const GatewayForm = ({ resolver: yupResolver(gatewayValidationSchema), defaultValues, }); - const { userBalance, clientDetails } = useContext(AppContext); + const { userBalance, clientDetails, denom } = useContext(AppContext); const { fee, getFee, resetFeeState, feeError } = useGetFee(); @@ -209,7 +209,7 @@ export const GatewayForm = ({ fullWidth label="Amount" onChanged={(val) => setValue('amount', val, { shouldValidate: true })} - denom={clientDetails?.denom} + denom={denom} validationError={errors.amount?.amount?.message} /> diff --git a/nym-wallet/src/pages/bond/components/MixnodeForm.tsx b/nym-wallet/src/pages/bond/components/MixnodeForm.tsx index 82b64405f7..91d38047cc 100644 --- a/nym-wallet/src/pages/bond/components/MixnodeForm.tsx +++ b/nym-wallet/src/pages/bond/components/MixnodeForm.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react'; import { yupResolver } from '@hookform/resolvers/yup'; import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types'; +import { CurrencyDenom, DecCoin } from '@nymproject/types'; import { useForm } from 'react-hook-form'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { useGetFee } from 'src/hooks/useGetFee'; @@ -20,7 +20,7 @@ type TBondFormFields = { identityKey: string; sphinxKey: string; profitMarginPercent: number; - amount: MajorCurrencyAmount; + amount: DecCoin; host: string; version: string; mixPort: number; @@ -34,7 +34,7 @@ const defaultValues = { identityKey: '', sphinxKey: '', ownerSignature: '', - amount: { amount: '', denom: 'NYM' as CurrencyDenom }, + amount: { amount: '', denom: 'nym' as CurrencyDenom }, host: '', version: '', profitMarginPercent: 10, @@ -66,7 +66,7 @@ export const MixnodeForm = ({ defaultValues, }); - const { userBalance, clientDetails } = useContext(AppContext); + const { userBalance, clientDetails, denom } = useContext(AppContext); const { fee, getFee, resetFeeState, feeError } = useGetFee(); @@ -216,7 +216,7 @@ export const MixnodeForm = ({ fullWidth label="Amount" onChanged={(val) => setValue('amount', val, { shouldValidate: true })} - denom={clientDetails?.denom} + denom={denom} validationError={errors.amount?.amount?.message} /> diff --git a/nym-wallet/src/pages/bond/components/SuccessView.tsx b/nym-wallet/src/pages/bond/components/SuccessView.tsx index 26c8c766d8..eadeeb4e25 100644 --- a/nym-wallet/src/pages/bond/components/SuccessView.tsx +++ b/nym-wallet/src/pages/bond/components/SuccessView.tsx @@ -5,7 +5,7 @@ import { AppContext } from '../../../context/main'; import { useCheckOwnership } from '../../../hooks/useCheckOwnership'; export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => { - const { userBalance, clientDetails } = useContext(AppContext); + const { userBalance, denom } = useContext(AppContext); const { ownership } = useCheckOwnership(); return ( @@ -15,8 +15,8 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string subtitle="Successfully bonded to node with following details" caption={ ownership.vestingPledge - ? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${clientDetails?.denom}` - : `Your current balance is: ${userBalance.balance?.printable_balance}` + ? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${denom}` + : `Your current balance is: ${userBalance.balance?.printable_balance.toUpperCase()}` } /> {details && ( @@ -24,7 +24,10 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string diff --git a/nym-wallet/src/pages/delegate/DelegateForm.tsx b/nym-wallet/src/pages/delegate/DelegateForm.tsx deleted file mode 100644 index ee7a22a770..0000000000 --- a/nym-wallet/src/pages/delegate/DelegateForm.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useContext, useEffect } from 'react'; -import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { useForm } from 'react-hook-form'; -import { MajorCurrencyAmount, TNodeType, TransactionExecuteResult } from '@nymproject/types'; -import { TDelegateArgs } from '../../types'; -import { validationSchema } from './validationSchema'; -import { AppContext } from '../../context/main'; -import { delegateToMixnode, vestingDelegateToMixnode } from '../../requests'; -import { Fee, TokenPoolSelector } from '../../components'; -import { Console } from '../../utils/console'; - -type TDelegateForm = { - identity: string; - amount: MajorCurrencyAmount; - tokenPool: string; - type: TNodeType; -}; - -const defaultValues: TDelegateForm = { - identity: '', - amount: { amount: '', denom: 'NYM' }, - tokenPool: 'balance', - type: 'mixnode', -}; - -export const DelegateForm = ({ - onError, - onSuccess, -}: { - onError: (message?: string) => void; - onSuccess: (details: { amount: string; result: TransactionExecuteResult }) => void; -}) => { - const { - register, - handleSubmit, - setValue, - reset, - formState: { errors, isSubmitting }, - } = useForm({ - defaultValues, - resolver: yupResolver(validationSchema), - }); - - const { userBalance, clientDetails } = useContext(AppContext); - - useEffect(() => { - reset(); - }, [clientDetails]); - - const onSubmit = async (data: TDelegateForm, cb: (data: TDelegateArgs) => Promise) => { - await cb({ - identity: data.identity, - amount: { ...data.amount, denom: clientDetails!.denom }, - }) - .then(async (result) => { - if (data.tokenPool === 'balance') { - await userBalance.fetchBalance(); - } else { - await userBalance.fetchTokenAllocation(); - } - onSuccess({ amount: data.amount.amount, result }); - }) - .catch((e) => { - Console.error(e as string); - onError(e); - }); - }; - - return ( - - - - - - - - {userBalance.originalVesting && ( - - setValue('tokenPool', pool)} disabled={false} /> - - )} - - - {clientDetails?.denom}, - }} - /> - - - - - - - - - - - ); -}; diff --git a/nym-wallet/src/pages/delegate/SuccessView.tsx b/nym-wallet/src/pages/delegate/SuccessView.tsx deleted file mode 100644 index 3942cfc252..0000000000 --- a/nym-wallet/src/pages/delegate/SuccessView.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useContext } from 'react'; -import { Box, Stack, Typography } from '@mui/material'; -import { TransactionExecuteResult } from '@nymproject/types'; -import { SuccessReponse, TransactionDetails } from '../../components'; -import { AppContext } from '../../context/main'; - -export const SuccessView: React.FC<{ details?: { amount: string; result: TransactionExecuteResult } }> = ({ - details, -}) => { - const { userBalance, clientDetails } = useContext(AppContext); - return ( - <> - - Successfully requested delegation to node - - Note it may take up to one hour to take effect - - - } - caption={`Your current balance is: ${userBalance.balance?.printable_balance}`} - /> - {details && ( - - - - )} - - ); -}; diff --git a/nym-wallet/src/pages/delegate/index.tsx b/nym-wallet/src/pages/delegate/index.tsx deleted file mode 100644 index 6fbe7dbf63..0000000000 --- a/nym-wallet/src/pages/delegate/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useContext, useState } from 'react'; -import { Alert, AlertTitle, Box, Button, Typography } from '@mui/material'; -import { TransactionExecuteResult } from '@nymproject/types'; -import { Link } from '@nymproject/react/link/Link'; -import { DelegateForm } from './DelegateForm'; -import { NymCard } from '../../components'; -import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus'; -import { SuccessView } from './SuccessView'; -import { AppContext, urls } from '../../context/main'; -import { PageLayout } from '../../layouts'; - -export const Delegate = () => { - const [status, setStatus] = useState(EnumRequestStatus.initial); - const [error, setError] = useState(); - const [successDetails, setSuccessDetails] = useState<{ amount: string; result: TransactionExecuteResult }>(); - - const { network } = useContext(AppContext); - - return ( - - - <> - {status === EnumRequestStatus.initial && ( - - Always ensure you leave yourself enough funds to UNDELEGATE - - )} - {status === EnumRequestStatus.initial && ( - { - setStatus(EnumRequestStatus.error); - setError(message); - }} - onSuccess={(details) => { - setStatus(EnumRequestStatus.success); - setSuccessDetails(details); - }} - /> - )} - {status !== EnumRequestStatus.initial && ( - <> - - Delegation failed - An error occurred with the request: - {error} - - } - Success={} - /> - - - - - )} - - - - Checkout the{' '} - {' '} - for uptime and performances to help make delegation decisions - - - ); -}; diff --git a/nym-wallet/src/pages/delegate/validationSchema.ts b/nym-wallet/src/pages/delegate/validationSchema.ts deleted file mode 100644 index 6f97bda05e..0000000000 --- a/nym-wallet/src/pages/delegate/validationSchema.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as Yup from 'yup'; -import { checkHasEnoughFunds, checkHasEnoughLockedTokens, validateAmount, validateKey } from '../../utils'; - -export const validationSchema = Yup.object().shape({ - identity: Yup.string() - .required() - .test( - 'valid-id-key', - 'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z', - (value) => (value ? validateKey(value, 32) : false), - ), - - amount: Yup.object().shape({ - amount: Yup.string() - .required() - .test('valid-amount', 'A valid amount is required', async function isValidAmount(value) { - const isValid = await validateAmount(value || '', '0'); - - if (!isValid) { - return this.createError({ message: 'A valid amount is required' }); - } - - const hasEnoughBalance = await checkHasEnoughFunds(value || ''); - const hasEnoughLocked = await checkHasEnoughLockedTokens(value || ''); - - if (this.parent.tokenPool === 'balance' && !hasEnoughBalance) { - return this.createError({ message: 'Not enough funds in wallet' }); - } - - if (this.parent.tokenPool === 'locked' && !hasEnoughLocked) { - return this.createError({ message: 'Not enough locked tokens' }); - } - - return true; - }), - denom: Yup.string().required(), - }), -}); diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index 8110c742c5..e3bf0d75a9 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -1,7 +1,7 @@ import React, { FC, useContext, useEffect, useState } from 'react'; import { Box, Button, Paper, Stack, Typography } from '@mui/material'; import { useTheme, Theme } from '@mui/material/styles'; -import { DelegationWithEverything, FeeDetails, MajorCurrencyAmount } from '@nymproject/types'; +import { DelegationWithEverything, FeeDetails, DecCoin } from '@nymproject/types'; import { Link } from '@nymproject/react/link/Link'; import { AppContext, urls } from 'src/context/main'; import { DelegationList } from 'src/components/Delegation/DelegationList'; @@ -22,7 +22,7 @@ import { DelegationModal, DelegationModalProps } from '../../components/Delegati import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles'; const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) => - !!isStorybook + isStorybook ? { backdropProps: { ...backDropStyles(theme), ...backdropProps }, sx: modalStyles(theme), @@ -44,6 +44,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const { clientDetails, network, + denom, userBalance: { balance, originalVesting, fetchBalance }, } = useContext(AppContext); @@ -64,7 +65,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const getAllBalances = async () => { const resBalance = (await userBalance()).printable_balance; - let resVesting: MajorCurrencyAmount | undefined; + let resVesting: DecCoin | undefined; try { resVesting = await getSpendableCoins(); } catch (e) { @@ -113,7 +114,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const handleNewDelegation = async ( identityKey: string, - amount: MajorCurrencyAmount, + amount: DecCoin, tokenPool: TPoolOption, fee?: FeeDetails, ) => { @@ -154,12 +155,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { } }; - const handleDelegateMore = async ( - identityKey: string, - amount: MajorCurrencyAmount, - tokenPool: TPoolOption, - fee?: FeeDetails, - ) => { + const handleDelegateMore = async (identityKey: string, amount: DecCoin, tokenPool: TPoolOption, fee?: FeeDetails) => { if (currentDelegationListActionItem?.node_identity !== identityKey || !clientDetails) { setConfirmationModalProps({ status: 'error', @@ -347,7 +343,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onOk={handleNewDelegation} header="Delegate" buttonText="Delegate stake" - currency={clientDetails!.denom} + currency={denom} accountBalance={balance?.printable_balance} rewardInterval="weekly" hasVestingContract={Boolean(originalVesting)} @@ -363,7 +359,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { header="Delegate more" buttonText="Delegate more" identityKey={currentDelegationListActionItem.node_identity} - currency={clientDetails!.denom} + currency={denom} accountBalance={balance?.printable_balance} nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent} profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent} @@ -390,7 +386,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onClose={() => setShowRedeemRewardsModal(false)} onOk={(identity, fee) => handleRedeem(identity, fee)} message="Redeem rewards" - currency={clientDetails!.denom} + currency={denom} identityKey={currentDelegationListActionItem?.node_identity} amount={+currentDelegationListActionItem.accumulated_rewards.amount} usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens} @@ -403,7 +399,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onClose={() => setShowCompoundRewardsModal(false)} onOk={(identity, fee) => handleCompound(identity, fee)} message="Compound rewards" - currency={clientDetails!.denom} + currency={denom} identityKey={currentDelegationListActionItem?.node_identity} amount={+currentDelegationListActionItem.accumulated_rewards.amount} usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens} diff --git a/nym-wallet/src/pages/index.ts b/nym-wallet/src/pages/index.ts index 708839f4da..b02d0413c9 100644 --- a/nym-wallet/src/pages/index.ts +++ b/nym-wallet/src/pages/index.ts @@ -1,12 +1,9 @@ export * from './Admin'; export * from './balance'; export * from './bond'; -export * from './delegate'; export * from './internal-docs'; export * from './receive'; -export * from './send'; export * from './auth'; export * from './settings'; export * from './unbond'; -export * from './undelegate'; export * from './delegation'; diff --git a/nym-wallet/src/pages/receive/index.tsx b/nym-wallet/src/pages/receive/index.tsx index 775329b27b..6d69f41cfe 100644 --- a/nym-wallet/src/pages/receive/index.tsx +++ b/nym-wallet/src/pages/receive/index.tsx @@ -6,11 +6,11 @@ import { AppContext } from '../../context/main'; import { PageLayout } from '../../layouts'; export const Receive = () => { - const { clientDetails } = useContext(AppContext); + const { clientDetails, denom } = useContext(AppContext); return ( - + You can receive tokens by providing this address to the sender diff --git a/nym-wallet/src/pages/send/SendConfirmation.tsx b/nym-wallet/src/pages/send/SendConfirmation.tsx deleted file mode 100644 index 6e4c21c35c..0000000000 --- a/nym-wallet/src/pages/send/SendConfirmation.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useContext } from 'react'; -import { Box, CircularProgress, Typography } from '@mui/material'; -import { TransactionDetails as TTransactionDetails } from '@nymproject/types'; -import { Link } from '@nymproject/react/link/Link'; -import { SendError } from './SendError'; -import { AppContext, urls } from '../../context/main'; -import { SuccessReponse } from '../../components'; -import { TransactionDetails } from '../../components/TransactionDetails'; - -export const SendConfirmation = ({ - data, - error, - isLoading, -}: { - data?: TTransactionDetails & { tx_hash: string }; - error?: string; - isLoading: boolean; -}) => { - const { userBalance, clientDetails, network } = useContext(AppContext); - - if (!data && !error && !isLoading) return null; - - return ( - - {isLoading && } - {!isLoading && !!error && } - {!isLoading && data && ( - <> - - Check the transaction hash{' '} - - - } - caption={ - userBalance.balance && ( - Your current balance is: {userBalance.balance.printable_balance} - ) - } - /> - - - )} - - ); -}; diff --git a/nym-wallet/src/pages/send/SendError.tsx b/nym-wallet/src/pages/send/SendError.tsx deleted file mode 100644 index 0eac30b87d..0000000000 --- a/nym-wallet/src/pages/send/SendError.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Alert, Box, Card, Typography } from '@mui/material'; -import { ErrorOutline } from '@mui/icons-material'; - -export const SendError = ({ message }: { message?: string }) => ( - - <> - - - Transaction failed - - - - - An error occured during the request {message} - - - - -); diff --git a/nym-wallet/src/pages/send/SendForm.tsx b/nym-wallet/src/pages/send/SendForm.tsx deleted file mode 100644 index 37d68663e0..0000000000 --- a/nym-wallet/src/pages/send/SendForm.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useContext } from 'react'; -import { useFormContext } from 'react-hook-form'; -import { Grid, TextField } from '@mui/material'; -import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { AppContext } from '../../context/main'; -import { Fee } from '../../components'; - -export const SendForm = () => { - const { - register, - setValue, - formState: { errors }, - } = useFormContext(); - const { clientDetails } = useContext(AppContext); - - return ( - - - - - - setValue('amount', val, { shouldValidate: true })} - validationError={errors.amount?.amount?.message} - denom={clientDetails?.denom} - /> - - - - - - ); -}; diff --git a/nym-wallet/src/pages/send/SendReview.tsx b/nym-wallet/src/pages/send/SendReview.tsx deleted file mode 100644 index 4dfb03ca5b..0000000000 --- a/nym-wallet/src/pages/send/SendReview.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useContext } from 'react'; -import { Card, Divider, Grid, Typography } from '@mui/material'; -import { useFormContext } from 'react-hook-form'; -import { AppContext } from '../../context/main'; - -const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => ( - <> - {title} - - {subtitle} - - -); - -export const SendReview = ({ transferFee }: { transferFee?: string }) => { - const { getValues } = useFormContext(); - const { clientDetails } = useContext(AppContext); - - const values = getValues(); - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/nym-wallet/src/pages/send/SendWizard.tsx b/nym-wallet/src/pages/send/SendWizard.tsx deleted file mode 100644 index 689a4b7747..0000000000 --- a/nym-wallet/src/pages/send/SendWizard.tsx +++ /dev/null @@ -1,177 +0,0 @@ -/* eslint-disable no-nested-ternary */ -import React, { useContext, useEffect, useState } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { Box, Button, Step, StepLabel, Stepper } from '@mui/material'; -import { CurrencyDenom, MajorCurrencyAmount, SendTxResult, TransactionDetails } from '@nymproject/types'; -import { SendForm } from './SendForm'; -import { SendReview } from './SendReview'; -import { SendConfirmation } from './SendConfirmation'; -import { AppContext } from '../../context/main'; -import { getGasFee, send } from '../../requests'; -import { checkHasEnoughFunds } from '../../utils'; -import { Console } from '../../utils/console'; -import { validationSchema } from './validationSchema'; - -const defaultValues = { - amount: { amount: '', denom: 'NYM' as CurrencyDenom }, - memo: '', - to: '', -}; - -export type TFormData = { - amount: MajorCurrencyAmount; - memo: string; - to: string; -}; - -export const SendWizard = () => { - const [activeStep, setActiveStep] = useState(0); - const [isLoading, setIsLoading] = useState(false); - const [requestError, setRequestError] = useState(); - const [transferFee, setTransferFee] = useState(); - const [confirmedData, setConfirmedData] = useState(); - - const { userBalance } = useContext(AppContext); - - useEffect(() => { - const getFee = async () => { - const fee = await getGasFee('Send'); - setTransferFee(fee.amount); - }; - getFee(); - }, []); - - const steps = ['Enter address', 'Review and send', 'Await confirmation']; - - const methods = useForm({ - defaultValues, - resolver: yupResolver(validationSchema), - }); - - const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1)); - - const handlePreviousStep = () => setActiveStep((s) => s - 1); - - const handleFinish = () => { - methods.reset(); - setIsLoading(false); - setRequestError(undefined); - setConfirmedData(undefined); - setActiveStep(0); - }; - - const handleSend = async () => { - setIsLoading(true); - - const formState = methods.getValues(); - - const hasEnoughFunds = await checkHasEnoughFunds(formState.amount.amount); - if (!hasEnoughFunds) { - methods.setError('amount.amount', { - message: 'Not enough funds in wallet', - }); - handlePreviousStep(); - return; - } - setActiveStep((s) => s + 1); - - send({ - amount: formState.amount, - address: formState.to, - memo: formState.memo, - }) - .then((res: SendTxResult) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { details, tx_hash } = res; - - setActiveStep((s) => s + 1); - setConfirmedData({ - ...details, - tx_hash, - }); - userBalance.fetchBalance(); - }) - .catch((e) => { - setRequestError(e); - Console.error(e); - }) - .finally(() => { - setIsLoading(false); - }); - }; - - return ( - - - - {steps.map((s) => ( - - {s} - - ))} - - - {activeStep === 0 && } - {activeStep === 1 && } - - - - {activeStep === 1 && ( - - )} - - - - - ); -}; diff --git a/nym-wallet/src/pages/send/index.tsx b/nym-wallet/src/pages/send/index.tsx deleted file mode 100644 index 386f4876d1..0000000000 --- a/nym-wallet/src/pages/send/index.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { useContext } from 'react'; -import { NymCard } from '../../components'; -import { SendWizard } from './SendWizard'; -import { AppContext } from '../../context/main'; -import { PageLayout } from '../../layouts'; - -export const Send = () => { - const { clientDetails } = useContext(AppContext); - return ( - - - - - - ); -}; diff --git a/nym-wallet/src/pages/send/validationSchema.ts b/nym-wallet/src/pages/send/validationSchema.ts deleted file mode 100644 index ba15abdc44..0000000000 --- a/nym-wallet/src/pages/send/validationSchema.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as Yup from 'yup'; -import { validateAmount } from '../../utils'; - -export const validationSchema = Yup.object().shape({ - to: Yup.string().strict().trim('Cannot have leading space').required(), - amount: Yup.object().shape({ - amount: Yup.string() - .required('Amount is required') - .test('valid-amount', 'A valid amount is required', (amount) => validateAmount(amount || '0', '0')), - denom: Yup.string(), - }), -}); diff --git a/nym-wallet/src/pages/settings/system-variables.tsx b/nym-wallet/src/pages/settings/system-variables.tsx index c075cd8561..d76aeb6708 100644 --- a/nym-wallet/src/pages/settings/system-variables.tsx +++ b/nym-wallet/src/pages/settings/system-variables.tsx @@ -6,7 +6,7 @@ import { PercentOutlined } from '@mui/icons-material'; import { yupResolver } from '@hookform/resolvers/yup'; import { InclusionProbabilityResponse, SelectionChance } from '@nymproject/types'; import { validationSchema } from './validationSchema'; -import { Fee, InfoTooltip } from '../../components'; +import { InfoTooltip } from '../../components'; import { useCheckOwnership } from '../../hooks/useCheckOwnership'; import { updateMixnode, vestingUpdateMixnode } from '../../requests'; import { AppContext } from '../../context/main'; @@ -162,7 +162,6 @@ export const SystemVariables = ({ {nodeUpdateResponse === 'failed' && ( Node update failed )} - {!nodeUpdateResponse && } - - - ); -}; diff --git a/nym-wallet/src/pages/undelegate/index.tsx b/nym-wallet/src/pages/undelegate/index.tsx deleted file mode 100644 index 98f51b49e9..0000000000 --- a/nym-wallet/src/pages/undelegate/index.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Alert, AlertTitle, Box, Button, CircularProgress, Grid, IconButton } from '@mui/material'; -import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; -import { DelegationResult, PendingUndelegate, TPagedDelegations } from '@nymproject/types'; -import { Epoch } from 'src/types'; -import { UndelegateForm } from './UndelegateForm'; -import { AppContext } from '../../context/main'; -import { EnumRequestStatus, NymCard, RequestStatus } from '../../components'; -import { getCurrentEpoch, getReverseMixDelegations } from '../../requests'; -import { PageLayout } from '../../layouts'; -import { removeObjectDuplicates } from '../../utils'; -import { PendingEvents } from './PendingEvents'; - -export const Undelegate = () => { - const [message, setMessage] = useState(); - const [status, setStatus] = useState(EnumRequestStatus.initial); - const [isLoading, setIsLoading] = useState(true); - const [pagedDelegations, setPagesDelegations] = useState(); - const [pendingUndelegations /* , setPendingndelegations */] = useState(); - const [pendingDelegations /* , setPendingDelegations */] = useState(); - const [currentEndEpoch, setCurrentEndEpoch] = useState(); - const [showPendingDelegations, setShowPendingDelegations] = useState(false); - - const { clientDetails } = useContext(AppContext); - - const refresh = async () => { - const mixnodeDelegations = await getReverseMixDelegations(); - - // Temporarily comment out to fix breaking type change - - // const pendingEvents = await getPendingDelegations(); - // const pendingVestingEvents = await getPendingVestingDelegations(); - // const pendingUndelegationEvents = [...pendingEvents, ...pendingVestingEvents] - // .filter((evt): evt is { Undelegate: PendingUndelegate } => 'Undelegate' in evt) - // .map((e) => ({ ...e.Undelegate })); - // const pendingDelegationEvents = [...pendingEvents, ...pendingVestingEvents] - // .filter((evt): evt is { Delegate: DelegationResult } => 'Delegate' in evt) - // .map((e) => ({ ...e.Delegate })); - const epoch = await getCurrentEpoch(); - - setCurrentEndEpoch(epoch.end); - - // Temporarily comment out to fix breaking type change - // setPendingUndelegations(pendingUndelegationEvents); - // setPendingDelegations(pendingDelegationEvents); - setPagesDelegations({ - ...mixnodeDelegations, - delegations: removeObjectDuplicates(mixnodeDelegations.delegations, 'node_identity'), - }); - }; - - const initialize = async () => { - setStatus(EnumRequestStatus.initial); - setIsLoading(true); - - try { - await refresh(); - } catch (e) { - setStatus(EnumRequestStatus.error); - setMessage(e as string); - } - - setIsLoading(false); - }; - - useEffect(() => { - initialize(); - }, [clientDetails]); - - return ( - - - - - {isLoading && ( - - - - )} - <> - {status === EnumRequestStatus.initial && pagedDelegations && ( - { - setMessage(m); - setStatus(EnumRequestStatus.error); - refresh(); - }} - onSuccess={(m) => { - setMessage(m); - setStatus(EnumRequestStatus.success); - refresh(); - }} - /> - )} - {status !== EnumRequestStatus.initial && ( - <> - - An error occurred with the request: {message} - - } - Success={ - - Undelegation request complete - {message} - - } - /> - - - - - )} - - - - {pendingDelegations?.length && ( - - setShowPendingDelegations((show) => !show)}> - {!showPendingDelegations ? : } - - } - > - {pendingDelegations ? ( - - ) : ( -
No pending delegations
- )} -
-
- )} - -
- ); -}; diff --git a/nym-wallet/src/pages/undelegate/validationSchema.ts b/nym-wallet/src/pages/undelegate/validationSchema.ts deleted file mode 100644 index 54a7cc7cde..0000000000 --- a/nym-wallet/src/pages/undelegate/validationSchema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as Yup from 'yup'; -import { validateKey } from '../../utils'; - -export const validationSchema = Yup.object().shape({ - identity: Yup.string() - .required() - .test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)), -}); diff --git a/nym-wallet/src/requests/actions.ts b/nym-wallet/src/requests/actions.ts index 7b0ebbbf79..bb9b161a9c 100644 --- a/nym-wallet/src/requests/actions.ts +++ b/nym-wallet/src/requests/actions.ts @@ -1,4 +1,4 @@ -import { Fee, MajorCurrencyAmount, SendTxResult, TransactionExecuteResult } from '@nymproject/types'; +import { Fee, DecCoin, SendTxResult, TransactionExecuteResult } from '@nymproject/types'; import { EnumNodeType, TBondGatewayArgs, TBondMixNodeArgs } from '../types'; import { invokeWrapper } from './wrapper'; @@ -15,7 +15,7 @@ export const unbondMixNode = async (fee?: Fee) => invokeWrapper invokeWrapper('update_mixnode', { profitMarginPercent }); -export const send = async (args: { amount: MajorCurrencyAmount; address: string; memo: string; fee?: Fee }) => +export const send = async (args: { amount: DecCoin; address: string; memo: string; fee?: Fee }) => invokeWrapper('send', args); export const unbond = async (type: EnumNodeType) => { diff --git a/nym-wallet/src/requests/delegation.ts b/nym-wallet/src/requests/delegation.ts index e26f8a6360..826e5e8f60 100644 --- a/nym-wallet/src/requests/delegation.ts +++ b/nym-wallet/src/requests/delegation.ts @@ -2,7 +2,7 @@ import { DelegationWithEverything, DelegationsSummaryResponse, TransactionExecuteResult, - MajorCurrencyAmount, + DecCoin, FeeDetails, } from '@nymproject/types'; import { invokeWrapper } from './wrapper'; @@ -26,5 +26,5 @@ export const undelegateAllFromMixnode = async ( fee: fee?.fee, }); -export const delegateToMixnode = async ({ identity, amount }: { identity: string; amount: MajorCurrencyAmount }) => +export const delegateToMixnode = async ({ identity, amount }: { identity: string; amount: DecCoin }) => invokeWrapper('delegate_to_mixnode', { identity, amount }); diff --git a/nym-wallet/src/requests/queries.ts b/nym-wallet/src/requests/queries.ts index dd161015fe..405e29867c 100644 --- a/nym-wallet/src/requests/queries.ts +++ b/nym-wallet/src/requests/queries.ts @@ -4,9 +4,8 @@ import { StakeSaturationResponse, MixnodeStatusResponse, InclusionProbabilityResponse, - MajorCurrencyAmount, + DecCoin, MixNodeBond, - Operation, } from '@nymproject/types'; import { Epoch } from 'src/types'; import { invokeWrapper } from './wrapper'; @@ -28,7 +27,7 @@ export const getAllPendingDelegations = async () => export const getMixnodeBondDetails = async () => invokeWrapper('mixnode_bond_details'); export const getOperatorRewards = async (address: string) => - invokeWrapper('get_operator_rewards', { address }); + invokeWrapper('get_operator_rewards', { address }); export const getMixnodeStakeSaturation = async (identity: string) => invokeWrapper('mixnode_stake_saturation', { identity }); @@ -43,10 +42,6 @@ export const checkMixnodeOwnership = async () => invokeWrapper('owns_mi export const checkGatewayOwnership = async () => invokeWrapper('owns_gateway'); -// TODO: remove this method -export const getGasFee = async (operation: Operation): Promise => - invokeWrapper('get_old_and_incorrect_hardcoded_fee', { operation }); - export const getInclusionProbability = async (identity: string) => invokeWrapper('mixnode_inclusion_probability', { identity }); diff --git a/nym-wallet/src/requests/simulate.ts b/nym-wallet/src/requests/simulate.ts index b574a55ba5..a537a7cb0d 100644 --- a/nym-wallet/src/requests/simulate.ts +++ b/nym-wallet/src/requests/simulate.ts @@ -1,4 +1,4 @@ -import { FeeDetails, MajorCurrencyAmount, Gateway, MixNode } from '@nymproject/types'; +import { FeeDetails, DecCoin, Gateway, MixNode } from '@nymproject/types'; import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types'; import { invokeWrapper } from './wrapper'; @@ -14,7 +14,7 @@ export const simulateUnbondMixnode = async (args: any) => invokeWrapper invokeWrapper('simulate_update_mixnode', args); -export const simulateDelegateToMixnode = async (args: { identity: string; amount: MajorCurrencyAmount }) => +export const simulateDelegateToMixnode = async (args: { identity: string; amount: DecCoin }) => invokeWrapper('simulate_delegate_to_mixnode', args); export const simulateUndelegateFromMixnode = async (identity: string) => @@ -35,11 +35,8 @@ export const simulateVestingCompoundDelgatorReward = async (identity: string) => export const simulateVestingUndelegateFromMixnode = async (args: any) => invokeWrapper('simulate_vesting_undelegate_from_mixnode', args); -export const simulateVestingBondGateway = async (args: { - gateway: Gateway; - pledge: MajorCurrencyAmount; - ownerSignature: string; -}) => invokeWrapper('simulate_vesting_bond_gateway', args); +export const simulateVestingBondGateway = async (args: { gateway: Gateway; pledge: DecCoin; ownerSignature: string }) => + invokeWrapper('simulate_vesting_bond_gateway', args); export const simulateVestingUnbondGateway = async (args: any) => invokeWrapper('simulate_vesting_unbond_gateway', args); @@ -47,11 +44,8 @@ export const simulateVestingUnbondGateway = async (args: any) => export const simulateVestingDelegateToMixnode = async (args: { identity: string }) => invokeWrapper('simulate_vesting_delegate_to_mixnode', args); -export const simulateVestingBondMixnode = async (args: { - mixnode: MixNode; - pledge: MajorCurrencyAmount; - ownerSignature: string; -}) => invokeWrapper('simulate_vesting_bond_mixnode', args); +export const simulateVestingBondMixnode = async (args: { mixnode: MixNode; pledge: DecCoin; ownerSignature: string }) => + invokeWrapper('simulate_vesting_bond_mixnode', args); export const simulateVestingUnbondMixnode = async () => invokeWrapper('simulate_vesting_unbond_mixnode'); @@ -61,5 +55,5 @@ export const simulateVestingUpdateMixnode = async (args: any) => export const simulateWithdrawVestedCoins = async (args: any) => invokeWrapper('simulate_withdraw_vested_coins', args); -export const simulateSend = async ({ address, amount }: { address: string; amount: MajorCurrencyAmount }) => +export const simulateSend = async ({ address, amount }: { address: string; amount: DecCoin }) => invokeWrapper('simulate_send', { address, amount }); diff --git a/nym-wallet/src/requests/vesting.ts b/nym-wallet/src/requests/vesting.ts index c9640bccf0..16d5aa768d 100644 --- a/nym-wallet/src/requests/vesting.ts +++ b/nym-wallet/src/requests/vesting.ts @@ -2,7 +2,7 @@ import { TNodeType, FeeDetails, Gateway, - MajorCurrencyAmount, + DecCoin, MixNode, OriginalVestingResponse, Period, @@ -13,17 +13,15 @@ import { import { Fee } from '@nymproject/types/dist/types/rust/Fee'; import { invokeWrapper } from './wrapper'; -export const getLockedCoins = async (): Promise => - invokeWrapper('locked_coins'); +export const getLockedCoins = async (): Promise => invokeWrapper('locked_coins'); -export const getSpendableCoins = async (): Promise => - invokeWrapper('spendable_coins'); +export const getSpendableCoins = async (): Promise => invokeWrapper('spendable_coins'); -export const getVestingCoins = async (vestingAccountAddress: string): Promise => - invokeWrapper('vesting_coins', { vestingAccountAddress }); +export const getVestingCoins = async (vestingAccountAddress: string): Promise => + invokeWrapper('vesting_coins', { vestingAccountAddress }); -export const getVestedCoins = async (vestingAccountAddress: string): Promise => - invokeWrapper('vested_coins', { vestingAccountAddress }); +export const getVestedCoins = async (vestingAccountAddress: string): Promise => + invokeWrapper('vested_coins', { vestingAccountAddress }); export const getOriginalVesting = async (vestingAccountAddress: string): Promise => { const res = await invokeWrapper('original_vesting', { vestingAccountAddress }); @@ -39,7 +37,7 @@ export const vestingBondGateway = async ({ ownerSignature, }: { gateway: Gateway; - pledge: MajorCurrencyAmount; + pledge: DecCoin; ownerSignature: string; }) => invokeWrapper('vesting_bond_gateway', { gateway, ownerSignature, pledge }); @@ -52,14 +50,14 @@ export const vestingBondMixNode = async ({ ownerSignature, }: { mixnode: MixNode; - pledge: MajorCurrencyAmount; + pledge: DecCoin; ownerSignature: string; }) => invokeWrapper('vesting_bond_mixnode', { mixnode, ownerSignature, pledge }); export const vestingUnbondMixnode = async (fee?: Fee) => invokeWrapper('vesting_unbond_mixnode', { fee }); -export const withdrawVestedCoins = async (amount: MajorCurrencyAmount) => +export const withdrawVestedCoins = async (amount: DecCoin) => invokeWrapper('withdraw_vested_coins', { amount }); export const vestingUpdateMixnode = async (profitMarginPercent: number) => @@ -71,7 +69,7 @@ export const vestingDelegateToMixnode = async ({ fee, }: { identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; fee?: FeeDetails; }) => invokeWrapper('vesting_delegate_to_mixnode', { identity, amount, fee: fee?.fee }); @@ -96,7 +94,7 @@ export const getVestingPledgeInfo = async ({ }; export const vestingDelegatedFree = async (vestingAccountAddress: string) => - invokeWrapper('delegated_free', { vestingAccountAddress }); + invokeWrapper('delegated_free', { vestingAccountAddress }); export const vestingUnbond = async (type: TNodeType) => { if (type === 'mixnode') return vestingUnbondMixnode(); diff --git a/nym-wallet/src/theme/mui-theme.d.ts b/nym-wallet/src/theme/mui-theme.d.ts index 21096a1827..83e2c36d79 100644 --- a/nym-wallet/src/theme/mui-theme.d.ts +++ b/nym-wallet/src/theme/mui-theme.d.ts @@ -82,7 +82,7 @@ declare module '@mui/material/styles' { /** * Add anything not palette related to the theme here */ - interface NymTheme { } + interface NymTheme {} /** * This augments the definitions of the MUI Theme with the Nym theme, as well as @@ -90,8 +90,8 @@ declare module '@mui/material/styles' { * * IMPORTANT: only add extensions to the interfaces above, do not modify the lines below */ - interface Theme extends NymTheme { } - interface ThemeOptions extends Partial { } - interface Palette extends NymPaletteAndNymWalletPalette { } - interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions { } + interface Theme extends NymTheme {} + interface ThemeOptions extends Partial {} + interface Palette extends NymPaletteAndNymWalletPalette {} + interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions {} } diff --git a/nym-wallet/src/types/global.ts b/nym-wallet/src/types/global.ts index ae5f46bedd..846817b0fc 100644 --- a/nym-wallet/src/types/global.ts +++ b/nym-wallet/src/types/global.ts @@ -1,4 +1,4 @@ -import { Gateway, MajorCurrencyAmount, MixNode, PledgeData } from '@nymproject/types'; +import { Gateway, DecCoin, MixNode, PledgeData } from '@nymproject/types'; import { Fee } from '@nymproject/types/dist/types/rust/Fee'; export enum EnumNodeType { @@ -19,7 +19,7 @@ export type TPendingDelegation = { export type TDelegation = { owner: string; node_identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; block_height: number; proxy: string; // proxy address used to delegate the funds on behalf of another address pending?: TPendingDelegation; @@ -27,21 +27,21 @@ export type TDelegation = { export type TBondGatewayArgs = { gateway: Gateway; - pledge: MajorCurrencyAmount; + pledge: DecCoin; ownerSignature: string; fee?: Fee; }; export type TBondMixNodeArgs = { mixnode: MixNode; - pledge: MajorCurrencyAmount; + pledge: DecCoin; ownerSignature: string; fee?: Fee; }; export type TDelegateArgs = { identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; }; export type Period = 'Before' | { In: number } | 'After'; diff --git a/nym-wallet/src/utils/index.ts b/nym-wallet/src/utils/index.ts index 82c3f97f0a..ab873134fc 100644 --- a/nym-wallet/src/utils/index.ts +++ b/nym-wallet/src/utils/index.ts @@ -1,7 +1,7 @@ import { appWindow } from '@tauri-apps/api/window'; import bs58 from 'bs58'; import { valid } from 'semver'; -import { isValidRawCoin, MajorAmountString } from '@nymproject/types'; +import { isValidRawCoin, DecCoin } from '@nymproject/types'; import { TPoolOption } from 'src/components'; import { getLockedCoins, getSpendableCoins, userBalance } from '../requests'; import { Console } from './console'; @@ -19,8 +19,8 @@ export const validateKey = (key: string, bytesLength: number): boolean => { }; export const validateAmount = async ( - majorAmountAsString: MajorAmountString, - minimumAmountAsString: MajorAmountString, + majorAmountAsString: DecCoin['amount'], + minimumAmountAsString: DecCoin['amount'], ): Promise => { // tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc if (!Number(majorAmountAsString)) { diff --git a/tools/ts-rs-cli/src/main.rs b/tools/ts-rs-cli/src/main.rs index b5f06dd38f..70a442cbae 100644 --- a/tools/ts-rs-cli/src/main.rs +++ b/tools/ts-rs-cli/src/main.rs @@ -4,7 +4,7 @@ use walkdir::WalkDir; use mixnet_contract_common::mixnode::RewardedSetNodeStatus; use nym_types::account::{Account, AccountEntry, AccountWithMnemonic, Balance}; -use nym_types::currency::{CurrencyDenom, MajorAmountString, MajorCurrencyAmount}; +use nym_types::currency::{CurrencyDenom, DecCoin}; use nym_types::delegation::{ Delegation, DelegationEvent, DelegationEventKind, DelegationRecord, DelegationResult, DelegationWithEverything, DelegationsSummaryResponse, PendingUndelegate, @@ -59,7 +59,6 @@ fn main() { do_export!(AccountEntry); do_export!(AccountWithMnemonic); do_export!(Balance); - do_export!(CurrencyDenom); do_export!(Delegation); do_export!(DelegationEvent); do_export!(DelegationEventKind); @@ -77,8 +76,8 @@ fn main() { do_export!(GasInfo); do_export!(Gateway); do_export!(GatewayBond); - do_export!(MajorAmountString); - do_export!(MajorCurrencyAmount); + do_export!(CurrencyDenom); + do_export!(DecCoin); do_export!(MixNode); do_export!(MixNodeBond); do_export!(OriginalVestingResponse); diff --git a/ts-packages/react-components/src/components/currency/Currency.stories.tsx b/ts-packages/react-components/src/components/currency/Currency.stories.tsx index b5a9be21bc..6a7c16c9df 100644 --- a/ts-packages/react-components/src/components/currency/Currency.stories.tsx +++ b/ts-packages/react-components/src/components/currency/Currency.stories.tsx @@ -11,24 +11,24 @@ export default { export const Mainnet = () => ( - - - - + + + + {amounts.map((amount) => ( - + ))} ); export const Testnet = () => ( - - - - + + + + {amounts.map((amount) => ( - + ))} ); @@ -40,7 +40,7 @@ export const WithSX = () => ( {amounts.map((amount) => ( @@ -48,7 +48,7 @@ export const WithSX = () => ( {amounts.map((amount) => ( ))} diff --git a/ts-packages/react-components/src/components/currency/Currency.tsx b/ts-packages/react-components/src/components/currency/Currency.tsx index 1168ff6b24..6f740a3916 100644 --- a/ts-packages/react-components/src/components/currency/Currency.tsx +++ b/ts-packages/react-components/src/components/currency/Currency.tsx @@ -1,11 +1,11 @@ import * as React from 'react'; -import type { MajorCurrencyAmount } from '@nymproject/types'; +import type { DecCoin } from '@nymproject/types'; import { Stack, SxProps, Typography } from '@mui/material'; import { CurrencyWithCoinMark } from './CurrencyWithCoinMark'; import { CURRENCY_AMOUNT_SPACING, CurrencyAmount } from './CurrencyAmount'; export const Currency: React.FC<{ - majorAmount?: MajorCurrencyAmount; + majorAmount?: DecCoin; showDenom?: boolean; showCoinMark?: boolean; coinMarkPrefix?: boolean; diff --git a/ts-packages/react-components/src/components/currency/CurrencyAmount.stories.tsx b/ts-packages/react-components/src/components/currency/CurrencyAmount.stories.tsx index d971d03207..0c6743022f 100644 --- a/ts-packages/react-components/src/components/currency/CurrencyAmount.stories.tsx +++ b/ts-packages/react-components/src/components/currency/CurrencyAmount.stories.tsx @@ -34,7 +34,7 @@ export const amounts = [ export const WithSeparators = () => ( {amounts.map((amount) => ( - + ))} ); @@ -42,20 +42,20 @@ export const WithSeparators = () => ( export const NoSeparators = () => ( {amounts.map((amount) => ( - + ))} ); -export const MaxRange = () => ; +export const MaxRange = () => ; export const Weird = () => ( - - - - - + + + + + ); @@ -66,7 +66,7 @@ export const NoSeparatorsWithSX = () => ( {amounts.map((amount) => ( @@ -79,7 +79,7 @@ export const WithSX = () => ( {amounts.map((amount) => ( ))} diff --git a/ts-packages/react-components/src/components/currency/CurrencyAmount.tsx b/ts-packages/react-components/src/components/currency/CurrencyAmount.tsx index 44726eacfc..534e79bf7c 100644 --- a/ts-packages/react-components/src/components/currency/CurrencyAmount.tsx +++ b/ts-packages/react-components/src/components/currency/CurrencyAmount.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-array-index-key */ import * as React from 'react'; -import type { MajorCurrencyAmount } from '@nymproject/types'; +import type { DecCoin } from '@nymproject/types'; import { Stack, SxProps, Typography } from '@mui/material'; export const CURRENCY_AMOUNT_SPACING = 0.35; @@ -94,7 +94,7 @@ export const CurrencyAmountString: React.FC<{ }; export const CurrencyAmount: React.FC<{ - majorAmount?: MajorCurrencyAmount; + majorAmount?: DecCoin; showSeparators?: boolean; sx?: SxProps; }> = ({ majorAmount, ...props }) => ; diff --git a/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx b/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx index bc949ba4b2..f03174776f 100644 --- a/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx +++ b/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { ChangeEvent } from 'react'; import { InputAdornment, TextField } from '@mui/material'; import { SxProps } from '@mui/system'; -import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types'; +import { CurrencyDenom, DecCoin } from '@nymproject/types'; import { CoinMark } from '../coins/CoinMark'; const MAX_VALUE = 1_000_000_000_000_000; @@ -18,8 +18,8 @@ export const CurrencyFormField: React.FC<{ validationError?: string; placeholder?: string; label?: string; - denom?: CurrencyDenom; - onChanged?: (newValue: MajorCurrencyAmount) => void; + denom?: string; + onChanged?: (newValue: DecCoin) => void; onValidate?: (newValue: string | undefined, isValid: boolean, error?: string) => void; sx?: SxProps; }> = ({ @@ -111,9 +111,9 @@ export const CurrencyFormField: React.FC<{ doValidation(newValue); if (onChanged) { - const newMajorCurrencyAmount: MajorCurrencyAmount = { + const newMajorCurrencyAmount: DecCoin = { amount: newValue, - denom, + denom: denom as CurrencyDenom, }; onChanged(newMajorCurrencyAmount); } @@ -132,8 +132,8 @@ export const CurrencyFormField: React.FC<{ required, endAdornment: showCoinMark && ( - {denom === 'NYM' && } - {denom !== 'NYM' && NYMT} + {denom === 'unym' && } + {denom !== 'unym' && NYMT} ), ...{ diff --git a/ts-packages/react-components/src/components/currency/CurrencyWithCoinMark.tsx b/ts-packages/react-components/src/components/currency/CurrencyWithCoinMark.tsx index 0fef6e3a75..3431a975be 100644 --- a/ts-packages/react-components/src/components/currency/CurrencyWithCoinMark.tsx +++ b/ts-packages/react-components/src/components/currency/CurrencyWithCoinMark.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { MajorCurrencyAmount } from '@nymproject/types'; +import { DecCoin } from '@nymproject/types'; import { Stack, SxProps } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { CoinMark } from '../coins/CoinMark'; @@ -7,7 +7,7 @@ import { CoinMarkTestnet } from '../coins/CoinMarkTestnet'; import { CurrencyAmount } from './CurrencyAmount'; export const CurrencyWithCoinMark: React.FC<{ - majorAmount?: MajorCurrencyAmount; + majorAmount?: DecCoin; fontSize?: number; prefix?: boolean; showSeparators?: boolean; @@ -18,7 +18,7 @@ export const CurrencyWithCoinMark: React.FC<{ if (!majorAmount) { return -; } - const DenomMark = majorAmount.denom === 'NYMT' ? CoinMarkTestnet : CoinMark; + const DenomMark = majorAmount.denom === 'nymt' ? CoinMarkTestnet : CoinMark; return ( {prefix ? ( diff --git a/ts-packages/types/src/types/global.ts b/ts-packages/types/src/types/global.ts index 98ec025a81..1ec545138c 100644 --- a/ts-packages/types/src/types/global.ts +++ b/ts-packages/types/src/types/global.ts @@ -1,4 +1,4 @@ -import { MixNode, MajorCurrencyAmount } from './rust'; +import { MixNode, DecCoin } from './rust'; export type TNodeType = 'mixnode' | 'gateway'; @@ -10,7 +10,7 @@ export type TNodeOwnership = { export type TDelegation = { owner: string; node_identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; block_height: number; proxy: string; // proxy address used to delegate the funds on behalf of anouther address }; @@ -21,8 +21,8 @@ export type TPagedDelegations = { }; export type TMixnodeBondDetails = { - pledge_amount: MajorCurrencyAmount; - total_delegation: MajorCurrencyAmount; + pledge_amount: DecCoin; + total_delegation: DecCoin; owner: string; layer: string; block_height: number; diff --git a/ts-packages/types/src/types/rust/Account.ts b/ts-packages/types/src/types/rust/Account.ts index 1f3c8f470a..ea0586828f 100644 --- a/ts-packages/types/src/types/rust/Account.ts +++ b/ts-packages/types/src/types/rust/Account.ts @@ -1,7 +1,7 @@ import type { CurrencyDenom } from './CurrencyDenom'; export interface Account { - contract_address: string; client_address: string; - denom: CurrencyDenom; + base_mix_denom: string; + display_mix_denom: CurrencyDenom; } diff --git a/ts-packages/types/src/types/rust/Balance.ts b/ts-packages/types/src/types/rust/Balance.ts index 80ebb917a1..ba6be26651 100644 --- a/ts-packages/types/src/types/rust/Balance.ts +++ b/ts-packages/types/src/types/rust/Balance.ts @@ -1,6 +1,6 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface Balance { - amount: MajorCurrencyAmount; + amount: DecCoin; printable_balance: string; } diff --git a/ts-packages/types/src/types/rust/Coin.ts b/ts-packages/types/src/types/rust/Coin.ts index e02141439d..dda00291d7 100644 --- a/ts-packages/types/src/types/rust/Coin.ts +++ b/ts-packages/types/src/types/rust/Coin.ts @@ -1,2 +1,4 @@ - -export interface Coin { denom: string, amount: bigint, } \ No newline at end of file +export interface Coin { + denom: string; + amount: bigint; +} diff --git a/ts-packages/types/src/types/rust/CosmosFee.ts b/ts-packages/types/src/types/rust/CosmosFee.ts index c4220b1658..54c0f689bc 100644 --- a/ts-packages/types/src/types/rust/CosmosFee.ts +++ b/ts-packages/types/src/types/rust/CosmosFee.ts @@ -1,3 +1,8 @@ -import type { Coin } from "./Coin"; +import type { Coin } from './Coin'; -export interface CosmosFee { amount: Array, gas_limit: bigint, payer: string | null, granter: string | null, } \ No newline at end of file +export interface CosmosFee { + amount: Array; + gas_limit: bigint; + payer: string | null; + granter: string | null; +} diff --git a/ts-packages/types/src/types/rust/Currency.ts b/ts-packages/types/src/types/rust/Currency.ts deleted file mode 100644 index f0c6d57fbb..0000000000 --- a/ts-packages/types/src/types/rust/Currency.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { CurrencyDenom } from './CurrencyDenom'; -import type { MajorAmountString } from './CurrencyStringMajorAmount'; - -export interface MajorCurrencyAmount { - amount: MajorAmountString; - denom: CurrencyDenom; -} diff --git a/ts-packages/types/src/types/rust/CurrencyDenom.ts b/ts-packages/types/src/types/rust/CurrencyDenom.ts index d6a45dc3d2..04760b4fd7 100644 --- a/ts-packages/types/src/types/rust/CurrencyDenom.ts +++ b/ts-packages/types/src/types/rust/CurrencyDenom.ts @@ -1 +1 @@ -export type CurrencyDenom = 'NYM' | 'NYMT' | 'NYX' | 'NYXT'; +export type CurrencyDenom = 'unknown' | 'nym' | 'nymt' | 'nyx' | 'nyxt'; diff --git a/ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts b/ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts deleted file mode 100644 index 9863028330..0000000000 --- a/ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts +++ /dev/null @@ -1 +0,0 @@ -export type MajorAmountString = string; diff --git a/ts-packages/types/src/types/rust/DecCoin.ts b/ts-packages/types/src/types/rust/DecCoin.ts new file mode 100644 index 0000000000..d2b1b4dd69 --- /dev/null +++ b/ts-packages/types/src/types/rust/DecCoin.ts @@ -0,0 +1,3 @@ +import type { CurrencyDenom } from './CurrencyDenom'; + +export type DecCoin = { denom: CurrencyDenom; amount: string }; diff --git a/ts-packages/types/src/types/rust/Delegation.ts b/ts-packages/types/src/types/rust/Delegation.ts index 72e2ce17c7..c6f857254d 100644 --- a/ts-packages/types/src/types/rust/Delegation.ts +++ b/ts-packages/types/src/types/rust/Delegation.ts @@ -1,9 +1,9 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface Delegation { owner: string; node_identity: string; - amount: MajorCurrencyAmount; + amount: DecCoin; block_height: bigint; proxy: string | null; } diff --git a/ts-packages/types/src/types/rust/DelegationEvent.ts b/ts-packages/types/src/types/rust/DelegationEvent.ts index ec44a8fda7..a7fbca2737 100644 --- a/ts-packages/types/src/types/rust/DelegationEvent.ts +++ b/ts-packages/types/src/types/rust/DelegationEvent.ts @@ -1,11 +1,11 @@ +import type { DecCoin } from './DecCoin'; import type { DelegationEventKind } from './DelegationEventKind'; -import type { MajorCurrencyAmount } from './Currency'; export interface DelegationEvent { kind: DelegationEventKind; node_identity: string; address: string; - amount: MajorCurrencyAmount | null; + amount: DecCoin | null; block_height: bigint; proxy: string | null; } diff --git a/ts-packages/types/src/types/rust/DelegationRecord.ts b/ts-packages/types/src/types/rust/DelegationRecord.ts index 38ec024a35..7f1d6aa7a3 100644 --- a/ts-packages/types/src/types/rust/DelegationRecord.ts +++ b/ts-packages/types/src/types/rust/DelegationRecord.ts @@ -1,7 +1,7 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface DelegationRecord { - amount: MajorCurrencyAmount; + amount: DecCoin; block_height: bigint; delegated_on_iso_datetime: string; uses_vesting_contract_tokens: boolean; diff --git a/ts-packages/types/src/types/rust/DelegationResult.ts b/ts-packages/types/src/types/rust/DelegationResult.ts index 33c6dba091..f6d29d9435 100644 --- a/ts-packages/types/src/types/rust/DelegationResult.ts +++ b/ts-packages/types/src/types/rust/DelegationResult.ts @@ -1,7 +1,7 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface DelegationResult { source_address: string; target_address: string; - amount: MajorCurrencyAmount | null; + amount: DecCoin | null; } diff --git a/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts b/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts index a801d2865e..0cb9346c59 100644 --- a/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts +++ b/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts @@ -1,8 +1,8 @@ +import type { DecCoin } from './DecCoin'; import type { DelegationWithEverything } from './DelegationWithEverything'; -import type { MajorCurrencyAmount } from './Currency'; export interface DelegationsSummaryResponse { delegations: Array; - total_delegations: MajorCurrencyAmount; - total_rewards: MajorCurrencyAmount; + total_delegations: DecCoin; + total_rewards: DecCoin; } diff --git a/ts-packages/types/src/types/rust/DelegationWithEverything.ts b/ts-packages/types/src/types/rust/DelegationWithEverything.ts index 6bf6896e0b..8c5f824e5e 100644 --- a/ts-packages/types/src/types/rust/DelegationWithEverything.ts +++ b/ts-packages/types/src/types/rust/DelegationWithEverything.ts @@ -1,20 +1,20 @@ +import type { DecCoin } from './DecCoin'; import type { DelegationEvent } from './DelegationEvent'; import type { DelegationRecord } from './DelegationRecord'; -import type { MajorCurrencyAmount } from './Currency'; export interface DelegationWithEverything { owner: string; node_identity: string; - amount: MajorCurrencyAmount; - total_delegation: MajorCurrencyAmount | null; - pledge_amount: MajorCurrencyAmount | null; + amount: DecCoin; + total_delegation: DecCoin | null; + pledge_amount: DecCoin | null; block_height: bigint; delegated_on_iso_datetime: string; profit_margin_percent: number | null; avg_uptime_percent: number | null; stake_saturation: number | null; uses_vesting_contract_tokens: boolean; - accumulated_rewards: MajorCurrencyAmount | null; + accumulated_rewards: DecCoin | null; pending_events: Array; history: Array; } diff --git a/ts-packages/types/src/types/rust/FeeDetails.ts b/ts-packages/types/src/types/rust/FeeDetails.ts index 17d9631bc9..60bcd4cae5 100644 --- a/ts-packages/types/src/types/rust/FeeDetails.ts +++ b/ts-packages/types/src/types/rust/FeeDetails.ts @@ -1,4 +1,4 @@ +import type { DecCoin } from './DecCoin'; import type { Fee } from './Fee'; -import type { MajorCurrencyAmount } from './Currency'; -export type FeeDetails = { amount: MajorCurrencyAmount | null; fee: Fee }; +export type FeeDetails = { amount: DecCoin | null; fee: Fee }; diff --git a/ts-packages/types/src/types/rust/GasInfo.ts b/ts-packages/types/src/types/rust/GasInfo.ts index 3fbaaac7ba..fd5b21e582 100644 --- a/ts-packages/types/src/types/rust/GasInfo.ts +++ b/ts-packages/types/src/types/rust/GasInfo.ts @@ -1,7 +1,6 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { Gas } from './Gas'; export interface GasInfo { - gas_wanted: bigint; - gas_used: bigint; - fee: MajorCurrencyAmount; + gas_wanted: Gas; + gas_used: Gas; } diff --git a/ts-packages/types/src/types/rust/GatewayBond.ts b/ts-packages/types/src/types/rust/GatewayBond.ts index 4ffe275186..277994efac 100644 --- a/ts-packages/types/src/types/rust/GatewayBond.ts +++ b/ts-packages/types/src/types/rust/GatewayBond.ts @@ -1,8 +1,8 @@ +import type { DecCoin } from './DecCoin'; import type { Gateway } from './Gateway'; -import type { MajorCurrencyAmount } from './Currency'; export interface GatewayBond { - pledge_amount: MajorCurrencyAmount; + pledge_amount: DecCoin; owner: string; block_height: bigint; gateway: Gateway; diff --git a/ts-packages/types/src/types/rust/MixNodeBond.ts b/ts-packages/types/src/types/rust/MixNodeBond.ts index 06e8cc884f..bfcb737290 100644 --- a/ts-packages/types/src/types/rust/MixNodeBond.ts +++ b/ts-packages/types/src/types/rust/MixNodeBond.ts @@ -1,13 +1,13 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; import type { MixNode } from './Mixnode'; export interface MixNodeBond { - pledge_amount: MajorCurrencyAmount; - total_delegation: MajorCurrencyAmount; + pledge_amount: DecCoin; + total_delegation: DecCoin; owner: string; layer: string; block_height: bigint; mix_node: MixNode; proxy: string | null; - accumulated_rewards: MajorCurrencyAmount | null; + accumulated_rewards: DecCoin | null; } diff --git a/ts-packages/types/src/types/rust/Operation.ts b/ts-packages/types/src/types/rust/Operation.ts deleted file mode 100644 index 405b5dbb0b..0000000000 --- a/ts-packages/types/src/types/rust/Operation.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type Operation = - | 'Upload' - | 'Init' - | 'Migrate' - | 'ChangeAdmin' - | 'Send' - | 'BondMixnode' - | 'BondMixnodeOnBehalf' - | 'UnbondMixnode' - | 'UnbondMixnodeOnBehalf' - | 'UpdateMixnodeConfig' - | 'DelegateToMixnode' - | 'DelegateToMixnodeOnBehalf' - | 'UndelegateFromMixnode' - | 'UndelegateFromMixnodeOnBehalf' - | 'BondGateway' - | 'BondGatewayOnBehalf' - | 'UnbondGateway' - | 'UnbondGatewayOnBehalf' - | 'UpdateContractSettings' - | 'BeginMixnodeRewarding' - | 'FinishMixnodeRewarding' - | 'TrackUnbondGateway' - | 'TrackUnbondMixnode' - | 'WithdrawVestedCoins' - | 'TrackUndelegation' - | 'CreatePeriodicVestingAccount' - | 'AdvanceCurrentInterval' - | 'AdvanceCurrentEpoch' - | 'WriteRewardedSet' - | 'ClearRewardedSet' - | 'UpdateMixnetAddress' - | 'CheckpointMixnodes' - | 'ReconcileDelegations'; diff --git a/ts-packages/types/src/types/rust/OriginalVestingResponse.ts b/ts-packages/types/src/types/rust/OriginalVestingResponse.ts index 2ebbd40bb2..8d781d5ad0 100644 --- a/ts-packages/types/src/types/rust/OriginalVestingResponse.ts +++ b/ts-packages/types/src/types/rust/OriginalVestingResponse.ts @@ -1,7 +1,7 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface OriginalVestingResponse { - amount: MajorCurrencyAmount; + amount: DecCoin; number_of_periods: number; period_duration: bigint; } diff --git a/ts-packages/types/src/types/rust/PledgeData.ts b/ts-packages/types/src/types/rust/PledgeData.ts index 2f82758842..9fb19f9aaf 100644 --- a/ts-packages/types/src/types/rust/PledgeData.ts +++ b/ts-packages/types/src/types/rust/PledgeData.ts @@ -1,6 +1,6 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface PledgeData { - amount: MajorCurrencyAmount; + amount: DecCoin; block_time: bigint; } diff --git a/ts-packages/types/src/types/rust/RpcTransactionResponse.ts b/ts-packages/types/src/types/rust/RpcTransactionResponse.ts index d2938e149e..2463e3dd20 100644 --- a/ts-packages/types/src/types/rust/RpcTransactionResponse.ts +++ b/ts-packages/types/src/types/rust/RpcTransactionResponse.ts @@ -1,9 +1,12 @@ -import type { GasInfo } from './GasInfo'; +import type { DecCoin } from './DecCoin'; +import type { Gas } from './Gas'; export interface RpcTransactionResponse { index: number; tx_result_json: string; block_height: bigint; transaction_hash: string; - gas_info: GasInfo; + gas_used: Gas; + gas_wanted: Gas; + fee: DecCoin | null; } diff --git a/ts-packages/types/src/types/rust/SendTxResult.ts b/ts-packages/types/src/types/rust/SendTxResult.ts index 1e6f5ac366..b44de3324d 100644 --- a/ts-packages/types/src/types/rust/SendTxResult.ts +++ b/ts-packages/types/src/types/rust/SendTxResult.ts @@ -1,10 +1,13 @@ +import type { DecCoin } from './DecCoin'; +import type { Gas } from './Gas'; import type { TransactionDetails } from './TransactionDetails'; export interface SendTxResult { block_height: bigint; code: number; details: TransactionDetails; - gas_used: bigint; - gas_wanted: bigint; + gas_used: Gas; + gas_wanted: Gas; tx_hash: string; + fee: DecCoin | null; } diff --git a/ts-packages/types/src/types/rust/TauriTxResult.ts b/ts-packages/types/src/types/rust/TauriTxResult.ts deleted file mode 100644 index 93c47da931..0000000000 --- a/ts-packages/types/src/types/rust/TauriTxResult.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { TransactionDetails } from './TransactionDetails'; - -export interface TauriTxResult { - block_height: bigint; - code: number; - details: TransactionDetails; - gas_used: bigint; - gas_wanted: bigint; - tx_hash: string; -} diff --git a/ts-packages/types/src/types/rust/TransactionDetails.ts b/ts-packages/types/src/types/rust/TransactionDetails.ts index 8e26480c19..8de1cf9ec3 100644 --- a/ts-packages/types/src/types/rust/TransactionDetails.ts +++ b/ts-packages/types/src/types/rust/TransactionDetails.ts @@ -1,7 +1,7 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; export interface TransactionDetails { - amount: MajorCurrencyAmount; + amount: DecCoin; from_address: string; to_address: string; } diff --git a/ts-packages/types/src/types/rust/TransactionExecuteResult.ts b/ts-packages/types/src/types/rust/TransactionExecuteResult.ts index fd67980be6..e6d93e8448 100644 --- a/ts-packages/types/src/types/rust/TransactionExecuteResult.ts +++ b/ts-packages/types/src/types/rust/TransactionExecuteResult.ts @@ -1,10 +1,10 @@ +import type { DecCoin } from './DecCoin'; import type { GasInfo } from './GasInfo'; -import type { MajorCurrencyAmount } from './Currency'; export interface TransactionExecuteResult { logs_json: string; data_json: string; transaction_hash: string; gas_info: GasInfo; - fee: MajorCurrencyAmount; + fee: DecCoin | null; } diff --git a/ts-packages/types/src/types/rust/VestingAccountInfo.ts b/ts-packages/types/src/types/rust/VestingAccountInfo.ts index 793431c215..d317d045d1 100644 --- a/ts-packages/types/src/types/rust/VestingAccountInfo.ts +++ b/ts-packages/types/src/types/rust/VestingAccountInfo.ts @@ -1,4 +1,4 @@ -import type { MajorCurrencyAmount } from './Currency'; +import type { DecCoin } from './DecCoin'; import type { VestingPeriod } from './VestingPeriod'; export interface VestingAccountInfo { @@ -6,5 +6,5 @@ export interface VestingAccountInfo { staking_address: string | null; start_time: bigint; periods: Array; - amount: MajorCurrencyAmount; + amount: DecCoin; } diff --git a/ts-packages/types/src/types/rust/index.ts b/ts-packages/types/src/types/rust/index.ts index 1be7a06002..031ecf14fc 100644 --- a/ts-packages/types/src/types/rust/index.ts +++ b/ts-packages/types/src/types/rust/index.ts @@ -3,14 +3,13 @@ export * from './AccountEntry'; export * from './AccountWithMnemonic'; export * from './Balance'; export * from './CoreNodeStatusResponse'; -export * from './Currency'; export * from './CurrencyDenom'; -export * from './CurrencyStringMajorAmount'; export * from './Delegation'; export * from './DelegationEvent'; export * from './DelegationEventKind'; export * from './DelegationRecord'; export * from './DelegationResult'; +export * from './DecCoin'; export * from './DelegationSummaryResponse'; export * from './DelegationWithEverything'; export * from './Fee'; @@ -24,7 +23,6 @@ export * from './Mixnode'; export * from './MixNodeBond'; export * from './MixnodeStatus'; export * from './MixnodeStatusResponse'; -export * from './Operation'; export * from './OriginalVestingResponse'; export * from './PendingUndelegate'; export * from './Period'; diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index 2eba8eed0d..9a9a0d4499 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -285,7 +285,7 @@ pub async fn verify_bandwidth_credential( .vote_proposal( proposal_id, verification_result, - Some(Fee::PayerGranterAuto( + Some(Fee::new_payer_granter_auto( None, None, Some(verify_credential_body.0.gateway_cosmos_addr().to_owned()), @@ -319,7 +319,7 @@ pub async fn post_propose_release_funds( title, blinded_serial_number, voucher_value, - Some(Fee::PayerGranterAuto( + Some(Fee::new_payer_granter_auto( None, None, Some(propose_release_funds.0.gateway_cosmos_addr().to_owned()), From d62e13c93212f165f10fae6ae54457f33aea1966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 18 Jul 2022 12:37:07 +0300 Subject: [PATCH 05/91] 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 --- CHANGELOG.md | 4 + Cargo.lock | 3 + .../validator-client/src/client.rs | 13 +- .../traits/coconut_bandwidth_query_client.rs | 33 ++ .../coconut_bandwidth_signing_client.rs | 34 ++ .../validator-client/src/nymd/traits/mod.rs | 4 +- .../src/nymd/traits/multisig_query_client.rs | 4 +- .../validator-client/src/validator_api/mod.rs | 20 +- .../src/validator_api/routes.rs | 1 - .../coconut-bandwidth-contract/Cargo.toml | 1 + .../coconut-bandwidth-contract/src/lib.rs | 1 + .../coconut-bandwidth-contract/src/msg.rs | 13 +- .../src/spend_credential.rs | 148 +++++ .../multisig-contract/src/msg.rs | 5 + common/nymcoconut/src/lib.rs | 3 +- common/nymcoconut/src/tests/e2e.rs | 66 +-- common/nymcoconut/src/tests/helpers.rs | 87 +++ common/nymcoconut/src/tests/mod.rs | 2 + contracts/Cargo.lock | 26 +- contracts/Cargo.toml | 2 +- contracts/coconut-bandwidth/Cargo.toml | 4 +- contracts/coconut-bandwidth/src/contract.rs | 91 +-- contracts/coconut-bandwidth/src/error.rs | 3 + contracts/coconut-bandwidth/src/lib.rs | 4 +- contracts/coconut-bandwidth/src/queries.rs | 178 ++++++ contracts/coconut-bandwidth/src/storage.rs | 106 ++++ .../coconut-bandwidth/src/support/mod.rs | 2 + .../coconut-bandwidth/src/support/tests.rs | 45 -- .../src/support/tests/fixtures.rs | 22 + .../src/support/tests/helpers.rs | 22 + .../src/support/tests/mod.rs | 5 + .../coconut-bandwidth/src/transactions.rs | 134 ++++- contracts/coconut-test/Cargo.toml | 31 ++ .../coconut-test/src/deposit_and_release.rs | 99 ++++ contracts/coconut-test/src/helpers.rs | 64 +++ .../src/spend_credential_creates_proposal.rs | 155 ++++++ contracts/coconut-test/src/tests.rs | 6 + .../mixnet/src/delegations/transactions.rs | 5 - .../multisig/cw3-flex-multisig/Cargo.toml | 1 + .../cw3-flex-multisig/src/contract.rs | 242 ++++---- .../multisig/cw3-flex-multisig/src/error.rs | 3 + .../multisig/cw3-flex-multisig/src/state.rs | 2 + .../connection_handler/authenticated.rs | 6 +- .../websocket/connection_handler/coconut.rs | 55 +- validator-api/Cargo.toml | 2 + validator-api/src/coconut/client.rs | 8 +- validator-api/src/coconut/comm.rs | 29 + validator-api/src/coconut/error.rs | 13 +- validator-api/src/coconut/mod.rs | 112 ++-- validator-api/src/coconut/tests.rs | 525 ++++++++++++++++-- validator-api/src/main.rs | 4 +- validator-api/src/nymd_client.rs | 40 +- .../validator-api-requests/src/coconut.rs | 28 - 53 files changed, 1958 insertions(+), 558 deletions(-) create mode 100644 common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs create mode 100644 common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs create mode 100644 common/nymcoconut/src/tests/helpers.rs create mode 100644 contracts/coconut-bandwidth/src/queries.rs create mode 100644 contracts/coconut-bandwidth/src/storage.rs delete mode 100644 contracts/coconut-bandwidth/src/support/tests.rs create mode 100644 contracts/coconut-bandwidth/src/support/tests/fixtures.rs create mode 100644 contracts/coconut-bandwidth/src/support/tests/helpers.rs create mode 100644 contracts/coconut-bandwidth/src/support/tests/mod.rs create mode 100644 contracts/coconut-test/Cargo.toml create mode 100644 contracts/coconut-test/src/deposit_and_release.rs create mode 100644 contracts/coconut-test/src/helpers.rs create mode 100644 contracts/coconut-test/src/spend_credential_creates_proposal.rs create mode 100644 contracts/coconut-test/src/tests.rs create mode 100644 validator-api/src/coconut/comm.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf11cf19f..191b28da4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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 @@ -49,6 +51,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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]) [#1249]: https://github.com/nymtech/nym/pull/1249 [#1256]: https://github.com/nymtech/nym/pull/1256 @@ -70,6 +73,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#1404]: https://github.com/nymtech/nym/pull/1404 [#1419]: https://github.com/nymtech/nym/pull/1419 [#1427]: https://github.com/nymtech/nym/pull/1427 +[#1457]: https://github.com/nymtech/nym/pull/1457 ## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11) diff --git a/Cargo.lock b/Cargo.lock index bd88b49e4d..8f89dcb1ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -644,6 +644,7 @@ name = "coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "multisig-contract-common", "schemars", "serde", ] @@ -3298,6 +3299,8 @@ dependencies = [ "credential-storage", "credentials", "crypto", + "cw-utils", + "cw3", "dirs", "dotenv", "futures", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 45f2ec5a91..71129dc50c 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -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, CosmosAddressResponse, - ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_api_requests::models::{ @@ -747,14 +746,4 @@ impl ApiClient { .verify_bandwidth_credential(request_body) .await?) } - - pub async fn propose_release_funds( - &self, - request_body: &ProposeReleaseFundsRequestBody, - ) -> Result { - Ok(self - .validator_api - .propose_release_funds(request_body) - .await?) - } } diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs new file mode 100644 index 0000000000..b2e38e12a5 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_query_client.rs @@ -0,0 +1,33 @@ +// Copyright 2022 - Nym Technologies SA +// 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; +} + +#[async_trait] +impl CoconutBandwidthQueryClient for NymdClient { + async fn get_spent_credential( + &self, + blinded_serial_number: String, + ) -> Result { + let request = QueryMsg::GetSpentCredential { + blinded_serial_number, + }; + self.client + .query_contract_smart(self.coconut_bandwidth_contract_address(), &request) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs index 352ee6df26..0fc0d0e126 100644 --- a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs @@ -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, ) -> Result; + async fn spend_credential( + &self, + funds: Coin, + blinded_serial_number: String, + gateway_cosmos_address: String, + fee: Option, + ) -> Result; } #[async_trait] @@ -46,4 +54,30 @@ impl CoconutBandwidthSigningClient for N ) .await } + async fn spend_credential( + &self, + funds: Coin, + blinded_serial_number: String, + gateway_cosmos_address: String, + fee: Option, + ) -> Result { + 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 + } } diff --git a/common/client-libs/validator-client/src/nymd/traits/mod.rs b/common/client-libs/validator-client/src/nymd/traits/mod.rs index 9198522d2d..f1cda7066b 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mod.rs +++ b/common/client-libs/validator-client/src/nymd/traits/mod.rs @@ -1,14 +1,16 @@ // Copyright 2021 - Nym Technologies SA // 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; diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs index 5869ada26a..9166897b18 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs @@ -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; } #[async_trait] -impl QueryClient for NymdClient { +impl MultisigQueryClient for NymdClient { async fn get_proposal(&self, proposal_id: u64) -> Result { let request = QueryMsg::Proposal { proposal_id }; self.client diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index f326904fb4..1fa3a50862 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -8,8 +8,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use url::Url; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, - ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_api_requests::models::{ @@ -405,23 +404,6 @@ impl Client { ) .await } - - pub async fn propose_release_funds( - &self, - request_body: &ProposeReleaseFundsRequestBody, - ) -> Result { - self.post_validator_api( - &[ - routes::API_VERSION, - routes::COCONUT_ROUTES, - routes::BANDWIDTH, - routes::COCONUT_PROPOSE_RELEASE_FUNDS, - ], - NO_PARAMS, - request_body, - ) - .await - } } // utility function that should solve the double slash problem in validator API forever. diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index a0cd6011b9..8f1a125c80 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -19,7 +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 STATUS_ROUTES: &str = "status"; pub const MIXNODE: &str = "mixnode"; diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml index 12b802d3b8..0d4cf1ef86 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml @@ -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" } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/lib.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/lib.rs index b9afd74b5e..34b5556745 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/lib.rs @@ -1,3 +1,4 @@ pub mod deposit; pub mod events; pub mod msg; +pub mod spend_credential; diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs index c3e8003894..ea2c1d18b9 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs @@ -5,7 +5,7 @@ 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 { @@ -17,12 +17,21 @@ pub struct InstantiateMsg { #[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, + start_after: Option, + }, +} #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs new file mode 100644 index 0000000000..8aef32f1a1 --- /dev/null +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs @@ -0,0 +1,148 @@ +// Copyright 2022 - Nym Technologies SA +// 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, + pub per_page: usize, + pub start_next_after: Option, +} + +impl PagedSpendCredentialResponse { + pub fn new( + spend_credentials: Vec, + per_page: usize, + start_next_after: Option, + ) -> Self { + PagedSpendCredentialResponse { + spend_credentials, + per_page, + start_next_after, + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct SpendCredentialResponse { + pub spend_credential: Option, +} + +impl SpendCredentialResponse { + pub fn new(spend_credential: Option) -> Self { + SpendCredentialResponse { spend_credential } + } +} + +pub fn to_cosmos_msg( + funds: Coin, + blinded_serial_number: String, + coconut_bandwidth_addr: String, + multisig_addr: String, +) -> StdResult { + 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) -> Option { + if let Some(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: _, + msg, + funds: _, + })) = msgs.get(0) + { + if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::(msg) { + return Some(funds); + } + } + None +} diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs index 6a677f9c24..e20464bcfc 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs @@ -77,3 +77,8 @@ pub enum QueryMsg { limit: Option, }, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg { + pub coconut_bandwidth_address: String, +} diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index 936b27005f..ab1cab7cf7 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -34,8 +34,7 @@ mod error; mod impls; mod proofs; mod scheme; -#[cfg(test)] -mod tests; +pub mod tests; mod traits; mod utils; diff --git a/common/nymcoconut/src/tests/e2e.rs b/common/nymcoconut/src/tests/e2e.rs index 39c97184d7..ce4fe2ab57 100644 --- a/common/nymcoconut/src/tests/e2e.rs +++ b/common/nymcoconut/src/tests/e2e.rs @@ -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(¶ms, &private_attributes, &public_attributes)?; // generate_keys let coconut_keypairs = ttp_keygen(¶ms, 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( - ¶ms, - &keypair.secret_key(), - &blind_sign_request, - &public_attributes, - )?; - blinded_signatures.push(blinded_signature) - } - - // Unblind - let unblinded_signatures: Vec = - izip!(blinded_signatures.iter(), verification_keys.iter()) - .map(|(s, vk)| { - s.unblind( - ¶ms, - vk, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - &commitments_openings, - ) - .unwrap() - }) - .collect(); - - // Aggregate signatures - let signature_shares: Vec = 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(¶ms, &verification_key, &attributes, &signature_shares)?; - // Generate cryptographic material to verify them - let theta = prove_bandwidth_credential( - ¶ms, - &verification_key, - &signature, - serial_number, - binding_number, - )?; + let theta = theta_from_keys_and_attributes(¶ms, &coconut_keypairs, &public_attributes)?; // Verify credentials assert!(verify_credential( diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs new file mode 100644 index 0000000000..7d9c7098b9 --- /dev/null +++ b/common/nymcoconut/src/tests/helpers.rs @@ -0,0 +1,87 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::*; +use itertools::izip; + +pub fn theta_from_keys_and_attributes( + params: &Parameters, + coconut_keypairs: &Vec, + public_attributes: &Vec, +) -> Result { + 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 = coconut_keypairs + .iter() + .map(|keypair| keypair.verification_key()) + .collect(); + + // aggregate verification keys + let indices: Vec = 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 = + 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 = 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) +} diff --git a/common/nymcoconut/src/tests/mod.rs b/common/nymcoconut/src/tests/mod.rs index b6ae44f843..c13b7a3b20 100644 --- a/common/nymcoconut/src/tests/mod.rs +++ b/common/nymcoconut/src/tests/mod.rs @@ -1 +1,3 @@ +#[cfg(test)] mod e2e; +pub mod helpers; diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 406c2b6814..9317ca8108 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -225,8 +225,8 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-controllers", - "cw-multi-test", "cw-storage-plus", + "multisig-contract-common", "schemars", "serde", "thiserror", @@ -237,10 +237,33 @@ 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", + "config", + "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" @@ -521,6 +544,7 @@ dependencies = [ "cw4", "cw4-group", "multisig-contract-common", + "network-defaults", "schemars", "serde", "thiserror", diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 6d5188e04e..ccf7857d21 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -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 diff --git a/contracts/coconut-bandwidth/Cargo.toml b/contracts/coconut-bandwidth/Cargo.toml index 56ae373c2b..c8c76fda46 100644 --- a/contracts/coconut-bandwidth/Cargo.toml +++ b/contracts/coconut-bandwidth/Cargo.toml @@ -11,6 +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" } +multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } config = { path = "../../common/config"} cosmwasm-std = "1.0.0" @@ -21,6 +22,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" } diff --git a/contracts/coconut-bandwidth/src/contract.rs b/contracts/coconut-bandwidth/src/contract.rs index 791bfc56de..1226417c71 100644 --- a/contracts/coconut-bandwidth/src/contract.rs +++ b/contracts/coconut-bandwidth/src/contract.rs @@ -1,11 +1,14 @@ // Copyright 2022 - Nym Technologies SA // 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; @@ -44,13 +47,23 @@ pub fn execute( ) -> Result { 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 { - unimplemented!(); +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + 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] @@ -62,11 +75,9 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result +// 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, + limit: Option, +) -> StdResult { + 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::>>()?; + + 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 { + 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()); + } +} diff --git a/contracts/coconut-bandwidth/src/storage.rs b/contracts/coconut-bandwidth/src/storage.rs new file mode 100644 index 0000000000..f3515f92e1 --- /dev/null +++ b/contracts/coconut-bandwidth/src/storage.rs @@ -0,0 +1,106 @@ +// Copyright 2022 - Nym Technologies SA +// 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 for SpendCredentialIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = 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 coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus; + use config::defaults::MIX_DENOM; + 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, MIX_DENOM.base); + 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); + } +} diff --git a/contracts/coconut-bandwidth/src/support/mod.rs b/contracts/coconut-bandwidth/src/support/mod.rs index 3e1ec563d5..2f17cb1c46 100644 --- a/contracts/coconut-bandwidth/src/support/mod.rs +++ b/contracts/coconut-bandwidth/src/support/mod.rs @@ -1,3 +1,5 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] pub mod tests; diff --git a/contracts/coconut-bandwidth/src/support/tests.rs b/contracts/coconut-bandwidth/src/support/tests.rs deleted file mode 100644 index 99bfba148d..0000000000 --- a/contracts/coconut-bandwidth/src/support/tests.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// 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> { - 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> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ); - Box::new(contract) - } -} diff --git a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs new file mode 100644 index 0000000000..e6b0e64249 --- /dev/null +++ b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs @@ -0,0 +1,22 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData}; +use config::defaults::MIX_DENOM; +use cosmwasm_std::{Addr, Coin}; + +pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential { + SpendCredential::new( + Coin::new(100, MIX_DENOM.base), + 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, MIX_DENOM.base), + blinded_serial_number.to_string(), + "gateway_owner_addr".to_string(), + ) +} diff --git a/contracts/coconut-bandwidth/src/support/tests/helpers.rs b/contracts/coconut-bandwidth/src/support/tests/helpers.rs new file mode 100644 index 0000000000..60eb675a2d --- /dev/null +++ b/contracts/coconut-bandwidth/src/support/tests/helpers.rs @@ -0,0 +1,22 @@ +// Copyright 2021 - Nym Technologies SA +// 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}; + +pub fn init_contract() -> OwnedDeps> { + 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 +} diff --git a/contracts/coconut-bandwidth/src/support/tests/mod.rs b/contracts/coconut-bandwidth/src/support/tests/mod.rs new file mode 100644 index 0000000000..1e96465ed5 --- /dev/null +++ b/contracts/coconut-bandwidth/src/support/tests/mod.rs @@ -0,0 +1,5 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod fixtures; +pub mod helpers; diff --git a/contracts/coconut-bandwidth/src/transactions.rs b/contracts/coconut-bandwidth/src/transactions.rs index 46941bcc14..9b5cf8f35a 100644 --- a/contracts/coconut-bandwidth/src/transactions.rs +++ b/contracts/coconut-bandwidth/src/transactions.rs @@ -1,10 +1,14 @@ // Copyright 2021 - Nym Technologies SA // 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::{ @@ -39,6 +43,41 @@ 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 { + if data.funds().denom != MIX_DENOM.base { + return Err(ContractError::WrongDenom); + } + 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, @@ -70,11 +109,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() { @@ -226,4 +267,91 @@ 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); + + let invalid_data = SpendCredentialData::new( + Coin::new(1, MIX_DENOM.base), + 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 + ); + } } diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml new file mode 100644 index 0000000000..ec13753706 --- /dev/null +++ b/contracts/coconut-test/Cargo.toml @@ -0,0 +1,31 @@ +[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" } +config = { path = "../../common/config"} + +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" diff --git a/contracts/coconut-test/src/deposit_and_release.rs b/contracts/coconut-test/src/deposit_and_release.rs new file mode 100644 index 0000000000..a0ff781459 --- /dev/null +++ b/contracts/coconut-test/src/deposit_and_release.rs @@ -0,0 +1,99 @@ +// Copyright 2022 - Nym Technologies SA +// 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 config::defaults::MIX_DENOM; +use cosmwasm_std::{coins, Addr}; +use cw_controllers::AdminError; +use cw_multi_test::Executor; + +#[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 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(), + }; + 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, MIX_DENOM.base).unwrap(); + assert_eq!(pool_bal, deposit_funds[0]); +} diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs new file mode 100644 index 0000000000..2a8c0b24ed --- /dev/null +++ b/contracts/coconut-test/src/helpers.rs @@ -0,0 +1,64 @@ +// Copyright 2022 - Nym Technologies SA +// 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 { + 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> { + let contract = ContractWrapper::new( + coconut_bandwidth::contract::execute, + coconut_bandwidth::contract::instantiate, + coconut_bandwidth::contract::query, + ); + Box::new(contract) +} + +pub fn contract_multisig() -> Box> { + 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> { + let contract = ContractWrapper::new( + cw4_group::contract::execute, + cw4_group::contract::instantiate, + cw4_group::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/coconut-test/src/spend_credential_creates_proposal.rs b/contracts/coconut-test/src/spend_credential_creates_proposal.rs new file mode 100644 index 0000000000..c47a8f0868 --- /dev/null +++ b/contracts/coconut-test/src/spend_credential_creates_proposal.rs @@ -0,0 +1,155 @@ +use crate::helpers::*; +use coconut_bandwidth::error::ContractError; +use coconut_bandwidth_contract_common::{ + msg::{ + ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg, + }, + spend_credential::SpendCredentialData, +}; +use config::defaults::MIX_DENOM; +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; + +#[test] +fn spend_credential_creates_proposal() { + let init_funds = coins(10, MIX_DENOM.base); + 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), + }; + 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, + }; + 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, MIX_DENOM.base), + 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::() + .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, MIX_DENOM.base), + 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::() + .unwrap(); + assert_eq!(2, proposal_id); +} diff --git a/contracts/coconut-test/src/tests.rs b/contracts/coconut-test/src/tests.rs new file mode 100644 index 0000000000..575a2c2ac1 --- /dev/null +++ b/contracts/coconut-test/src/tests.rs @@ -0,0 +1,6 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +mod deposit_and_release; +mod helpers; +mod spend_credential_creates_proposal; diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index bad70e9b8b..ddf5ba146e 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -470,7 +470,6 @@ mod tests { use crate::support::tests; use crate::support::tests::test_helpers; - use super::storage; use super::*; #[cfg(test)] @@ -1062,17 +1061,13 @@ mod tests { #[cfg(test)] mod removing_mix_stake_delegation { - use crate::delegations::queries::query_mixnode_delegation; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Addr; - use cosmwasm_std::Uint128; - use crate::mixnodes::transactions::try_remove_mixnode; use crate::support::tests; - use super::storage; use super::*; // TODO: Probably delete due to reconciliation logic diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 0168eec05f..f1e76c09ac 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -29,6 +29,7 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } +network-defaults = { path = "../../../common/network-defaults" } [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 0af5001f12..3a9b7aadd0 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Binary, BlockInfo, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, + to_binary, Addr, Binary, BlockInfo, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, Response, StdResult, }; @@ -16,10 +16,11 @@ use cw3_fixed_multisig::state::{next_id, Ballot, Proposal, Votes, BALLOTS, PROPO use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff}; use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; +use network_defaults::DEFAULT_NETWORK; use crate::error::ContractError; use crate::state::{Config, CONFIG}; -use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig"; @@ -37,6 +38,13 @@ pub fn instantiate( addr: msg.group_addr.clone(), } })?); + // This might need to be changed via a migration, due to circular dependency + // of deploying the two contracts + let coconut_bandwidth_addr = Addr::unchecked( + DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .ok_or(ContractError::InvalidCoconutBandwidth {})?, + ); let total_weight = group_addr.total_weight(&deps.querier)?; msg.threshold.validate(total_weight)?; @@ -46,12 +54,21 @@ pub fn instantiate( threshold: msg.threshold, max_voting_period: msg.max_voting_period, group_addr, + coconut_bandwidth_addr, }; CONFIG.save(deps.storage, &cfg)?; Ok(Response::default()) } +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result { + 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()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -88,15 +105,12 @@ pub fn execute_propose( // only members of the multisig can create a proposal let cfg = CONFIG.load(deps.storage)?; - // Only members of the multisig can create a proposal - // Non-voting members are special - they are allowed to create a proposal and - // therefore "vote", but they aren't allowed to vote otherwise. - // Such vote is also special, because despite having 0 weight it still counts when - // counting threshold passing - let vote_power = cfg - .group_addr - .is_member(&deps.querier, &info.sender, None)? - .ok_or(ContractError::Unauthorized {})?; + // Only the coconut bandwidth contract can create proposals + if info.sender != cfg.coconut_bandwidth_addr { + return Err(ContractError::Unauthorized {}); + } + // The contract doesn't have any say in the voting outcome + let vote_power = 0; // max expires also used as default let max_expires = cfg.max_voting_period.after(&env.block); @@ -433,7 +447,9 @@ mod tests { use cw2::{query_contract_info, ContractVersion}; use cw4::{Cw4ExecuteMsg, Member}; use cw4_group::helpers::Cw4GroupContract; - use cw_multi_test::{next_block, App, AppBuilder, Contract, ContractWrapper, Executor}; + use cw_multi_test::{ + next_block, App, AppBuilder, AppResponse, Contract, ContractWrapper, Executor, + }; use cw_utils::{Duration, Threshold}; use super::*; @@ -491,6 +507,28 @@ mod tests { .unwrap() } + fn propose_and_vote( + app: &mut App, + flex_addr: Addr, + proposal: ExecuteMsg, + voter: &str, + ) -> AppResponse { + let proposer = DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .unwrap(); + let res = app + .execute_contract(Addr::unchecked(proposer), flex_addr.clone(), &proposal, &[]) + .unwrap(); + + let yes_vote = ExecuteMsg::Vote { + proposal_id: res.custom_attrs(1)[2].value.parse().unwrap(), + vote: Vote::Yes, + }; + let _ = app.execute_contract(Addr::unchecked(voter), flex_addr, &yes_vote, &[]); + + res + } + #[track_caller] fn instantiate_flex( app: &mut App, @@ -538,8 +576,12 @@ mod tests { init_funds: Vec, multisig_as_group_admin: bool, ) -> (Addr, Addr) { - // 1. Instantiate group contract with members (and OWNER as admin) + let coconut_bandwidth_contract = DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .unwrap(); + // 1. Instantiate group contract with members (and OWNER as admin) and coconut bandwidth contract let members = vec![ + member(coconut_bandwidth_contract, 0), member(OWNER, 0), member(VOTER1, 1), member(VOTER2, 2), @@ -730,41 +772,17 @@ mod tests { }; let err = app .execute_contract( - Addr::unchecked(OWNER), + Addr::unchecked( + DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .unwrap(), + ), flex_addr.clone(), &proposal_wrong_exp, &[], ) .unwrap_err(); assert_eq!(ContractError::WrongExpiration {}, err.downcast().unwrap()); - - // Proposal from voter works - let res = app - .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) - .unwrap(); - assert_eq!( - res.custom_attrs(1), - [ - ("action", "propose"), - ("sender", VOTER3), - ("proposal_id", "1"), - ("status", "Open"), - ], - ); - - // Proposal from voter with enough vote power directly passes - let res = app - .execute_contract(Addr::unchecked(VOTER4), flex_addr, &proposal, &[]) - .unwrap(); - assert_eq!( - res.custom_attrs(1), - [ - ("action", "propose"), - ("sender", VOTER4), - ("proposal_id", "2"), - ("status", "Passed"), - ], - ); } fn get_tally(app: &App, flex_addr: &str, proposal_id: u64) -> u64 { @@ -805,6 +823,56 @@ mod tests { } } + #[test] + fn test_proposer_limited_to_coconut_bandwidth() { + let init_funds = coins(10, "BTC"); + let mut app = mock_app(&init_funds); + + let voting_period = Duration::Time(2000000); + let threshold = Threshold::ThresholdQuorum { + threshold: Decimal::percent(80), + quorum: Decimal::percent(20), + }; + let (flex_addr, _) = setup_test_case(&mut app, threshold, voting_period, init_funds, false); + let proposal = pay_somebody_proposal(); + + let err = app + .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let err = app + .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let err = app + .execute_contract(Addr::unchecked(SOMEBODY), flex_addr.clone(), &proposal, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let proposer = DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .unwrap(); + let res = app + .execute_contract( + Addr::unchecked(&proposer), + flex_addr.clone(), + &proposal, + &[], + ) + .unwrap(); + assert_eq!( + res.custom_attrs(1), + [ + ("action", "propose"), + ("sender", &proposer), + ("proposal_id", "1"), + ("status", "Open"), + ], + ); + } + #[test] fn test_proposal_queries() { let init_funds = coins(10, "BTC"); @@ -819,17 +887,13 @@ mod tests { // create proposal with 1 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER1); let proposal_id1: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // another proposal immediately passes app.update_block(next_block); let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER4), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER4); let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); // expire them both @@ -837,9 +901,7 @@ mod tests { // add one more open proposal, 2 votes let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER2), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER2); let proposal_id3: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let proposed_at = app.block_info(); @@ -914,9 +976,7 @@ mod tests { // create proposal with 0 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1017,22 +1077,6 @@ mod tests { assert_eq!(ContractError::NotOpen {}, err.downcast().unwrap()); // query individual votes - // initial (with 0 weight) - let voter = OWNER.into(); - let vote: VoteResponse = app - .wrap() - .query_wasm_smart(&flex_addr, &QueryMsg::Vote { proposal_id, voter }) - .unwrap(); - assert_eq!( - vote.vote.unwrap(), - VoteInfo { - proposal_id, - voter: OWNER.into(), - vote: Vote::Yes, - weight: 0 - } - ); - // nay sayer let voter = VOTER2.into(); let vote: VoteResponse = app @@ -1059,9 +1103,7 @@ mod tests { // create proposal with 0 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1109,9 +1151,7 @@ mod tests { // create proposal with 0 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1215,9 +1255,7 @@ mod tests { // create proposal with 0 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1286,9 +1324,7 @@ mod tests { // create proposal with 0 vote power let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1338,9 +1374,7 @@ mod tests { // VOTER1 starts a proposal to send some tokens (1/4 votes) let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER1); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App, proposal_id: u64| -> Status { @@ -1400,9 +1434,7 @@ mod tests { // make a second proposal let proposal2 = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal2, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal2, VOTER1); // Get the proposal id from the logs let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1472,14 +1504,7 @@ mod tests { msgs: vec![update_msg], latest: None, }; - let res = app - .execute_contract( - Addr::unchecked(VOTER1), - flex_addr.clone(), - &update_proposal, - &[], - ) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), update_proposal, VOTER1); // Get the proposal id from the logs let update_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1488,14 +1513,7 @@ mod tests { // VOTER1 starts a proposal to send some tokens let cash_proposal = pay_somebody_proposal(); - let res = app - .execute_contract( - Addr::unchecked(VOTER1), - flex_addr.clone(), - &cash_proposal, - &[], - ) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), cash_proposal, VOTER1); // Get the proposal id from the logs let cash_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); assert_ne!(cash_proposal_id, update_proposal_id); @@ -1584,9 +1602,7 @@ mod tests { // VOTER3 starts a proposal to send some tokens (3/12 votes) let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER3); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { @@ -1628,9 +1644,7 @@ mod tests { // new proposal can be passed single-handedly by newbie let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(newbie), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, newbie); // Get the proposal id from the logs let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); @@ -1667,9 +1681,7 @@ mod tests { // VOTER3 starts a proposal to send some tokens (3 votes) let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER3); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { @@ -1737,9 +1749,7 @@ mod tests { // create proposal let proposal = pay_somebody_proposal(); - let res = app - .execute_contract(Addr::unchecked(VOTER5), flex_addr.clone(), &proposal, &[]) - .unwrap(); + let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER5); // Get the proposal id from the logs let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap(); let prop_status = |app: &App| -> Status { diff --git a/contracts/multisig/cw3-flex-multisig/src/error.rs b/contracts/multisig/cw3-flex-multisig/src/error.rs index 4936a8923f..f7addae37b 100644 --- a/contracts/multisig/cw3-flex-multisig/src/error.rs +++ b/contracts/multisig/cw3-flex-multisig/src/error.rs @@ -14,6 +14,9 @@ pub enum ContractError { #[error("Group contract invalid address '{addr}'")] InvalidGroup { addr: String }, + #[error("Coconut bandwidth contract address not found")] + InvalidCoconutBandwidth {}, + #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/multisig/cw3-flex-multisig/src/state.rs b/contracts/multisig/cw3-flex-multisig/src/state.rs index 023662b8bc..96e6146c2f 100644 --- a/contracts/multisig/cw3-flex-multisig/src/state.rs +++ b/contracts/multisig/cw3-flex-multisig/src/state.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -11,6 +12,7 @@ pub struct Config { pub max_voting_period: Duration, // Total weight and voters are queried from this contract pub group_addr: Cw4Contract, + pub coconut_bandwidth_addr: Addr, } // unique items diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index b372cbcc94..cf11305bab 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -64,7 +64,7 @@ pub(crate) enum RequestHandlingError { EthContractError(#[from] web3::contract::Error), #[cfg(feature = "coconut")] - #[error("Validator API error")] + #[error("Validator API error - {0}")] APIError(#[from] validator_client::ValidatorClientError), #[cfg(feature = "coconut")] @@ -72,8 +72,8 @@ pub(crate) enum RequestHandlingError { NotEnoughValidatorAPIs { received: usize, needed: usize }, #[cfg(feature = "coconut")] - #[error("Validator API {url} misbehaved in the bandwidth redemption protocol: {reason}")] - MisbehavingAPI { url: String, reason: String }, + #[error("There was a problem with the proposal id: {reason}")] + ProposalIdError { reason: String }, #[cfg(feature = "coconut")] #[error("Coconut interface error - {0}")] diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 48106eda07..04f6fa4ac5 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -9,7 +9,8 @@ use coconut_interface::{Credential, VerificationKey}; use network_defaults::MIX_DENOM; use validator_client::{ nymd::{ - traits::{MultisigSigningClient, QueryClient}, + cosmwasm_client::logs::find_attribute, + traits::{CoconutBandwidthSigningClient, MultisigQueryClient, MultisigSigningClient}, Coin, Fee, NymdClient, SigningNymdClient, }, ApiClient, @@ -54,45 +55,29 @@ impl CoconutVerifier { // isn't enough let revoke_fee = Some(Fee::Auto(Some(1.5))); - let first_api_client = self - .api_clients - .get(0) - .expect("This shouldn't happen, as we check for length in constructor"); - - let first_api_cosmos_addr = first_api_client.get_cosmos_address().await?.addr; - self.nymd_client - .grant_allowance( - &first_api_cosmos_addr, - vec![Coin::new(MAX_FEEGRANT_UNYM, MIX_DENOM.base)], - SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)), - // It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable - vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")], - "Create allowance to propose the release of funds".to_string(), + let res = self + .nymd_client + .spend_credential( + Coin::new(credential.voucher_value().into(), MIX_DENOM.base), + credential.blinded_serial_number(), + self.nymd_client.address().to_string(), None, ) .await?; - - let req = validator_api_requests::coconut::ProposeReleaseFundsRequestBody::new( - credential.clone(), - self.nymd_client.address().clone(), - ); - let ret = first_api_client.propose_release_funds(&req).await; - - self.nymd_client - .revoke_allowance( - &first_api_cosmos_addr, - "Cleanup the previous allowance for releasing funds".to_string(), - revoke_fee.clone(), - ) - .await?; - - let proposal_id = ret?.proposal_id; + let proposal_id = find_attribute(&res.logs, "wasm", "proposal_id") + .ok_or(RequestHandlingError::ProposalIdError { + reason: String::from("proposal id not found"), + })? + .value + .parse::() + .map_err(|_| RequestHandlingError::ProposalIdError { + reason: String::from("proposal id could not be parsed to u64"), + })?; let proposal = self.nymd_client.get_proposal(proposal_id).await?; if !credential.has_blinded_serial_number(&proposal.description)? { - return Err(RequestHandlingError::MisbehavingAPI { - url: first_api_client.validator_api.current_url().to_string(), - reason: String::from("Created proposal with different serial number"), + return Err(RequestHandlingError::ProposalIdError { + reason: String::from("proposal has different serial number"), }); } @@ -101,7 +86,7 @@ impl CoconutVerifier { proposal_id, self.nymd_client.address().clone(), ); - for client in self.api_clients.iter().skip(1) { + for client in self.api_clients.iter() { let api_cosmos_addr = client.get_cosmos_address().await?.addr; self.nymd_client .grant_allowance( diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index e4cb7fc8e4..f1277fd6d1 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -82,3 +82,5 @@ vergen = { version = "5", default-features = false, features = ["build", "git", [dev-dependencies] attohttpc = {version = "0.18.0", features = ["json"]} nymcoconut = { path = "../common/nymcoconut" } +cw3 = "0.13.2" +cw-utils = "0.13.2" diff --git a/validator-api/src/coconut/client.rs b/validator-api/src/coconut/client.rs index 612efc20c1..626a4b775b 100644 --- a/validator-api/src/coconut/client.rs +++ b/validator-api/src/coconut/client.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; +use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use multisig_contract_common::msg::ProposalResponse; use validator_client::nymd::{AccountId, Fee, TxResponse}; @@ -10,13 +11,10 @@ pub trait Client { async fn address(&self) -> AccountId; async fn get_tx(&self, tx_hash: &str) -> Result; async fn get_proposal(&self, proposal_id: u64) -> Result; - async fn propose_release_funds( + async fn get_spent_credential( &self, - title: String, blinded_serial_number: String, - voucher_value: u128, - fee: Option, - ) -> Result; + ) -> Result; async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) -> Result<()>; } diff --git a/validator-api/src/coconut/comm.rs b/validator-api/src/coconut/comm.rs new file mode 100644 index 0000000000..e6f7b8a1ac --- /dev/null +++ b/validator-api/src/coconut/comm.rs @@ -0,0 +1,29 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::error::Result; +use coconut_interface::VerificationKey; +use credentials::obtain_aggregate_verification_key; +use url::Url; + +#[async_trait] +pub trait APICommunicationChannel { + async fn aggregated_verification_key(&self) -> Result; +} + +pub struct QueryCommunicationChannel { + validator_apis: Vec, +} + +impl QueryCommunicationChannel { + pub fn new(validator_apis: Vec) -> Self { + QueryCommunicationChannel { validator_apis } + } +} + +#[async_trait] +impl APICommunicationChannel for QueryCommunicationChannel { + async fn aggregated_verification_key(&self) -> Result { + Ok(obtain_aggregate_verification_key(&self.validator_apis).await?) + } +} diff --git a/validator-api/src/coconut/error.rs b/validator-api/src/coconut/error.rs index 78f9b08e43..a1915dac17 100644 --- a/validator-api/src/coconut/error.rs +++ b/validator-api/src/coconut/error.rs @@ -63,22 +63,17 @@ pub enum CoconutError { #[error("Error in coconut interface - {0}")] CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), - #[error("Could not create proposal for spending credential")] - CreateProposalError, - #[error("Storage error - {0}")] StorageError(#[from] ValidatorApiStorageError), #[error("Credentials error - {0}")] CredentialsError(#[from] credentials::error::Error), - #[error( - "Incorrect credential proposal description. Expected blinded serial number in base 58" - )] - IncorrectProposal, + #[error("Incorrect credential proposal description: {reason}")] + IncorrectProposal { reason: String }, - #[error("Internal error: {0}")] - InternalError(String), + #[error("Invalid status of credential: {status}")] + InvalidCredentialStatus { status: String }, } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index 9a9a0d4499..2f8105b2a9 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod client; +pub(crate) mod comm; mod deposit; pub(crate) mod error; #[cfg(test)] @@ -12,23 +13,24 @@ use crate::coconut::deposit::extract_encryption_key; use crate::coconut::error::{CoconutError, Result}; use crate::ValidatorApiStorage; +use coconut_bandwidth_contract_common::spend_credential::{ + funds_from_cosmos_msgs, SpendCredentialStatus, +}; use coconut_interface::{ Attribute, BlindSignRequest, BlindedSignature, KeyPair, Parameters, VerificationKey, }; -use config::defaults::VALIDATOR_API_VERSION; +use config::defaults::{MIX_DENOM, VALIDATOR_API_VERSION}; use credentials::coconut::params::{ ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, }; -use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; use crypto::symmetric::stream_cipher; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, - ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, }; -use validator_client::nymd::Fee; +use validator_client::nymd::{Coin, Fee}; use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; use getset::{CopyGetters, Getters}; @@ -38,32 +40,35 @@ use rocket::serde::json::Json; use rocket::State as RocketState; use std::sync::Arc; use tokio::sync::Mutex; -use url::Url; + +use self::comm::APICommunicationChannel; pub struct State { client: Arc, key_pair: KeyPair, - validator_apis: Vec, + comm_channel: Arc, storage: ValidatorApiStorage, rng: Arc>, } impl State { - pub(crate) fn new( + pub(crate) fn new( client: C, key_pair: KeyPair, - validator_apis: Vec, + comm_channel: D, storage: ValidatorApiStorage, ) -> Self where C: LocalClient + Send + Sync + 'static, + D: APICommunicationChannel + Send + Sync + 'static, { let client = Arc::new(client); + let comm_channel = Arc::new(comm_channel); let rng = Arc::new(Mutex::new(OsRng)); Self { client, key_pair, - validator_apis, + comm_channel, storage, rng, } @@ -125,7 +130,7 @@ impl State { } pub async fn verification_key(&self) -> Result { - Ok(obtain_aggregate_verification_key(&self.validator_apis).await?) + self.comm_channel.aggregated_verification_key().await } } @@ -153,16 +158,17 @@ impl InternalSignRequest { } } - pub fn stage( + pub fn stage( client: C, key_pair: KeyPair, - validator_apis: Vec, + comm_channel: D, storage: ValidatorApiStorage, ) -> AdHoc where C: LocalClient + Send + Sync + 'static, + D: APICommunicationChannel + Send + Sync + 'static, { - let state = State::new(client, key_pair, validator_apis, storage); + let state = State::new(client, key_pair, comm_channel, storage); AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... @@ -175,8 +181,7 @@ impl InternalSignRequest { get_verification_key, get_cosmos_address, post_partial_bandwidth_credential, - verify_bandwidth_credential, - post_propose_release_funds + verify_bandwidth_credential ], ) }) @@ -263,69 +268,60 @@ pub async fn verify_bandwidth_credential( verify_credential_body: Json, state: &RocketState, ) -> Result> { - let proposal_id = *verify_credential_body.0.proposal_id(); + let proposal_id = *verify_credential_body.proposal_id(); let proposal = state.client.get_proposal(proposal_id).await?; // Proposal description is the blinded serial number if !verify_credential_body - .0 .credential() .has_blinded_serial_number(&proposal.description)? { - return Err(CoconutError::IncorrectProposal); + return Err(CoconutError::IncorrectProposal { + reason: String::from("incorrect blinded serial number in description"), + }); + } + let proposed_release_funds = + funds_from_cosmos_msgs(proposal.msgs).ok_or(CoconutError::IncorrectProposal { + reason: String::from("action is not to release funds"), + })?; + // Credential has not been spent before, and is on its way of being spent + let credential_status = state + .client + .get_spent_credential(verify_credential_body.credential().blinded_serial_number()) + .await? + .spend_credential + .ok_or(CoconutError::InvalidCredentialStatus { + status: String::from("Inexistent"), + })? + .status(); + if credential_status != SpendCredentialStatus::InProgress { + return Err(CoconutError::InvalidCredentialStatus { + status: format!("{:?}", credential_status), + }); } let verification_key = state.verification_key().await?; - let verification_result = verify_credential_body - .0 + let mut vote_yes = verify_credential_body .credential() .verify(&verification_key); + vote_yes &= Coin::from(proposed_release_funds) + == Coin::new( + verify_credential_body.credential().voucher_value() as u128, + MIX_DENOM.base, + ); + // Vote yes or no on the proposal based on the verification result state .client .vote_proposal( proposal_id, - verification_result, + vote_yes, Some(Fee::new_payer_granter_auto( None, None, - Some(verify_credential_body.0.gateway_cosmos_addr().to_owned()), + Some(verify_credential_body.gateway_cosmos_addr().to_owned()), )), ) .await?; - Ok(Json(VerifyCredentialResponse::new(verification_result))) -} - -#[post("/propose-release-funds", data = "")] -pub async fn post_propose_release_funds( - propose_release_funds: Json, - state: &RocketState, -) -> Result> { - let verification_key = state.verification_key().await?; - if !propose_release_funds - .0 - .credential() - .verify(&verification_key) - { - return Err(CoconutError::CreateProposalError); - } - - let title = String::from("Create proposal to spend a coconut credential"); - let blinded_serial_number = propose_release_funds.0.credential().blinded_serial_number(); - let voucher_value = propose_release_funds.0.credential().voucher_value() as u128; - let proposal_id = state - .client - .propose_release_funds( - title, - blinded_serial_number, - voucher_value, - Some(Fee::new_payer_granter_auto( - None, - None, - Some(propose_release_funds.0.gateway_cosmos_addr().to_owned()), - )), - ) - .await?; - - Ok(Json(ProposeReleaseFundsResponse::new(proposal_id))) + Ok(Json(VerifyCredentialResponse::new(vote_yes))) } diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index 1fd66b9160..bba98e871d 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -7,7 +7,12 @@ use coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; -use config::defaults::VOUCHER_INFO; +use coconut_bandwidth_contract_common::spend_credential::{ + SpendCredential, SpendCredentialResponse, +}; +use coconut_interface::{hash_to_scalar, Credential, VerificationKey}; +use config::defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO}; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; use credentials::coconut::bandwidth::BandwidthVoucher; use credentials::coconut::params::{ ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, @@ -15,16 +20,20 @@ use credentials::coconut::params::{ use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; use multisig_contract_common::msg::ProposalResponse; +use nymcoconut::tests::helpers::theta_from_keys_and_attributes; use nymcoconut::{ prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, }; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse, + BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, + VerifyCredentialBody, VerifyCredentialResponse, }; +use validator_client::nymd::Coin; use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; use validator_client::validator_api::routes::{ - API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, - COCONUT_ROUTES, COCONUT_VERIFICATION_KEY, + API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_COSMOS_ADDRESS, + COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, COCONUT_ROUTES, COCONUT_VERIFICATION_KEY, + COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, }; use crate::coconut::State; @@ -38,25 +47,41 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, RwLock}; +#[derive(Clone, Debug)] struct DummyClient { - db: Arc>>, + validator_address: AccountId, + tx_db: Arc>>, + proposal_db: Arc>>, + spent_credential_db: Arc>>, } impl DummyClient { - pub fn new(db: &Arc>>) -> Self { - let db = Arc::clone(db); - Self { db } + pub fn new( + validator_address: AccountId, + tx_db: &Arc>>, + proposal_db: &Arc>>, + spent_credential_db: &Arc>>, + ) -> Self { + let tx_db = Arc::clone(tx_db); + let proposal_db = Arc::clone(proposal_db); + let spent_credential_db = Arc::clone(spent_credential_db); + Self { + validator_address, + tx_db, + proposal_db, + spent_credential_db, + } } } #[async_trait] impl super::client::Client for DummyClient { async fn address(&self) -> AccountId { - todo!() + self.validator_address.clone() } async fn get_tx(&self, tx_hash: &str) -> Result { - self.db + self.tx_db .read() .unwrap() .get(tx_hash) @@ -64,27 +89,65 @@ impl super::client::Client for DummyClient { .ok_or(CoconutError::TxHashParseError) } - async fn get_proposal(&self, _proposal_id: u64) -> Result { - todo!() + async fn get_proposal(&self, proposal_id: u64) -> Result { + self.proposal_db + .read() + .unwrap() + .get(&proposal_id) + .cloned() + .ok_or(CoconutError::IncorrectProposal { + reason: String::from("proposal not found"), + }) } - async fn propose_release_funds( + async fn get_spent_credential( &self, - _title: String, - _blinded_serial_number: String, - _voucher_value: u128, - _fee: Option, - ) -> Result { - todo!() + blinded_serial_number: String, + ) -> Result { + self.spent_credential_db + .read() + .unwrap() + .get(&blinded_serial_number) + .cloned() + .ok_or(CoconutError::InvalidCredentialStatus { + status: String::from("spent credential not found"), + }) } async fn vote_proposal( &self, - _proposal_id: u64, - _vote_yes: bool, + proposal_id: u64, + vote_yes: bool, _fee: Option, ) -> Result<()> { - todo!() + if let Some(proposal) = self.proposal_db.write().unwrap().get_mut(&proposal_id) { + if vote_yes { + proposal.status = cw3::Status::Passed; + } else { + proposal.status = cw3::Status::Rejected; + } + } + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct DummyCommunicationChannel { + aggregated_verification_key: VerificationKey, +} + +impl DummyCommunicationChannel { + pub fn new(aggregated_verification_key: VerificationKey) -> Self { + DummyCommunicationChannel { + aggregated_verification_key, + } + } +} + +#[async_trait] +impl super::comm::APICommunicationChannel for DummyCommunicationChannel { + async fn aggregated_verification_key(&self) -> Result { + Ok(self.aggregated_verification_key.clone()) } } @@ -114,13 +177,18 @@ async fn check_signer_verif_key(key_pair: KeyPair) { let mut db_dir = std::env::temp_dir(); db_dir.push(&verification_key.to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_db = Arc::new(RwLock::new(HashMap::new())); - let nymd_client = DummyClient::new(&nymd_db); + let nymd_client = DummyClient::new( + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, - vec![], + comm_channel, storage, )); @@ -197,17 +265,23 @@ async fn signed_before() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_db = Arc::new(RwLock::new(HashMap::new())); - nymd_db + let tx_db = Arc::new(RwLock::new(HashMap::new())); + tx_db .write() .unwrap() .insert(tx_hash.to_string(), tx_entry.clone()); - let nymd_client = DummyClient::new(&nymd_db); + let nymd_client = DummyClient::new( + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + &tx_db, + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, - vec![], + comm_channel, storage.clone(), )); let client = Client::tracked(rocket) @@ -260,14 +334,19 @@ async fn signed_before() { #[tokio::test] async fn state_functions() { - let nymd_db = Arc::new(RwLock::new(HashMap::new())); - let nymd_client = DummyClient::new(&nymd_db); + let nymd_client = DummyClient::new( + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); let params = Parameters::new(4).unwrap(); let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let state = State::new(nymd_client, key_pair, vec![], storage.clone()); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let state = State::new(nymd_client, key_pair, comm_channel, storage.clone()); let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E"); assert!(state.signed_before(&tx_hash).await.unwrap().is_none()); @@ -391,7 +470,7 @@ async fn blind_sign_correct() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_db = Arc::new(RwLock::new(HashMap::new())); + let tx_db = Arc::new(RwLock::new(HashMap::new())); let mut tx_entry = tx_entry_fixture(&tx_hash.to_string()); tx_entry.tx_result.events.push(Event { @@ -420,16 +499,22 @@ async fn blind_sign_correct() { .unwrap(), }, ]; - nymd_db + tx_db .write() .unwrap() .insert(tx_hash.to_string(), tx_entry.clone()); - let nymd_client = DummyClient::new(&nymd_db); + let nymd_client = DummyClient::new( + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + &tx_db, + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, - vec![], + comm_channel, storage.clone(), )); let client = Client::tracked(rocket) @@ -496,13 +581,18 @@ async fn signature_test() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_db = Arc::new(RwLock::new(HashMap::new())); - let nymd_client = DummyClient::new(&nymd_db); + let nymd_client = DummyClient::new( + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, - vec![], + comm_channel, storage.clone(), )); let client = Client::tracked(rocket) @@ -552,3 +642,360 @@ async fn signature_test() { expected_response.to_bytes() ); } + +#[tokio::test] +async fn get_cosmos_address() { + let validator_address = + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(); + let nymd_client = DummyClient::new( + validator_address.clone(), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + &Arc::new(RwLock::new(HashMap::new())), + ); + let mut db_dir = std::env::temp_dir(); + let key_pair = ttp_keygen(&Parameters::new(4).unwrap(), 1, 1) + .unwrap() + .remove(0); + db_dir.push(&key_pair.verification_key().to_bs58()[..8]); + let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let rocket = rocket::build().attach(InternalSignRequest::stage( + nymd_client, + key_pair, + comm_channel, + storage.clone(), + )); + let client = Client::tracked(rocket) + .await + .expect("valid rocket instance"); + + let response = client + .get(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_COSMOS_ADDRESS + )) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let cosmos_addr_response = + serde_json::from_str::(&response.into_string().await.unwrap()) + .unwrap(); + assert_eq!(validator_address, cosmos_addr_response.addr); +} + +#[tokio::test] +async fn verification_of_bandwidth_credential() { + // Setup variables + let validator_address = + AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(); + let proposal_db = Arc::new(RwLock::new(HashMap::new())); + let spent_credential_db = Arc::new(RwLock::new(HashMap::new())); + let nymd_client = DummyClient::new( + validator_address.clone(), + &Arc::new(RwLock::new(HashMap::new())), + &proposal_db, + &spent_credential_db, + ); + let mut db_dir = std::env::temp_dir(); + let params = Parameters::new(4).unwrap(); + let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap(); + let voucher_value = 1234u64; + let voucher_info = "voucher info"; + let public_attributes = vec![ + hash_to_scalar(voucher_value.to_string()), + hash_to_scalar(voucher_info.to_string()), + ]; + let theta = theta_from_keys_and_attributes(¶ms, &key_pairs, &public_attributes).unwrap(); + let key_pair = key_pairs.remove(0); + db_dir.push(&key_pair.verification_key().to_bs58()[..8]); + let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap(); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let rocket = rocket::build().attach(InternalSignRequest::stage( + nymd_client.clone(), + key_pair, + comm_channel.clone(), + storage1.clone(), + )); + + let client = Client::tracked(rocket) + .await + .expect("valid rocket instance"); + + let credential = Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string()); + let proposal_id = 42; + // The address is not used, so we can use a duplicate + let gateway_cosmos_addr = validator_address.clone(); + let req = + VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone()); + + // Test endpoint with not proposal for the proposal id + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::IncorrectProposal { + reason: "proposal not found".to_string() + } + .to_string() + ); + + let mut proposal = ProposalResponse { + id: proposal_id, + title: String::new(), + description: String::from("25mnnoCcUfeizfC85avvroFg2prpEZBgJbJM2SLtkgyyUkoAU3cqJiqWmg8cMHEPjfFf5sQF92SMAM2vbEoLZvUjenvXhadTLdA4TqMYArJpihyqirW2AhGoNehtcdcK5gnH"), + msgs: vec![], + status: cw3::Status::Open, + expires: cw_utils::Expiration::Never {}, + threshold: cw_utils::ThresholdResponse::AbsolutePercentage { + percentage: Decimal::from_ratio(2u32, 3u32), + total_weight: 100, + }, + }; + + // Test the endpoint with a different blinded serial number in the description + proposal_db + .write() + .unwrap() + .insert(proposal_id, proposal.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::IncorrectProposal { + reason: "incorrect blinded serial number in description".to_string() + } + .to_string() + ); + + // Test the endpoint with no msg in the proposal action + proposal.description = credential.blinded_serial_number(); + proposal_db + .write() + .unwrap() + .insert(proposal_id, proposal.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::IncorrectProposal { + reason: "action is not to release funds".to_string() + } + .to_string() + ); + + // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract + let funds = Coin::new(voucher_value as u128, MIX_DENOM.base); + let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + funds: funds.clone().into(), + }; + let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: String::new(), + msg: to_binary(&msg).unwrap(), + funds: vec![], + }); + proposal.msgs = vec![cosmos_msg]; + proposal_db + .write() + .unwrap() + .insert(proposal_id, proposal.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::InvalidCredentialStatus { + status: "spent credential not found".to_string() + } + .to_string() + ); + + spent_credential_db.write().unwrap().insert( + credential.blinded_serial_number(), + SpendCredentialResponse::new(None), + ); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::InvalidCredentialStatus { + status: "Inexistent".to_string() + } + .to_string() + ); + + // Test the endpoint with a credential that doesn't verify correctly + let mut spent_credential = SpendCredential::new( + funds.clone().into(), + credential.blinded_serial_number(), + Addr::unchecked("unimportant"), + ); + spent_credential_db.write().unwrap().insert( + credential.blinded_serial_number(), + SpendCredentialResponse::new(Some(spent_credential.clone())), + ); + let bad_credential = Credential::new( + 4, + theta.clone(), + voucher_value, + String::from("bad voucher info"), + ); + let bad_req = + VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&bad_req) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let verify_credential_response = + serde_json::from_str::(&response.into_string().await.unwrap()) + .unwrap(); + assert!(!verify_credential_response.verification_result); + assert_eq!( + cw3::Status::Rejected, + proposal_db + .read() + .unwrap() + .get(&proposal_id) + .unwrap() + .status + ); + + // Test the endpoint with a proposal that has a different value for the funds to be released + // then what's in the credential + let funds = Coin::new((voucher_value + 10) as u128, MIX_DENOM.base); + let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + funds: funds.clone().into(), + }; + let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: String::new(), + msg: to_binary(&msg).unwrap(), + funds: vec![], + }); + proposal.msgs = vec![cosmos_msg]; + proposal_db + .write() + .unwrap() + .insert(proposal_id, proposal.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let verify_credential_response = + serde_json::from_str::(&response.into_string().await.unwrap()) + .unwrap(); + assert!(!verify_credential_response.verification_result); + assert_eq!( + cw3::Status::Rejected, + proposal_db + .read() + .unwrap() + .get(&proposal_id) + .unwrap() + .status + ); + + // Test the endpoint with every dependency met + let funds = Coin::new(voucher_value as u128, MIX_DENOM.base); + let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { + funds: funds.clone().into(), + }; + let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: String::new(), + msg: to_binary(&msg).unwrap(), + funds: vec![], + }); + proposal.msgs = vec![cosmos_msg]; + proposal_db + .write() + .unwrap() + .insert(proposal_id, proposal.clone()); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let verify_credential_response = + serde_json::from_str::(&response.into_string().await.unwrap()) + .unwrap(); + assert!(verify_credential_response.verification_result); + assert_eq!( + cw3::Status::Passed, + proposal_db + .read() + .unwrap() + .get(&proposal_id) + .unwrap() + .status + ); + + // Test the endpoint with the credential marked as Spent in the Coconut Bandwidth Contract + spent_credential.mark_as_spent(); + spent_credential_db.write().unwrap().insert( + credential.blinded_serial_number(), + SpendCredentialResponse::new(Some(spent_credential)), + ); + let response = client + .post(format!( + "/{}/{}/{}/{}", + API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::BadRequest); + assert_eq!( + response.into_string().await.unwrap(), + CoconutError::InvalidCredentialStatus { + status: "Spent".to_string() + } + .to_string() + ); +} diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 51c4bccc6e..662cb93269 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -32,7 +32,7 @@ use tokio::sync::Notify; use crate::rewarded_set_updater::RewardedSetUpdater; #[cfg(feature = "coconut")] -use coconut::InternalSignRequest; +use coconut::{comm::QueryCommunicationChannel, InternalSignRequest}; #[cfg(feature = "coconut")] use coconut_interface::{Base58, KeyPair}; use validator_client::nymd::SigningNymdClient; @@ -453,7 +453,7 @@ async fn setup_rocket( rocket.attach(InternalSignRequest::stage( _nymd_client, keypair, - config.get_all_validator_api_endpoints(), + QueryCommunicationChannel::new(config.get_all_validator_api_endpoints()), storage.clone().unwrap(), )) } else { diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 6977e73c1b..06b17a7fd1 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -3,9 +3,9 @@ #[cfg(feature = "coconut")] use async_trait::async_trait; -use serde::Serialize; #[cfg(feature = "coconut")] -use std::str::FromStr; +use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; +use serde::Serialize; use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; @@ -18,17 +18,16 @@ use mixnet_contract_common::{ }; #[cfg(feature = "coconut")] use multisig_contract_common::msg::ProposalResponse; -#[cfg(feature = "coconut")] -use validator_client::nymd::{ - cosmwasm_client::logs::find_attribute, - traits::{MultisigSigningClient, QueryClient}, - AccountId, -}; use validator_client::nymd::{ hash::{Hash, SHA256_HASH_SIZE}, Coin, CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, TendermintTime, }; +#[cfg(feature = "coconut")] +use validator_client::nymd::{ + traits::{CoconutBandwidthQueryClient, MultisigQueryClient, MultisigSigningClient}, + AccountId, +}; use validator_client::ValidatorClientError; #[cfg(feature = "coconut")] @@ -455,32 +454,17 @@ where Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?) } - async fn propose_release_funds( + async fn get_spent_credential( &self, - title: String, blinded_serial_number: String, - voucher_value: u128, - fee: Option, - ) -> Result { - let res = self + ) -> crate::coconut::error::Result { + Ok(self .0 .read() .await .nymd - .propose_release_funds(title, blinded_serial_number, voucher_value, fee) - .await?; - let proposal_id = u64::from_str( - &find_attribute(&res.logs, "wasm", "proposal_id") - .ok_or_else(|| { - CoconutError::InternalError("No attribute with proposal_id as key".to_string()) - })? - .value, - ) - .map_err(|_| { - CoconutError::InternalError("proposal_id could not be parsed to u64".to_string()) - })?; - - Ok(proposal_id) + .get_spent_credential(blinded_serial_number) + .await?) } async fn vote_proposal( diff --git a/validator-api/validator-api-requests/src/coconut.rs b/validator-api/validator-api-requests/src/coconut.rs index 917934dd52..18afdd81e9 100644 --- a/validator-api/validator-api-requests/src/coconut.rs +++ b/validator-api/validator-api-requests/src/coconut.rs @@ -156,31 +156,3 @@ impl CosmosAddressResponse { CosmosAddressResponse { addr } } } - -#[derive(Serialize, Deserialize, Getters, CopyGetters)] -pub struct ProposeReleaseFundsRequestBody { - #[getset(get = "pub")] - credential: Credential, - #[getset(get = "pub")] - gateway_cosmos_addr: AccountId, -} - -impl ProposeReleaseFundsRequestBody { - pub fn new(credential: Credential, gateway_cosmos_addr: AccountId) -> Self { - ProposeReleaseFundsRequestBody { - credential, - gateway_cosmos_addr, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ProposeReleaseFundsResponse { - pub proposal_id: u64, -} - -impl ProposeReleaseFundsResponse { - pub fn new(proposal_id: u64) -> Self { - ProposeReleaseFundsResponse { proposal_id } - } -} From c485934b06e16cb78bd8965c67b68c070ff195b0 Mon Sep 17 00:00:00 2001 From: Fouad Date: Mon, 18 Jul 2022 15:18:39 +0100 Subject: [PATCH 06/91] 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 --- explorer/src/App.tsx | 6 +- explorer/src/components/Filters/Filters.tsx | 161 ++++++++++++++++++ .../src/components/Filters/filterSchema.ts | 116 +++++++++++++ explorer/src/components/Footer.tsx | 9 +- .../src/components/MixNodes/BondBreakdown.tsx | 9 +- .../src/components/MixNodes/DetailSection.tsx | 8 +- .../components/MixNodes/Economics/Table.tsx | 19 +-- .../components/MixNodes/StatusDropdown.tsx | 9 +- explorer/src/components/TableToolbar.tsx | 21 ++- explorer/src/context/main.tsx | 29 +++- explorer/src/hooks/useIsMobile.ts | 9 + explorer/src/pages/Mixnodes/index.tsx | 1 + explorer/src/typeDefs/filters.ts | 20 +++ 13 files changed, 366 insertions(+), 51 deletions(-) create mode 100644 explorer/src/components/Filters/Filters.tsx create mode 100644 explorer/src/components/Filters/filterSchema.ts create mode 100644 explorer/src/hooks/useIsMobile.ts create mode 100644 explorer/src/typeDefs/filters.ts diff --git a/explorer/src/App.tsx b/explorer/src/App.tsx index a33b49c8ca..9ce60f06f9 100644 --- a/explorer/src/App.tsx +++ b/explorer/src/App.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; -import { useMediaQuery } from '@mui/material'; -import { useTheme } from '@mui/material/styles'; import { Nav } from './components/Nav'; import { MobileNav } from './components/MobileNav'; import { Routes } from './routes/index'; +import { useIsMobile } from './hooks/useIsMobile'; export const App: React.FC = () => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useIsMobile(); if (isMobile) { return ( diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx new file mode 100644 index 0000000000..c62b09a53f --- /dev/null +++ b/explorer/src/components/Filters/Filters.tsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { Tune } from '@mui/icons-material'; +import { + Button, + Dialog, + DialogContent, + DialogActions, + DialogTitle, + IconButton, + Slider, + Typography, + Box, + Snackbar, + Slide, + Alert, +} from '@mui/material'; +import { useParams } from 'react-router-dom'; +import { useMainContext } from '../../context/main'; +import { MixnodeStatusWithAll, toMixnodeStatus } from '../../typeDefs/explorer-api'; +import { EnumFilterKey, TFilterItem, TFilters } from '../../typeDefs/filters'; +import { formatOnSave, generateFilterSchema } from './filterSchema'; +import { Api } from '../../api'; +import { useIsMobile } from '../../hooks/useIsMobile'; + +const FilterItem = ({ + label, + id, + value, + marks, + scale, + min, + max, + onChange, +}: TFilterItem & { + onChange: (id: EnumFilterKey, newValue: number[]) => void; +}) => ( + + {label} + onChange(id, newValue as number[])} + valueLabelDisplay="off" + marks={marks} + step={null} + scale={scale} + min={min} + max={max} + /> + +); + +export const Filters = () => { + const { filterMixnodes, fetchMixnodes } = useMainContext(); + const { status } = useParams<{ status: MixnodeStatusWithAll | undefined }>(); + const isMobile = useIsMobile(); + + const [showFilters, setShowFilters] = useState(false); + const [isFiltered, setIsFiltered] = useState(false); + const [filters, setFilters] = React.useState(); + + const baseFilters = useRef(); + const prevFilters = useRef(); + + const handleToggleShowFilters = () => setShowFilters(!showFilters); + + const initialiseFilters = useCallback(async () => { + let upperSaturationValue; + const allMixnodes = await Api.fetchMixnodes(); + if (allMixnodes) { + upperSaturationValue = Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1); + const initFilters = generateFilterSchema(upperSaturationValue); + baseFilters.current = initFilters; + prevFilters.current = initFilters; + setFilters(initFilters); + } + }, []); + + const handleOnChange = (id: EnumFilterKey, newValue: number[]) => { + setFilters((ftrs) => { + if (ftrs) return { ...ftrs, [id]: { ...ftrs[id], value: newValue } }; + return undefined; + }); + }; + + const handleOnSave = async () => { + setShowFilters(false); + await filterMixnodes(formatOnSave(filters!), status); + setIsFiltered(true); + prevFilters.current = filters; + }; + + const handleOnCancel = () => { + setShowFilters(false); + setFilters(prevFilters.current); + }; + + const resetFilters = () => { + setFilters(baseFilters.current); + setIsFiltered(false); + prevFilters.current = baseFilters.current; + }; + + const onClearFilters = async () => { + await fetchMixnodes(toMixnodeStatus(status)); + resetFilters(); + }; + + useEffect(() => { + initialiseFilters(); + }, [initialiseFilters]); + + useEffect(() => { + resetFilters(); + }, [status]); + + if (!filters) return null; + + return ( + <> + + + Clear + + } + sx={{ width: 300 }} + > + Filters applied + + + + + + + Mixnode filters + + {Object.values(filters).map((v) => ( + + ))} + + + + + + + + ); +}; diff --git a/explorer/src/components/Filters/filterSchema.ts b/explorer/src/components/Filters/filterSchema.ts new file mode 100644 index 0000000000..1d6b742f90 --- /dev/null +++ b/explorer/src/components/Filters/filterSchema.ts @@ -0,0 +1,116 @@ +import { EnumFilterKey, TFilters } from '../../typeDefs/filters'; + +export const generateFilterSchema = (upperSaturationValue?: number) => ({ + profitMargin: { + label: 'Profit margin (%)', + id: EnumFilterKey.profitMargin, + value: [0, 100], + marks: [ + { label: '0', value: 0 }, + { label: '10', value: 10 }, + { label: '20', value: 20 }, + { label: '30', value: 30 }, + { label: '40', value: 40 }, + { label: '50', value: 50 }, + { label: '60', value: 60 }, + { label: '70', value: 70 }, + { label: '80', value: 80 }, + { label: '90', value: 90 }, + { label: '100', value: 100 }, + ], + }, + stakeSaturation: { + label: 'Stake saturation (%)', + id: EnumFilterKey.stakeSaturation, + value: [0, upperSaturationValue || 100], + marks: [ + { label: '0', value: 0 }, + + { + label: '10', + value: 10, + }, + + { + label: '50', + value: 50, + }, + { label: '90', value: 90 }, + + { + label: upperSaturationValue ? `${upperSaturationValue}` : '100', + value: upperSaturationValue || 100, + }, + ], + max: upperSaturationValue, + }, + stake: { + label: 'Stake (NYM)', + id: EnumFilterKey.stake, + value: [20, 90], + min: 20, + max: 90, + marks: [ + { + value: 0, + label: '1', + }, + { + value: 10, + label: '10', + }, + { + value: 20, + label: '100', + }, + { + value: 30, + label: '1k', + }, + { + value: 40, + label: '10k', + }, + { + value: 50, + label: '100k', + }, + { + value: 60, + label: '1M', + }, + { + value: 70, + label: '10M', + }, + { + value: 80, + label: '100M', + }, + { + value: 90, + label: '1B', + }, + ], + }, +}); + +const formatStakeValuesToMinorDenom = ([value_1, value_2]: number[]) => { + const lowerValue = 10 ** (value_1 / 10) * 1_000_000; + const upperValue = 10 ** (value_2 / 10) * 1_000_000; + + return [lowerValue, upperValue]; +}; + +const formatStakeSaturationValues = ([value_1, value_2]: number[]) => { + const lowerValue = value_1 / 100; + const upperValue = value_2 / 100; + + return [lowerValue, upperValue]; +}; + +export const formatOnSave = (filters: TFilters) => ({ + stake: formatStakeValuesToMinorDenom(filters.stake.value), + profitMargin: filters.profitMargin.value, + stakeSaturation: formatStakeSaturationValues(filters.stakeSaturation.value), +}); diff --git a/explorer/src/components/Footer.tsx b/explorer/src/components/Footer.tsx index 1696def9ee..2ee7c77198 100644 --- a/explorer/src/components/Footer.tsx +++ b/explorer/src/components/Footer.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { Typography, useMediaQuery } from '@mui/material'; -import { useTheme } from '@mui/material/styles'; +import { Typography } from '@mui/material'; import { Socials } from './Socials'; +import { useIsMobile } from '../hooks/useIsMobile'; export const Footer: React.FC = () => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useIsMobile(); return ( { sx={{ fontSize: 12, textAlign: isMobile ? 'center' : 'end', - color: theme.palette.nym.muted.onDarkBg, + color: 'nym.muted.onDarkBg', }} > © {new Date().getFullYear()} Nym Technologies SA, all rights reserved diff --git a/explorer/src/components/MixNodes/BondBreakdown.tsx b/explorer/src/components/MixNodes/BondBreakdown.tsx index 4b2dc58d92..06009be05d 100644 --- a/explorer/src/components/MixNodes/BondBreakdown.tsx +++ b/explorer/src/components/MixNodes/BondBreakdown.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Alert, Box, CircularProgress, useMediaQuery, Typography } from '@mui/material'; +import { Alert, Box, CircularProgress, Typography } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -11,6 +11,7 @@ import Paper from '@mui/material/Paper'; import { ExpandMore } from '@mui/icons-material'; import { currencyToString } from '../../utils/currency'; import { useMixnodeContext } from '../../context/mixnode'; +import { useIsMobile } from '../../hooks/useIsMobile'; export const BondBreakdownTable: React.FC = () => { const { mixNode, delegations, uniqDelegations } = useMixnodeContext(); @@ -23,7 +24,7 @@ export const BondBreakdownTable: React.FC = () => { hasLoaded: false, }); const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.down('sm')); + const isMobile = useIsMobile(); React.useEffect(() => { if (mixNode?.data) { @@ -83,7 +84,7 @@ export const BondBreakdownTable: React.FC = () => { - + { {uniqDelegations?.data?.map(({ owner, amount: { amount, denom } }) => ( - + {owner} {currencyToString(amount.toString(), denom)} diff --git a/explorer/src/components/MixNodes/DetailSection.tsx b/explorer/src/components/MixNodes/DetailSection.tsx index 6ffe7af34e..b338487fd2 100644 --- a/explorer/src/components/MixNodes/DetailSection.tsx +++ b/explorer/src/components/MixNodes/DetailSection.tsx @@ -1,10 +1,10 @@ -import { Box, Button, Grid, Typography, useMediaQuery } from '@mui/material'; import * as React from 'react'; +import { Box, Button, Grid, Typography, useTheme } from '@mui/material'; import Identicon from 'react-identicons'; -import { useTheme } from '@mui/material/styles'; import { MixnodeRowType } from '.'; import { getMixNodeStatusText, MixNodeStatus } from './Status'; import { MixNodeDescriptionResponse } from '../../typeDefs/explorer-api'; +import { useIsMobile } from '../../hooks/useIsMobile'; interface MixNodeDetailProps { mixNodeRow: MixnodeRowType; @@ -14,7 +14,7 @@ interface MixNodeDetailProps { export const MixNodeDetailSection: React.FC = ({ mixNodeRow, mixnodeDescription }) => { const theme = useTheme(); const palette = [theme.palette.text.primary]; - const matches = useMediaQuery(theme.breakpoints.down('sm')); + const isMobile = useIsMobile(); const statusText = React.useMemo(() => getMixNodeStatusText(mixNodeRow.status), [mixNodeRow.status]); return ( @@ -64,7 +64,7 @@ export const MixNodeDetailSection: React.FC = ({ mixNodeRow, - + Node status: diff --git a/explorer/src/components/MixNodes/Economics/Table.tsx b/explorer/src/components/MixNodes/Economics/Table.tsx index 12c67e9323..d60a83da84 100644 --- a/explorer/src/components/MixNodes/Economics/Table.tsx +++ b/explorer/src/components/MixNodes/Economics/Table.tsx @@ -1,15 +1,5 @@ import * as React from 'react'; -import { - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, - useMediaQuery, -} from '@mui/material'; +import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@mui/material'; import { Box } from '@mui/system'; import { useTheme, Theme } from '@mui/material/styles'; import { Tooltip } from '@nymproject/react/tooltip/Tooltip'; @@ -17,6 +7,7 @@ import { EconomicsRowsType, EconomicsInfoRowWithIndex } from './types'; import { EconomicsProgress } from './EconomicsProgress'; import { cellStyles } from '../../Universal-DataGrid'; import { UniversalTableProps } from '../../DetailTable'; +import { useIsMobile } from '../../../hooks/useIsMobile'; const threshold = 100; @@ -44,8 +35,8 @@ const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => { return theme.palette.nym.wallet.fee; }; -const formatCellValues = (value: EconomicsRowsType, field: string, theme: Theme) => { - const isTablet = useMediaQuery(theme.breakpoints.down('lg')); +const formatCellValues = (value: EconomicsRowsType, field: string) => { + const isTablet = useIsMobile('lg'); if (value.progressBarValue) { return ( @@ -130,7 +121,7 @@ export const DelegatorsInfoTable: React.FC - {formatCellValues(value, columnsData[index].field, theme)} + {formatCellValues(value, columnsData[index].field)} ); })} diff --git a/explorer/src/components/MixNodes/StatusDropdown.tsx b/explorer/src/components/MixNodes/StatusDropdown.tsx index 6856beca4a..1dba2c2f95 100644 --- a/explorer/src/components/MixNodes/StatusDropdown.tsx +++ b/explorer/src/components/MixNodes/StatusDropdown.tsx @@ -1,11 +1,11 @@ -import { MenuItem, useMediaQuery } from '@mui/material'; import * as React from 'react'; +import { MenuItem } from '@mui/material'; import Select from '@mui/material/Select'; import { SelectInputProps } from '@mui/material/Select/SelectInput'; -import { useTheme } from '@mui/material/styles'; import { SxProps } from '@mui/system'; import { MixNodeStatus } from './Status'; import { MixnodeStatus, MixnodeStatusWithAll } from '../../typeDefs/explorer-api'; +import { useIsMobile } from '../../hooks/useIsMobile'; // TODO: replace with i18n const ALL_NODES = 'All nodes'; @@ -17,8 +17,7 @@ interface MixNodeStatusDropdownProps { } export const MixNodeStatusDropdown: React.FC = ({ status, onSelectionChanged, sx }) => { - const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.down('sm')); + const isMobile = useIsMobile(); const [statusValue, setStatusValue] = React.useState(status || MixnodeStatusWithAll.all); const onChange: SelectInputProps['onChange'] = React.useCallback( ({ target: { value } }) => { @@ -47,7 +46,7 @@ export const MixNodeStatusDropdown: React.FC = ({ st } }} sx={{ - width: matches ? 'auto' : 200, + width: isMobile ? 'auto' : 200, ...sx, }} > diff --git a/explorer/src/components/TableToolbar.tsx b/explorer/src/components/TableToolbar.tsx index 05f6b8b651..c6a64dd6c5 100644 --- a/explorer/src/components/TableToolbar.tsx +++ b/explorer/src/components/TableToolbar.tsx @@ -1,13 +1,15 @@ import * as React from 'react'; -import { Box, useMediaQuery, TextField, MenuItem } from '@mui/material'; -import { useTheme } from '@mui/material/styles'; +import { Box, TextField, MenuItem } from '@mui/material'; import Select, { SelectChangeEvent } from '@mui/material/Select'; +import { Filters } from './Filters/Filters'; +import { useIsMobile } from '../hooks/useIsMobile'; type TableToolBarProps = { onChangeSearch: (arg: string) => void; onChangePageSize: (event: SelectChangeEvent) => void; pageSize: string; searchTerm: string; + withFilters?: boolean; childrenBefore?: React.ReactNode; childrenAfter?: React.ReactNode; }; @@ -19,16 +21,16 @@ export const TableToolbar: React.FC = ({ pageSize, childrenBefore, childrenAfter, + withFilters, }) => { - const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.down('sm')); + const isMobile = useIsMobile(); return ( @@ -40,7 +42,7 @@ export const TableToolbar: React.FC = ({ value={pageSize} onChange={onChangePageSize} sx={{ - width: matches ? 100 : 200, + width: isMobile ? 100 : 200, }} > @@ -57,17 +59,18 @@ export const TableToolbar: React.FC = ({ - + onChangeSearch(event.target.value)} /> + {withFilters && } {childrenAfter} diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index 4663cd2644..7fca785209 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -10,6 +10,7 @@ import { SummaryOverviewResponse, ValidatorsResponse, } from '../typeDefs/explorer-api'; +import { EnumFilterKey } from '../typeDefs/filters'; import { Api } from '../api'; import { NavOptionType, originalNavOptions } from './nav'; @@ -26,8 +27,8 @@ interface StateData { } interface StateApi { - fetchMixnodes: (status?: MixnodeStatus) => void; - filterMixnodes: (mixnodes: MixNodeResponse) => void; + fetchMixnodes: (status?: MixnodeStatus) => Promise; + filterMixnodes: (filters: any, status: any) => void; toggleMode: () => void; updateNavState: (id: number) => void; } @@ -40,7 +41,7 @@ export const MainContext = React.createContext({ navState: originalNavOptions, toggleMode: () => undefined, filterMixnodes: () => null, - fetchMixnodes: () => null, + fetchMixnodes: () => Promise.resolve(undefined), }); export const useMainContext = (): React.ContextType => React.useContext(MainContext); @@ -78,9 +79,10 @@ export const MainContextProvider: React.FC = ({ children }) => { }; const fetchMixnodes = async (status?: MixnodeStatus) => { + let data; setMixnodes((d) => ({ ...d, isLoading: true })); try { - const data = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes(); + data = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes(); setMixnodes({ data, isLoading: false }); } catch (error) { setMixnodes({ @@ -88,10 +90,24 @@ export const MainContextProvider: React.FC = ({ children }) => { isLoading: false, }); } + return data; }; - const filterMixnodes = (arr: MixNodeResponse) => { - setMixnodes({ data: arr, isLoading: false }); + + const filterMixnodes = async (filters: { [key in EnumFilterKey]: number[] }, status?: MixnodeStatus) => { + setMixnodes((d) => ({ ...d, isLoading: true })); + const mxns = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes(); + const filtered = mxns?.filter( + (m) => + m.mix_node.profit_margin_percent >= filters.profitMargin[0] && + m.mix_node.profit_margin_percent <= filters.profitMargin[1] && + m.stake_saturation >= filters.stakeSaturation[0] && + m.stake_saturation <= filters.stakeSaturation[1] && + +m.pledge_amount.amount + +m.total_delegation.amount >= filters.stake[0] && + +m.pledge_amount.amount + +m.total_delegation.amount <= filters.stake[1], + ); + setMixnodes({ data: filtered, isLoading: false }); }; + const fetchGateways = async () => { try { const data = await Api.fetchGateways(); @@ -144,6 +160,7 @@ export const MainContextProvider: React.FC = ({ children }) => { })); updateNav(updated); }; + React.useEffect(() => { Promise.all([fetchOverviewSummary(), fetchGateways(), fetchValidators(), fetchBlock(), fetchCountryData()]); }, []); diff --git a/explorer/src/hooks/useIsMobile.ts b/explorer/src/hooks/useIsMobile.ts new file mode 100644 index 0000000000..225964b464 --- /dev/null +++ b/explorer/src/hooks/useIsMobile.ts @@ -0,0 +1,9 @@ +import { Breakpoint, useMediaQuery } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; + +export const useIsMobile = (queryInput: number | Breakpoint = 'md') => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(queryInput)); + + return isMobile; +}; diff --git a/explorer/src/pages/Mixnodes/index.tsx b/explorer/src/pages/Mixnodes/index.tsx index 4427ba8b46..33ebb84993 100644 --- a/explorer/src/pages/Mixnodes/index.tsx +++ b/explorer/src/pages/Mixnodes/index.tsx @@ -290,6 +290,7 @@ export const PageMixnodes: React.FC = () => { onChangePageSize={handlePageSize} pageSize={pageSize} searchTerm={searchTerm} + withFilters /> number; +}; + +export type TFilters = { [key in EnumFilterKey]: TFilterItem }; From 9ca3f69aa864cd3e4cc12109814e827c4e9ec919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 20 Jul 2022 13:16:37 +0300 Subject: [PATCH 07/91] 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 --- CHANGELOG.md | 3 + Cargo.lock | 8 +- clients/client-core/src/config/mod.rs | 3 +- clients/credential/src/client.rs | 13 +- clients/native/Cargo.toml | 1 - clients/native/src/commands/mod.rs | 14 +- clients/native/src/commands/upgrade.rs | 11 +- clients/native/src/main.rs | 3 +- clients/socks5/Cargo.toml | 1 - clients/socks5/src/commands/mod.rs | 12 +- clients/socks5/src/commands/upgrade.rs | 11 +- clients/socks5/src/main.rs | 3 +- .../validator-client/src/client.rs | 17 +-- .../nymd/traits/multisig_signing_client.rs | 6 +- .../validator-client/src/nymd/wallet/mod.rs | 41 ++---- .../coconut-bandwidth-contract/src/msg.rs | 1 + .../mixnet-contract/Cargo.toml | 1 - .../mixnet-contract/src/msg.rs | 1 + .../multisig-contract/src/msg.rs | 1 + .../vesting-contract/src/lib.rs | 5 +- .../vesting-contract/src/messages.rs | 1 + common/credentials/Cargo.toml | 1 - .../crypto/src/bech32_address_validation.rs | 18 ++- common/network-defaults/Cargo.toml | 1 + common/network-defaults/build.rs | 11 -- common/network-defaults/src/lib.rs | 124 +++++++++--------- common/network-defaults/src/mainnet.rs | 48 ++++++- common/network-defaults/src/var_names.rs | 21 +++ common/statistics/Cargo.toml | 2 - contracts/Cargo.lock | 13 +- contracts/bandwidth-claim/Cargo.toml | 2 - contracts/bandwidth-claim/src/lib.rs | 7 +- contracts/coconut-bandwidth/Cargo.toml | 1 - contracts/coconut-bandwidth/src/contract.rs | 9 +- contracts/coconut-bandwidth/src/error.rs | 6 +- contracts/coconut-bandwidth/src/state.rs | 1 + contracts/coconut-bandwidth/src/storage.rs | 4 +- .../src/support/tests/fixtures.rs | 7 +- .../src/support/tests/helpers.rs | 3 + .../coconut-bandwidth/src/transactions.rs | 51 ++++--- contracts/coconut-test/Cargo.toml | 1 - .../coconut-test/src/deposit_and_release.rs | 12 +- .../src/spend_credential_creates_proposal.rs | 13 +- contracts/mixnet/src/contract.rs | 16 ++- contracts/mixnet/src/delegations/queries.rs | 9 +- contracts/mixnet/src/delegations/storage.rs | 6 +- .../mixnet/src/delegations/transactions.rs | 117 ++++++++++------- contracts/mixnet/src/error.rs | 9 +- contracts/mixnet/src/gateways/storage.rs | 4 +- contracts/mixnet/src/gateways/transactions.rs | 72 +++++++--- .../src/mixnet_contract_settings/models.rs | 1 + .../src/mixnet_contract_settings/queries.rs | 1 + .../src/mixnet_contract_settings/storage.rs | 11 +- contracts/mixnet/src/mixnodes/storage.rs | 12 +- contracts/mixnet/src/mixnodes/transactions.rs | 79 ++++++++--- contracts/mixnet/src/rewards/queries.rs | 4 +- contracts/mixnet/src/rewards/transactions.rs | 59 +++++---- .../mixnet/src/support/tests/fixtures.rs | 12 +- contracts/mixnet/src/support/tests/mod.rs | 9 +- contracts/mixnet/src/support/tests/queries.rs | 5 +- .../multisig/cw3-flex-multisig/Cargo.toml | 1 - .../cw3-flex-multisig/src/contract.rs | 35 ++--- contracts/vesting/Cargo.toml | 1 - contracts/vesting/src/contract.rs | 37 +++--- contracts/vesting/src/storage.rs | 1 + contracts/vesting/src/support/tests.rs | 8 +- .../vesting/src/traits/vesting_account.rs | 2 + .../src/vesting/account/delegating_account.rs | 7 +- .../account/gateway_bonding_account.rs | 3 +- .../account/mixnode_bonding_account.rs | 9 +- .../src/vesting/account/vesting_account.rs | 36 ++--- contracts/vesting/src/vesting/mod.rs | 79 ++++++----- envs/mainnet.env | 20 +++ envs/qa.env | 20 +++ explorer-api/Cargo.toml | 1 + explorer-api/src/client.rs | 24 ++-- explorer-api/src/commands/mod.rs | 12 ++ explorer-api/src/main.rs | 5 + gateway/src/commands/init.rs | 3 + gateway/src/commands/mod.rs | 20 ++- gateway/src/commands/upgrade.rs | 10 +- gateway/src/config/mod.rs | 8 +- gateway/src/main.rs | 13 +- .../websocket/connection_handler/coconut.rs | 11 +- gateway/src/node/mod.rs | 6 +- mixnode/src/commands/mod.rs | 7 +- mixnode/src/commands/upgrade.rs | 10 +- mixnode/src/config/mod.rs | 6 +- mixnode/src/main.rs | 13 +- nym-connect/Cargo.lock | 11 +- nym-wallet/Cargo.lock | 4 +- nym-wallet/src-tauri/src/error.rs | 2 +- .../src/operations/mixnet/account.rs | 20 ++- .../src/statistics/collector.rs | 12 +- validator-api/src/coconut/mod.rs | 10 +- validator-api/src/coconut/tests.rs | 41 ++++-- validator-api/src/config/mod.rs | 17 +-- validator-api/src/main.rs | 36 ++++- validator-api/src/nymd_client.rs | 17 +-- 99 files changed, 900 insertions(+), 617 deletions(-) delete mode 100644 common/network-defaults/build.rs create mode 100644 common/network-defaults/src/var_names.rs create mode 100644 envs/mainnet.env create mode 100644 envs/qa.env create mode 100644 explorer-api/src/commands/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 191b28da4d..251ce93fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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]) + [#1249]: https://github.com/nymtech/nym/pull/1249 [#1256]: https://github.com/nymtech/nym/pull/1256 @@ -74,6 +76,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#1419]: https://github.com/nymtech/nym/pull/1419 [#1427]: https://github.com/nymtech/nym/pull/1427 [#1457]: https://github.com/nymtech/nym/pull/1457 +[#1463]: https://github.com/nymtech/nym/pull/1463 ## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11) diff --git a/Cargo.lock b/Cargo.lock index 8f89dcb1ae..f9935dfc1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -928,7 +928,6 @@ dependencies = [ "coconut-interface", "cosmrs", "crypto", - "network-defaults", "rand 0.7.3", "thiserror", "url", @@ -1591,6 +1590,7 @@ name = "explorer-api" version = "1.0.1" dependencies = [ "chrono", + "clap 3.2.8", "humantime-serde", "isocountry", "itertools", @@ -2871,7 +2871,6 @@ dependencies = [ "cosmwasm-std", "fixed", "log", - "network-defaults", "schemars", "serde", "serde_repr", @@ -2968,6 +2967,7 @@ name = "network-defaults" version = "0.1.0" dependencies = [ "cfg-if 1.0.0", + "dotenv", "hex-literal", "once_cell", "serde", @@ -3065,7 +3065,6 @@ dependencies = [ "credentials", "crypto", "dirs", - "dotenv", "futures", "gateway-client", "gateway-requests", @@ -3232,7 +3231,6 @@ dependencies = [ "credentials", "crypto", "dirs", - "dotenv", "futures", "gateway-client", "gateway-requests", @@ -5390,7 +5388,6 @@ version = "1.0.1" dependencies = [ "async-trait", "log", - "network-defaults", "reqwest", "serde", "serde_json", @@ -6338,7 +6335,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vesting-contract" version = "1.0.1" dependencies = [ - "config", "cosmwasm-std", "cw-storage-plus", "mixnet-contract-common", diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index f797851ce3..1c81d5e079 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // 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 Default for Client { 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(), diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs index 1c3286f7c5..005aab356e 100644 --- a/clients/credential/src/client.rs +++ b/clients/credential/src/client.rs @@ -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, + 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, ) -> Result { - 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( diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 79b04a7d7b..68b4c6d8b3 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -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 diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index c63ada0383..b1e5da339f 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -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, + #[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 { diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index 2ac897cf23..c925b10f08 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -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()); diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index dd0f644e43..13e82ec660 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -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; } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 0c431871df..2c79789bc0 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -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" diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index ea9d5da052..9ff48199a7 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -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, + #[clap(subcommand)] command: Commands, } @@ -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 { diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index 849400d3d6..5e9517ac2e 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -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()); diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index 7571a482e0..fb7a8bb607 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -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; } diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 71129dc50c..d820611bf0 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -28,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}; @@ -113,9 +113,6 @@ impl Config { #[cfg(feature = "nymd-client")] pub struct Client { - // 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, @@ -134,9 +131,6 @@ pub struct Client { impl Client { 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, ValidatorClientError> { let validator_api_client = validator_api::Client::new(config.api_url.clone()); @@ -148,7 +142,6 @@ impl Client { )?; Ok(Client { - network, mnemonic: Some(mnemonic), mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, @@ -176,18 +169,12 @@ impl Client { #[cfg(feature = "nymd-client")] impl Client { - 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, ValidatorClientError> { + pub fn new_query(config: Config) -> Result, 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, diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs index 9b685a0f0e..46bb118ef0 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs @@ -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 MultisigSigningClient for NymdClien ) -> Result { 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(), diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index 0ea6b081c2..247ffcd743 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -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() + ) } } } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs index ea2c1d18b9..20f1e2700d 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/msg.rs @@ -11,6 +11,7 @@ use crate::{deposit::DepositData, spend_credential::SpendCredentialData}; pub struct InstantiateMsg { pub multisig_addr: String, pub pool_addr: String, + pub mix_denom: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 0f6e5d9756..20fc4ecb16 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -14,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" diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index d8f09d504a..00d5361c40 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -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)] diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs index e20464bcfc..93fd9f4839 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs @@ -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, } diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs index f995c241fd..da29e02cae 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -1,6 +1,5 @@ // Copyright 2022 - Nym Technologies SA // 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))] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 51802e77ac..668d24db66 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -7,6 +7,7 @@ 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)] diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 110d6ee224..4ff6b60d2b 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -14,7 +14,6 @@ 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" } diff --git a/common/crypto/src/bech32_address_validation.rs b/common/crypto/src/bech32_address_validation.rs index 21db7e5297..f432daab4c 100644 --- a/common/crypto/src/bech32_address_validation.rs +++ b/common/crypto/src/bech32_address_validation.rs @@ -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 { } } -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" ) ) diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml index 85282473ce..0c84938c7b 100644 --- a/common/network-defaults/Cargo.toml +++ b/common/network-defaults/Cargo.toml @@ -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"]} diff --git a/common/network-defaults/build.rs b/common/network-defaults/build.rs deleted file mode 100644 index 2c71c67e3d..0000000000 --- a/common/network-defaults/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// 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"), - } -} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 6281527910..a156394e56 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -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>(mut self, prefix: S) -> Self { self.chain_details.bech32_account_prefix = prefix.into(); self @@ -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 { - DEFAULT_NETWORK - .validators() - .iter() - .map(ValidatorDetails::nymd_url) - .collect() -} - -pub fn default_api_endpoints() -> Vec { - DEFAULT_NETWORK - .validators() - .iter() - .filter_map(ValidatorDetails::api_url) - .collect() +pub fn setup_env(config_env_file: Option) { + 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, diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index badce8e2bc..2bb7084a12 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // 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 { - 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); } diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs new file mode 100644 index 0000000000..d1f3cf2736 --- /dev/null +++ b/common/network-defaults/src/var_names.rs @@ -0,0 +1,21 @@ +// Copyright 2022 - Nym Technologies SA +// 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"; diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 1094962c21..9514edd29b 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -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" } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 9317ca8108..5438f45804 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -50,7 +50,6 @@ name = "bandwidth-claim" version = "1.0.0" dependencies = [ "bandwidth-claim-contract", - "config", "cosmwasm-std", "cosmwasm-storage", "schemars", @@ -221,7 +220,6 @@ version = "0.1.0" dependencies = [ "bandwidth-claim-contract", "coconut-bandwidth-contract-common", - "config", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", @@ -249,7 +247,6 @@ dependencies = [ "bandwidth-claim-contract", "coconut-bandwidth", "coconut-bandwidth-contract-common", - "config", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", @@ -544,7 +541,6 @@ dependencies = [ "cw4", "cw4-group", "multisig-contract-common", - "network-defaults", "schemars", "serde", "thiserror", @@ -616,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" @@ -1080,7 +1082,6 @@ dependencies = [ "cosmwasm-std", "fixed", "log", - "network-defaults", "schemars", "serde", "serde_repr", @@ -1106,6 +1107,7 @@ name = "network-defaults" version = "0.1.0" dependencies = [ "cfg-if", + "dotenv", "hex-literal", "once_cell", "serde", @@ -1852,7 +1854,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vesting-contract" version = "1.0.1" dependencies = [ - "config", "cosmwasm-std", "cw-storage-plus", "mixnet-contract-common", diff --git a/contracts/bandwidth-claim/Cargo.toml b/contracts/bandwidth-claim/Cargo.toml index 5d530a5452..b6491dda88 100644 --- a/contracts/bandwidth-claim/Cargo.toml +++ b/contracts/bandwidth-claim/Cargo.toml @@ -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" } diff --git a/contracts/bandwidth-claim/src/lib.rs b/contracts/bandwidth-claim/src/lib.rs index 9dd88197e7..91deceaefa 100644 --- a/contracts/bandwidth-claim/src/lib.rs +++ b/contracts/bandwidth-claim/src/lib.rs @@ -62,10 +62,11 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result Result { 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)?; @@ -74,8 +76,8 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result = Item::new("config"); diff --git a/contracts/coconut-bandwidth/src/storage.rs b/contracts/coconut-bandwidth/src/storage.rs index f3515f92e1..40e33b9f8d 100644 --- a/contracts/coconut-bandwidth/src/storage.rs +++ b/contracts/coconut-bandwidth/src/storage.rs @@ -43,8 +43,8 @@ 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 config::defaults::MIX_DENOM; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::Addr; use cosmwasm_std::Coin; @@ -76,7 +76,7 @@ mod tests { #[test] fn mark_as_spent_credential() { let mut mock_storage = MockStorage::new(); - let funds = Coin::new(100, MIX_DENOM.base); + 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"); diff --git a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs index e6b0e64249..c1c68a7f0c 100644 --- a/contracts/coconut-bandwidth/src/support/tests/fixtures.rs +++ b/contracts/coconut-bandwidth/src/support/tests/fixtures.rs @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData}; -use config::defaults::MIX_DENOM; 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, MIX_DENOM.base), + Coin::new(100, TEST_MIX_DENOM), blinded_serial_number.to_string(), Addr::unchecked("gateway_owner_addr"), ) @@ -15,7 +16,7 @@ pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData { SpendCredentialData::new( - Coin::new(100, MIX_DENOM.base), + Coin::new(100, TEST_MIX_DENOM), blinded_serial_number.to_string(), "gateway_owner_addr".to_string(), ) diff --git a/contracts/coconut-bandwidth/src/support/tests/helpers.rs b/contracts/coconut-bandwidth/src/support/tests/helpers.rs index 60eb675a2d..31a4a6f279 100644 --- a/contracts/coconut-bandwidth/src/support/tests/helpers.rs +++ b/contracts/coconut-bandwidth/src/support/tests/helpers.rs @@ -9,11 +9,14 @@ 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> { 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", &[]); diff --git a/contracts/coconut-bandwidth/src/transactions.rs b/contracts/coconut-bandwidth/src/transactions.rs index 9b5cf8f35a..92d3a28bd1 100644 --- a/contracts/coconut-bandwidth/src/transactions.rs +++ b/contracts/coconut-bandwidth/src/transactions.rs @@ -15,10 +15,9 @@ 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, @@ -29,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(); @@ -49,8 +49,9 @@ pub(crate) fn spend_credential( _info: MessageInfo, data: SpendCredentialData, ) -> Result { - if data.funds().denom != MIX_DENOM.base { - return Err(ContractError::WrongDenom); + 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); @@ -84,12 +85,13 @@ pub(crate) fn release_funds( info: MessageInfo, funds: Coin, ) -> Result { - 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); } @@ -133,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()]); @@ -145,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() + }) ); } @@ -163,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(); @@ -212,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(), @@ -221,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(), @@ -248,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()]); @@ -327,10 +339,15 @@ mod tests { String::new(), ); let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data); - assert_eq!(ret.unwrap_err(), ContractError::WrongDenom); + 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, MIX_DENOM.base), + Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM), String::new(), "Blinded Serial Number".to_string(), ); diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml index ec13753706..5955391250 100644 --- a/contracts/coconut-test/Cargo.toml +++ b/contracts/coconut-test/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" 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" } -config = { path = "../../common/config"} cosmwasm-std = "1.0.0" cosmwasm-storage = "1.0.0" diff --git a/contracts/coconut-test/src/deposit_and_release.rs b/contracts/coconut-test/src/deposit_and_release.rs index a0ff781459..0212741261 100644 --- a/contracts/coconut-test/src/deposit_and_release.rs +++ b/contracts/coconut-test/src/deposit_and_release.rs @@ -7,16 +7,17 @@ use coconut_bandwidth_contract_common::{ deposit::DepositData, msg::{ExecuteMsg, InstantiateMsg}, }; -use config::defaults::MIX_DENOM; 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, MIX_DENOM.base); - let deposit_funds = coins(1, MIX_DENOM.base); - let release_funds = coins(2, MIX_DENOM.base); + 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); @@ -26,6 +27,7 @@ fn deposit_and_release() { 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( @@ -94,6 +96,6 @@ fn deposit_and_release() { &[], ) .unwrap(); - let pool_bal = app.wrap().query_balance(pool_addr, MIX_DENOM.base).unwrap(); + let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap(); assert_eq!(pool_bal, deposit_funds[0]); } diff --git a/contracts/coconut-test/src/spend_credential_creates_proposal.rs b/contracts/coconut-test/src/spend_credential_creates_proposal.rs index c47a8f0868..7d07905b97 100644 --- a/contracts/coconut-test/src/spend_credential_creates_proposal.rs +++ b/contracts/coconut-test/src/spend_credential_creates_proposal.rs @@ -6,16 +6,19 @@ use coconut_bandwidth_contract_common::{ }, spend_credential::SpendCredentialData, }; -use config::defaults::MIX_DENOM; 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, MIX_DENOM.base); + let init_funds = coins(10, TEST_COIN_DENOM); let mut app = mock_app(&init_funds); let pool_addr = String::from(POOL_CONTRACT); @@ -42,6 +45,7 @@ fn spend_credential_creates_proposal() { 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( @@ -58,6 +62,7 @@ fn spend_credential_creates_proposal() { 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( @@ -83,7 +88,7 @@ fn spend_credential_creates_proposal() { let msg = CoconutBandwidthExecuteMsg::SpendCredential { data: SpendCredentialData::new( - Coin::new(1, MIX_DENOM.base), + Coin::new(1, TEST_COIN_DENOM), String::from("blinded_serial_number"), String::from("gateway_cosmos_address"), ), @@ -126,7 +131,7 @@ fn spend_credential_creates_proposal() { let msg = CoconutBandwidthExecuteMsg::SpendCredential { data: SpendCredentialData::new( - Coin::new(1, MIX_DENOM.base), + Coin::new(1, TEST_COIN_DENOM), String::from("blinded_serial_number2"), String::from("gateway_cosmos_address"), ), diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index d95f364e50..923ed77ac9 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -61,9 +61,14 @@ pub fn debug_with_visibility>(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 +93,7 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { 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)?; @@ -452,7 +457,7 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result) -> Result { +fn validate_delegation_stake( + mut delegation: Vec, + mix_denom: String, +) -> Result { // check if anything was put as delegation if delegation.is_empty() { return Err(ContractError::EmptyDelegation); @@ -85,8 +87,8 @@ fn validate_delegation_stake(mut delegation: Vec) -> Result Result { // check if the delegation contains any funds of the appropriate denomination - let amount = validate_delegation_stake(info.funds)?; + let amount = + validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?; _try_delegate_to_mixnode( deps, @@ -124,7 +127,8 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf( delegate: String, ) -> Result { // check if the delegation contains any funds of the appropriate denomination - let amount = validate_delegation_stake(info.funds)?; + let amount = + validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?; _try_delegate_to_mixnode( deps, @@ -260,6 +264,7 @@ pub(crate) fn try_reconcile_undelegation( pending_undelegate: &PendingUndelegate, ) -> Result { let delegation_map = storage::delegations(); + let mix_denom = mixnet_params_storage::mix_denom(storage)?; // debug_with_visibility(api, "Reconciling undelegations"); @@ -389,7 +394,7 @@ pub(crate) fn try_reconcile_undelegation( .as_ref() .unwrap_or(&pending_undelegate.delegate()) .to_string(), - amount: coins(total_funds.u128(), MIX_DENOM.base), + amount: coins(total_funds.u128(), mix_denom.clone()), }) } else { None @@ -401,10 +406,10 @@ pub(crate) fn try_reconcile_undelegation( let msg = Some(VestingContractExecuteMsg::TrackUndelegation { owner: pending_undelegate.delegate().as_str().to_string(), mix_identity: pending_undelegate.mix_identity(), - amount: Coin::new(total_funds.u128(), MIX_DENOM.base), + amount: Coin::new(total_funds.u128(), mix_denom.clone()), }); - wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?); + wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?); } let event = new_undelegation_event( @@ -468,6 +473,7 @@ mod tests { use cosmwasm_std::coins; use crate::support::tests; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers; use super::*; @@ -482,7 +488,7 @@ mod tests { fn stake_cant_be_empty() { assert_eq!( Err(ContractError::EmptyDelegation), - validate_delegation_stake(vec![]) + validate_delegation_stake(vec![], TEST_COIN_DENOM.to_string()) ) } @@ -490,19 +496,24 @@ mod tests { fn stake_must_have_single_coin_type() { assert_eq!( Err(ContractError::MultipleDenoms), - validate_delegation_stake(vec![ - coin(123, MIX_DENOM.base), - coin(123, "BTC"), - coin(123, "DOGE") - ]) + validate_delegation_stake( + vec![ + coin(123, TEST_COIN_DENOM), + coin(123, "BTC"), + coin(123, "DOGE") + ], + TEST_COIN_DENOM.to_string() + ) ) } #[test] fn stake_coin_must_be_of_correct_type() { assert_eq!( - Err(ContractError::WrongDenom {}), - validate_delegation_stake(coins(123, "DOGE")) + Err(ContractError::WrongDenom { + mix_denom: TEST_COIN_DENOM.to_string() + }), + validate_delegation_stake(coins(123, "DOGE"), TEST_COIN_DENOM.to_string()) ) } @@ -510,16 +521,28 @@ mod tests { fn stake_coin_must_have_value_greater_than_zero() { assert_eq!( Err(ContractError::EmptyDelegation), - validate_delegation_stake(coins(0, MIX_DENOM.base)) + validate_delegation_stake(coins(0, TEST_COIN_DENOM), TEST_COIN_DENOM.to_string()) ) } #[test] fn stake_can_have_any_positive_value() { // this might change in the future, but right now an arbitrary (positive) value can be delegated - assert!(validate_delegation_stake(coins(1, MIX_DENOM.base)).is_ok()); - assert!(validate_delegation_stake(coins(123, MIX_DENOM.base)).is_ok()); - assert!(validate_delegation_stake(coins(10000000000, MIX_DENOM.base)).is_ok()); + assert!(validate_delegation_stake( + coins(1, TEST_COIN_DENOM), + TEST_COIN_DENOM.to_string() + ) + .is_ok()); + assert!(validate_delegation_stake( + coins(123, TEST_COIN_DENOM), + TEST_COIN_DENOM.to_string() + ) + .is_ok()); + assert!(validate_delegation_stake( + coins(10000000000, TEST_COIN_DENOM), + TEST_COIN_DENOM.to_string() + ) + .is_ok()); } } @@ -542,7 +565,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("sender", &coins(123, MIX_DENOM.base)), + mock_info("sender", &coins(123, TEST_COIN_DENOM)), "non-existent-mix-identity".into(), ) ); @@ -558,7 +581,7 @@ mod tests { deps.as_mut(), ); let delegation_owner = Addr::unchecked("sender"); - let delegation = coin(123, MIX_DENOM.base); + let delegation = coin(123, TEST_COIN_DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), @@ -615,7 +638,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)), + mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)), identity, ) ); @@ -636,7 +659,7 @@ mod tests { tests::fixtures::good_mixnode_pledge(), deps.as_mut(), ); - let delegation = coin(123, MIX_DENOM.base); + let delegation = coin(123, TEST_COIN_DENOM); let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_mixnode( deps.as_mut(), @@ -686,8 +709,8 @@ mod tests { deps.as_mut(), ); let delegation_owner = Addr::unchecked("sender"); - let delegation1 = coin(100, MIX_DENOM.base); - let delegation2 = coin(50, MIX_DENOM.base); + let delegation1 = coin(100, TEST_COIN_DENOM); + let delegation2 = coin(50, TEST_COIN_DENOM); let mut env = mock_env(); @@ -714,7 +737,7 @@ mod tests { // let expected = Delegation::new( // delegation_owner.clone(), // identity.clone(), - // coin(delegation1.amount.u128() + delegation2.amount.u128(), MIX_DENOM.base), + // coin(delegation1.amount.u128() + delegation2.amount.u128(), TEST_COIN_DENOM), // mock_env().block.height, // None, // ); @@ -749,7 +772,7 @@ mod tests { deps.as_mut(), ); let delegation_owner = Addr::unchecked("sender"); - let delegation = coin(100, MIX_DENOM.base); + let delegation = coin(100, TEST_COIN_DENOM); let env1 = mock_env(); let mut env2 = mock_env(); let initial_height = env1.block.height; @@ -814,8 +837,8 @@ mod tests { ); let delegation_owner1 = Addr::unchecked("sender1"); let delegation_owner2 = Addr::unchecked("sender2"); - let delegation1 = coin(100, MIX_DENOM.base); - let delegation2 = coin(120, MIX_DENOM.base); + let delegation1 = coin(100, TEST_COIN_DENOM); + let delegation2 = coin(120, TEST_COIN_DENOM); let env1 = mock_env(); let mut env2 = mock_env(); let initial_height = env1.block.height; @@ -890,7 +913,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(100, MIX_DENOM.base)), + mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)), identity.clone(), ) .unwrap(); @@ -902,7 +925,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(50, MIX_DENOM.base)), + mock_info(delegation_owner.as_str(), &coins(50, TEST_COIN_DENOM)), identity, ) ); @@ -927,14 +950,14 @@ mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)), + mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)), identity1.clone(), ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &coins(42, MIX_DENOM.base)), + mock_info(delegation_owner.as_str(), &coins(42, TEST_COIN_DENOM)), identity2.clone(), ) .is_ok()); @@ -944,7 +967,7 @@ mod tests { let expected1 = Delegation::new( delegation_owner.clone(), identity1.clone(), - coin(123, MIX_DENOM.base), + coin(123, TEST_COIN_DENOM), mock_env().block.height, None, ); @@ -952,7 +975,7 @@ mod tests { let expected2 = Delegation::new( delegation_owner.clone(), identity2.clone(), - coin(42, MIX_DENOM.base), + coin(42, TEST_COIN_DENOM), mock_env().block.height, None, ); @@ -988,8 +1011,8 @@ mod tests { tests::fixtures::good_mixnode_pledge(), deps.as_mut(), ); - let delegation1 = coin(123, MIX_DENOM.base); - let delegation2 = coin(234, MIX_DENOM.base); + let delegation1 = coin(123, TEST_COIN_DENOM); + let delegation2 = coin(234, TEST_COIN_DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), @@ -1025,7 +1048,7 @@ mod tests { deps.as_mut(), ); let delegation_owner = Addr::unchecked("sender"); - let delegation_amount = coin(100, MIX_DENOM.base); + let delegation_amount = coin(100, TEST_COIN_DENOM); try_delegate_to_mixnode( deps.as_mut(), mock_env(), @@ -1113,7 +1136,7 @@ mod tests { // try_delegate_to_mixnode( // deps.as_mut(), // mock_env(), - // mock_info(delegation_owner.as_str(), &coins(100, MIX_DENOM.base)), + // mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)), // identity.clone(), // ) // .unwrap(); @@ -1132,7 +1155,7 @@ mod tests { // let expected_response = Response::new() // .add_message(BankMsg::Send { // to_address: delegation_owner.clone().into(), - // amount: coins(100, MIX_DENOM.base), + // amount: coins(100, TEST_COIN_DENOM), // }) // .add_event(new_undelegation_event( // &delegation_owner, @@ -1183,7 +1206,7 @@ mod tests { // try_delegate_to_mixnode( // deps.as_mut(), // mock_env(), - // mock_info(delegation_owner.as_str(), &coins(100, MIX_DENOM.base)), + // mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)), // identity.clone(), // ) // .unwrap(); @@ -1202,7 +1225,7 @@ mod tests { // let expected_response = Response::new() // .add_message(BankMsg::Send { // to_address: delegation_owner.clone().into(), - // amount: coins(100, MIX_DENOM.base), + // amount: coins(100, TEST_COIN_DENOM), // }) // .add_event(new_undelegation_event( // &delegation_owner, @@ -1246,8 +1269,8 @@ mod tests { ); let delegation_owner1 = Addr::unchecked("sender1"); let delegation_owner2 = Addr::unchecked("sender2"); - let delegation1 = coin(123, MIX_DENOM.base); - let delegation2 = coin(234, MIX_DENOM.base); + let delegation1 = coin(123, TEST_COIN_DENOM); + let delegation2 = coin(234, TEST_COIN_DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index c4d4c85fda..25bdd91f3f 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use config::defaults::MIX_DENOM; use cosmwasm_std::{Addr, StdError}; use mixnet_contract_common::{error::MixnetContractError, IdentityKey}; use thiserror::Error; @@ -33,14 +32,14 @@ pub enum ContractError { #[error("MIXNET ({}): Unauthorized", line!())] Unauthorized, - #[error("MIXNET ({}): Wrong coin denomination, you must send {}", line!(), MIX_DENOM.base)] - WrongDenom, + #[error("MIXNET ({}): Wrong coin denomination, you must send {mix_denom}", line!())] + WrongDenom { mix_denom: String }, #[error("MIXNET ({}): Received multiple coin types during staking", line!())] MultipleDenoms, - #[error("MIXNET ({}): No coin was sent for the bonding, you must send {}", line!(), MIX_DENOM.base)] - NoBondFound, + #[error("MIXNET ({}): No coin was sent for the bonding, you must send {mix_denom}", line!())] + NoBondFound { mix_denom: String }, #[error("MIXNET ({}): Provided active set size is bigger than the rewarded set", line!())] InvalidActiveSetSize, diff --git a/contracts/mixnet/src/gateways/storage.rs b/contracts/mixnet/src/gateways/storage.rs index 6b79f5ed4e..89786a3019 100644 --- a/contracts/mixnet/src/gateways/storage.rs +++ b/contracts/mixnet/src/gateways/storage.rs @@ -36,7 +36,7 @@ pub(crate) fn gateways<'a>() -> IndexedMap<'a, IdentityKeyRef<'a>, GatewayBond, mod tests { use super::super::storage; use crate::support::tests; - use config::defaults::MIX_DENOM; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::StdResult; use cosmwasm_std::Storage; @@ -84,7 +84,7 @@ mod tests { let pledge_amount = 1000; let gateway_bond = GatewayBond { - pledge_amount: coin(pledge_amount, MIX_DENOM.base), + pledge_amount: coin(pledge_amount, TEST_COIN_DENOM), owner: node_owner, block_height: 12_345, gateway: Gateway { diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index 76689422bd..6315a2b2e2 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -5,7 +5,6 @@ use super::storage; use crate::error::ContractError; use crate::mixnet_contract_settings::storage as mixnet_params_storage; use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature}; -use config::defaults::MIX_DENOM; use cosmwasm_std::{ wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128, }; @@ -26,7 +25,8 @@ pub fn try_add_gateway( .load(deps.storage)? .params .minimum_mixnode_pledge; - let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?; + let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?; + let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?; _try_add_gateway( deps, @@ -52,7 +52,8 @@ pub fn try_add_gateway_on_behalf( .load(deps.storage)? .params .minimum_mixnode_pledge; - let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?; + let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?; + let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?; let proxy = info.sender; _try_add_gateway( @@ -138,6 +139,7 @@ pub(crate) fn _try_remove_gateway( proxy: Option, ) -> Result { let owner = deps.api.addr_validate(owner)?; + let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?; // try to find the node of the sender let gateway_bond = match storage::gateways() .idx @@ -177,7 +179,7 @@ pub(crate) fn _try_remove_gateway( amount: gateway_bond.pledge_amount(), }; - let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?; response = response.add_message(track_unbond_message); } @@ -192,10 +194,11 @@ pub(crate) fn _try_remove_gateway( fn validate_gateway_pledge( mut pledge: Vec, minimum_pledge: Uint128, + mix_denom: String, ) -> Result { // check if anything was put as bond if pledge.is_empty() { - return Err(ContractError::NoBondFound); + return Err(ContractError::NoBondFound { mix_denom }); } if pledge.len() > 1 { @@ -203,8 +206,8 @@ fn validate_gateway_pledge( } // check that the denomination is correct - if pledge[0].denom != MIX_DENOM.base { - return Err(ContractError::WrongDenom {}); + if pledge[0].denom != mix_denom { + return Err(ContractError::WrongDenom { mix_denom }); } // check that we have at least 100 coins in our pledge @@ -225,8 +228,8 @@ pub mod tests { use crate::error::ContractError; use crate::gateways::transactions::validate_gateway_pledge; use crate::support::tests; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers; - use config::defaults::MIX_DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, BankMsg, Response}; use cosmwasm_std::{from_binary, Addr, Uint128}; @@ -238,7 +241,7 @@ pub mod tests { // if we fail validation (by say not sending enough funds let insufficient_bond = Into::::into(INITIAL_GATEWAY_PLEDGE) - 1; - let info = mock_info("anyone", &coins(insufficient_bond, MIX_DENOM.base)); + let info = mock_info("anyone", &coins(insufficient_bond, TEST_COIN_DENOM)); let (msg, _) = tests::messages::valid_bond_gateway_msg("anyone"); // we are informed that we didn't send enough funds @@ -544,13 +547,26 @@ pub mod tests { #[test] fn validating_gateway_bond() { // you must send SOME funds - let result = validate_gateway_pledge(Vec::new(), INITIAL_GATEWAY_PLEDGE); - assert_eq!(result, Err(ContractError::NoBondFound)); + let result = validate_gateway_pledge( + Vec::new(), + INITIAL_GATEWAY_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::NoBondFound { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); // you must send at least 100 coins... let mut bond = tests::fixtures::good_gateway_pledge(); bond[0].amount = INITIAL_GATEWAY_PLEDGE.checked_sub(Uint128::new(1)).unwrap(); - let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE); + let result = validate_gateway_pledge( + bond.clone(), + INITIAL_GATEWAY_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); assert_eq!( result, Err(ContractError::InsufficientGatewayBond { @@ -562,18 +578,40 @@ pub mod tests { // more than that is still fine let mut bond = tests::fixtures::good_gateway_pledge(); bond[0].amount = INITIAL_GATEWAY_PLEDGE + Uint128::new(1); - let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE); + let result = validate_gateway_pledge( + bond.clone(), + INITIAL_GATEWAY_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); assert!(result.is_ok()); // it must be sent in the defined denom! let mut bond = tests::fixtures::good_gateway_pledge(); bond[0].denom = "baddenom".to_string(); - let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE); - assert_eq!(result, Err(ContractError::WrongDenom {})); + let result = validate_gateway_pledge( + bond.clone(), + INITIAL_GATEWAY_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::WrongDenom { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); let mut bond = tests::fixtures::good_gateway_pledge(); bond[0].denom = "foomp".to_string(); - let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE); - assert_eq!(result, Err(ContractError::WrongDenom {})); + let result = validate_gateway_pledge( + bond.clone(), + INITIAL_GATEWAY_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::WrongDenom { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); } } diff --git a/contracts/mixnet/src/mixnet_contract_settings/models.rs b/contracts/mixnet/src/mixnet_contract_settings/models.rs index 105475a2e1..d271972be7 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/models.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/models.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct ContractState { pub owner: Addr, // only the owner account can update state + pub mix_denom: String, pub rewarding_validator_address: Addr, pub params: ContractStateParams, } diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index 9ed2ac8a2e..845be0dc67 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -45,6 +45,7 @@ pub(crate) mod tests { let dummy_state = ContractState { owner: Addr::unchecked("someowner"), + mix_denom: String::from("unym"), rewarding_validator_address: Addr::unchecked("monitor"), params: ContractStateParams { minimum_mixnode_pledge: 123u128.into(), diff --git a/contracts/mixnet/src/mixnet_contract_settings/storage.rs b/contracts/mixnet/src/mixnet_contract_settings/storage.rs index 94f6a73db2..5f4c736d13 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/storage.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/storage.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::error::ContractError; use crate::mixnet_contract_settings::models::ContractState; use cosmwasm_std::StdResult; use cosmwasm_std::Storage; @@ -11,10 +10,14 @@ use mixnet_contract_common::{Layer, LayerDistribution}; pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new("config"); pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new("layers"); -pub fn rewarding_validator_address(storage: &dyn Storage) -> Result { - Ok(CONTRACT_STATE +pub fn rewarding_validator_address(storage: &dyn Storage) -> StdResult { + CONTRACT_STATE .load(storage) - .map(|state| state.rewarding_validator_address.to_string())?) + .map(|state| state.rewarding_validator_address.to_string()) +} + +pub fn mix_denom(storage: &dyn Storage) -> StdResult { + CONTRACT_STATE.load(storage).map(|state| state.mix_denom) } pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> { diff --git a/contracts/mixnet/src/mixnodes/storage.rs b/contracts/mixnet/src/mixnodes/storage.rs index a45e8c74a2..d2722879a9 100644 --- a/contracts/mixnet/src/mixnodes/storage.rs +++ b/contracts/mixnet/src/mixnodes/storage.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use config::defaults::MIX_DENOM; use cosmwasm_std::{StdResult, Storage, Uint128}; use cw_storage_plus::{Index, IndexList, IndexedSnapshotMap, Map, Strategy, UniqueIndex}; use mixnet_contract_common::{ @@ -11,6 +10,8 @@ use mixnet_contract_common::{SphinxKey, U128}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; +use crate::mixnet_contract_settings::storage::mix_denom; + // storage prefixes const TOTAL_DELEGATION_NAMESPACE: &str = "td"; const MIXNODES_PK_NAMESPACE: &str = "mn"; @@ -171,7 +172,7 @@ pub(crate) fn read_full_mixnode_bond( Ok(Some(MixNodeBond { pledge_amount: stored_bond.pledge_amount, total_delegation: Coin { - denom: MIX_DENOM.base.to_owned(), + denom: mix_denom(storage)?, amount: total_delegation.unwrap_or_default(), }, owner: stored_bond.owner, @@ -190,7 +191,8 @@ mod tests { use super::super::storage; use super::*; use crate::support::tests; - use config::defaults::MIX_DENOM; + use crate::support::tests::fixtures::TEST_COIN_DENOM; + use crate::support::tests::test_helpers::init_contract; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{coin, Addr, Uint128}; use mixnet_contract_common::{IdentityKey, MixNode}; @@ -211,7 +213,7 @@ mod tests { #[test] fn reading_mixnode_bond() { - let mut mock_storage = MockStorage::new(); + let mut mock_storage = init_contract().storage; let node_owner: Addr = Addr::unchecked("node-owner"); let node_identity: IdentityKey = "nodeidentity".into(); @@ -223,7 +225,7 @@ mod tests { let pledge_value = 1000000000; let mixnode_bond = StoredMixnodeBond { - pledge_amount: coin(pledge_value, MIX_DENOM.base), + pledge_amount: coin(pledge_value, TEST_COIN_DENOM), owner: node_owner, layer: Layer::One, block_height: 12_345, diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index e0ff269665..9f0e91c890 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -3,11 +3,10 @@ use super::storage::{self, LAST_PM_UPDATE_TIME}; use crate::error::ContractError; -use crate::mixnet_contract_settings::storage as mixnet_params_storage; +use crate::mixnet_contract_settings::storage::{self as mixnet_params_storage, mix_denom}; use crate::mixnodes::layer_queries::query_layer_distribution; use crate::mixnodes::storage::StoredMixnodeBond; use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature}; -use config::defaults::MIX_DENOM; use cosmwasm_std::{ wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128, }; @@ -48,7 +47,7 @@ pub fn try_add_mixnode( .load(deps.storage)? .params .minimum_mixnode_pledge; - let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?; + let pledge = validate_mixnode_pledge(info.funds, minimum_pledge, mix_denom(deps.storage)?)?; _try_add_mixnode( deps, @@ -74,7 +73,7 @@ pub fn try_add_mixnode_on_behalf( .load(deps.storage)? .params .minimum_mixnode_pledge; - let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?; + let pledge = validate_mixnode_pledge(info.funds, minimum_pledge, mix_denom(deps.storage)?)?; let proxy = info.sender; _try_add_mixnode( @@ -239,7 +238,8 @@ pub(crate) fn _try_remove_mixnode( amount: mixnode_bond.pledge_amount(), }; - let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + let track_unbond_message = + wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(deps.storage)?)])?; response = response.add_message(track_unbond_message); } @@ -288,6 +288,7 @@ pub(crate) fn _try_update_mixnode_config( .item(deps.storage, owner.clone())? .ok_or(ContractError::NoAssociatedMixNodeBond { owner })? .1; + let mix_denom = mix_denom(deps.storage)?; if proxy != mixnode_bond.proxy { return Err(ContractError::ProxyMismatch { @@ -329,7 +330,9 @@ pub(crate) fn _try_update_mixnode_config( mixnode_bond.block_height = env.block.height; mixnode_bond }) - .ok_or(ContractError::NoBondFound) + .ok_or(ContractError::NoBondFound { + mix_denom: mix_denom.clone(), + }) }, )?; @@ -342,7 +345,7 @@ pub(crate) fn _try_update_mixnode_config( // and they could potentially leak 1 unym per transaction, altough I'm pretty sure transaction fees make that silly. let return_one_ucoint = BankMsg::Send { to_address: proxy.as_str().to_string(), - amount: vec![one_ucoin()], + amount: vec![one_ucoin(mix_denom)], }; response = response.add_message(return_one_ucoint); } @@ -353,10 +356,11 @@ pub(crate) fn _try_update_mixnode_config( fn validate_mixnode_pledge( mut pledge: Vec, minimum_pledge: Uint128, + mix_denom: String, ) -> Result { // check if anything was put as bond if pledge.is_empty() { - return Err(ContractError::NoBondFound); + return Err(ContractError::NoBondFound { mix_denom }); } if pledge.len() > 1 { @@ -364,8 +368,8 @@ fn validate_mixnode_pledge( } // check that the denomination is correct - if pledge[0].denom != MIX_DENOM.base { - return Err(ContractError::WrongDenom {}); + if pledge[0].denom != mix_denom { + return Err(ContractError::WrongDenom { mix_denom }); } // check that we have at least MIXNODE_BOND coins in our pledge @@ -386,8 +390,8 @@ pub mod tests { use crate::error::ContractError; use crate::mixnodes::transactions::validate_mixnode_pledge; use crate::support::tests; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers; - use config::defaults::MIX_DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, BankMsg, Response}; use cosmwasm_std::{from_binary, Addr, Uint128}; @@ -402,7 +406,7 @@ pub mod tests { // if we don't send enough funds let insufficient_bond = Into::::into(INITIAL_MIXNODE_PLEDGE) - 1; - let info = mock_info("anyone", &coins(insufficient_bond, MIX_DENOM.base)); + let info = mock_info("anyone", &coins(insufficient_bond, TEST_COIN_DENOM)); let (msg, _) = tests::messages::valid_bond_mixnode_msg("anyone"); // we are informed that we didn't send enough funds @@ -808,13 +812,26 @@ pub mod tests { #[test] fn validating_mixnode_bond() { // you must send SOME funds - let result = validate_mixnode_pledge(Vec::new(), INITIAL_MIXNODE_PLEDGE); - assert_eq!(result, Err(ContractError::NoBondFound)); + let result = validate_mixnode_pledge( + Vec::new(), + INITIAL_MIXNODE_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::NoBondFound { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); // you must send at least 100 coins... let mut bond = tests::fixtures::good_mixnode_pledge(); bond[0].amount = INITIAL_MIXNODE_PLEDGE.checked_sub(Uint128::new(1)).unwrap(); - let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE); + let result = validate_mixnode_pledge( + bond.clone(), + INITIAL_MIXNODE_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); assert_eq!( result, Err(ContractError::InsufficientMixNodeBond { @@ -826,19 +843,41 @@ pub mod tests { // more than that is still fine let mut bond = tests::fixtures::good_mixnode_pledge(); bond[0].amount = INITIAL_MIXNODE_PLEDGE + Uint128::new(1); - let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE); + let result = validate_mixnode_pledge( + bond.clone(), + INITIAL_MIXNODE_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); assert!(result.is_ok()); // it must be sent in the defined denom! let mut bond = tests::fixtures::good_mixnode_pledge(); bond[0].denom = "baddenom".to_string(); - let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE); - assert_eq!(result, Err(ContractError::WrongDenom {})); + let result = validate_mixnode_pledge( + bond.clone(), + INITIAL_MIXNODE_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::WrongDenom { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); let mut bond = tests::fixtures::good_mixnode_pledge(); bond[0].denom = "foomp".to_string(); - let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE); - assert_eq!(result, Err(ContractError::WrongDenom {})); + let result = validate_mixnode_pledge( + bond.clone(), + INITIAL_MIXNODE_PLEDGE, + TEST_COIN_DENOM.to_string(), + ); + assert_eq!( + result, + Err(ContractError::WrongDenom { + mix_denom: TEST_COIN_DENOM.to_string() + }) + ); } #[test] diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index 035382ddb7..9abcd66a0f 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -83,7 +83,7 @@ pub(crate) mod tests { use crate::delegations::transactions::try_delegate_to_mixnode; use crate::interval::storage::{save_epoch, save_epoch_reward_params}; use crate::rewards::transactions::try_reward_mixnode; - use config::defaults::MIX_DENOM; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use cosmwasm_std::{coin, Addr}; use mixnet_contract_common::{ Interval, RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT, @@ -187,7 +187,7 @@ pub(crate) mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &[coin(200_000000, MIX_DENOM.base)], + &[coin(200_000000, TEST_COIN_DENOM)], ), node_identity.clone(), ) diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 927390e8fd..226bb822b2 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -10,11 +10,11 @@ use crate::contract::debug_with_visibility; use crate::delegations::storage as delegations_storage; use crate::delegations::transactions::_try_delegate_to_mixnode; use crate::error::ContractError; +use crate::mixnet_contract_settings::storage::mix_denom; use crate::mixnodes::storage::mixnodes; use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond}; use crate::rewards::helpers; use crate::support::helpers::{is_authorized, operator_cost_at_epoch}; -use config::defaults::MIX_DENOM; use cosmwasm_std::{ coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128, @@ -69,6 +69,7 @@ fn _try_claim_operator_reward( proxy: Option, ) -> Result { let owner = api.addr_validate(owner)?; + let mix_denom = mix_denom(storage)?; let bond = match crate::mixnodes::storage::mixnodes() .idx @@ -105,7 +106,7 @@ fn _try_claim_operator_reward( let return_tokens = BankMsg::Send { to_address: proxy.as_ref().unwrap_or(&owner).to_string(), - amount: coins(reward.u128(), MIX_DENOM.base), + amount: coins(reward.u128(), mix_denom.clone()), }; let mut response = Response::default() @@ -115,10 +116,10 @@ fn _try_claim_operator_reward( if let Some(proxy) = proxy { let msg = Some(VestingContractExecuteMsg::TrackReward { address: owner.to_string(), - amount: Coin::new(reward.u128(), MIX_DENOM.base), + amount: Coin::new(reward.u128(), mix_denom.clone()), }); - let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?; response = response.add_message(wasm_msg); } @@ -134,6 +135,7 @@ pub fn _try_claim_delegator_reward( proxy: Option, ) -> Result { let owner = api.addr_validate(owner)?; + let mix_denom = mix_denom(storage)?; let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref()); let reward = calculate_delegator_reward(storage, api, key.clone(), mix_identity)?; @@ -153,7 +155,7 @@ pub fn _try_claim_delegator_reward( let return_tokens = BankMsg::Send { to_address: proxy.as_ref().unwrap_or(&owner).to_string(), - amount: coins(reward.u128(), MIX_DENOM.base), + amount: coins(reward.u128(), mix_denom.clone()), }; let mut response = @@ -169,10 +171,10 @@ pub fn _try_claim_delegator_reward( if let Some(proxy) = proxy { let msg = Some(VestingContractExecuteMsg::TrackReward { address: owner.to_string(), - amount: Coin::new(reward.u128(), MIX_DENOM.base), + amount: Coin::new(reward.u128(), mix_denom.clone()), }); - let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?; + let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?; response = response.add_message(wasm_msg); } @@ -403,6 +405,7 @@ pub fn _try_compound_delegator_reward( proxy: Option, ) -> Result { let delegation_map = crate::delegations::storage::delegations(); + let mix_denom = mix_denom(deps.storage)?; let key = mixnet_contract_common::delegation::generate_storage_key( &deps.api.addr_validate(owner_address)?, @@ -438,7 +441,7 @@ pub fn _try_compound_delegator_reward( owner_address, Coin { amount: compounded_delegation, - denom: MIX_DENOM.base.to_string(), + denom: mix_denom, }, proxy, )?; @@ -724,9 +727,9 @@ pub mod tests { use crate::rewards::transactions::try_reward_mixnode; use crate::support::helpers::{current_operator_epoch_cost, epochs_in_interval}; use crate::support::tests; + use crate::support::tests::fixtures::TEST_COIN_DENOM; use crate::support::tests::test_helpers; use az::CheckedCast; - use config::defaults::MIX_DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, coins, Addr, StdError, Timestamp, Uint128}; use mixnet_contract_common::events::{ @@ -744,7 +747,7 @@ pub mod tests { let mut deps = test_helpers::init_contract(); let mut env = mock_env(); let sender = rewarding_validator_address(&deps.storage).unwrap(); - let info = mock_info(&sender, &coins(1000, MIX_DENOM.base)); + let info = mock_info(&sender, &coins(1000, TEST_COIN_DENOM)); crate::interval::transactions::init_epoch(&mut deps.storage, env.clone()).unwrap(); // bond the node @@ -885,7 +888,7 @@ pub mod tests { let initial_bond = 10000_000000; let initial_delegation = 20000_000000; let mixnode_bond = StoredMixnodeBond { - pledge_amount: coin(initial_bond, MIX_DENOM.base), + pledge_amount: coin(initial_bond, TEST_COIN_DENOM), owner: node_owner, layer: Layer::One, block_height: env.block.height, @@ -925,7 +928,7 @@ pub mod tests { &Delegation::new( Addr::unchecked("delegator"), node_identity.clone(), - coin(initial_delegation, MIX_DENOM.base), + coin(initial_delegation, TEST_COIN_DENOM), env.block.height, None, ), @@ -1109,7 +1112,7 @@ pub mod tests { assert_eq!(staking_supply, 100_000_000_000_000u128); let sender = Addr::unchecked("alice"); - let stake = coins(10_000_000_000, MIX_DENOM.base); + let stake = coins(10_000_000_000, TEST_COIN_DENOM); let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng()); let owner_signature = keypair @@ -1160,14 +1163,14 @@ pub mod tests { let node_owner: Addr = Addr::unchecked("johnny"); let node_identity_2 = test_helpers::add_mixnode( node_owner.as_str(), - coins(10_000_000_000, MIX_DENOM.base), + coins(10_000_000_000, TEST_COIN_DENOM), deps.as_mut(), ); try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]), + mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]), node_identity_1.clone(), ) .unwrap(); @@ -1175,7 +1178,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alice_d2", &[coin(2000_000000, MIX_DENOM.base)]), + mock_info("alice_d2", &[coin(2000_000000, TEST_COIN_DENOM)]), node_identity_1.clone(), ) .unwrap(); @@ -1183,7 +1186,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("bob_d1", &[coin(8000_000000, MIX_DENOM.base)]), + mock_info("bob_d1", &[coin(8000_000000, TEST_COIN_DENOM)]), node_identity_2.clone(), ) .unwrap(); @@ -1191,7 +1194,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("bob_d2", &[coin(2000_000000, MIX_DENOM.base)]), + mock_info("bob_d2", &[coin(2000_000000, TEST_COIN_DENOM)]), node_identity_2.clone(), ) .unwrap(); @@ -1199,14 +1202,14 @@ pub mod tests { let node_owner: Addr = Addr::unchecked("alicebob"); let node_identity_3 = test_helpers::add_mixnode( node_owner.as_str(), - coins(10_000_000_000 * 2, MIX_DENOM.base), + coins(10_000_000_000 * 2, TEST_COIN_DENOM), deps.as_mut(), ); try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alicebob_d1", &[coin(8000_000000 * 2, MIX_DENOM.base)]), + mock_info("alicebob_d1", &[coin(8000_000000 * 2, TEST_COIN_DENOM)]), node_identity_3.clone(), ) .unwrap(); @@ -1214,7 +1217,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alicebob_d2", &[coin(2000_000000 * 2, MIX_DENOM.base)]), + mock_info("alicebob_d2", &[coin(2000_000000 * 2, TEST_COIN_DENOM)]), node_identity_3.clone(), ) .unwrap(); @@ -1320,7 +1323,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), env.clone(), - mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]), + mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]), node_identity_1.clone(), ) .unwrap(); @@ -1559,14 +1562,14 @@ pub mod tests { let node_owner: Addr = Addr::unchecked("alice"); let node_identity = test_helpers::add_mixnode( node_owner.as_str(), - coins(10_000_000_000, MIX_DENOM.base), + coins(10_000_000_000, TEST_COIN_DENOM), deps.as_mut(), ); try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]), + mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]), node_identity.clone(), ) .unwrap(); @@ -1574,7 +1577,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info("alice_d2", &[coin(2000_000000, MIX_DENOM.base)]), + mock_info("alice_d2", &[coin(2000_000000, TEST_COIN_DENOM)]), node_identity.clone(), ) .unwrap(); @@ -1750,7 +1753,7 @@ pub mod tests { #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), - coins(10000_000_000, MIX_DENOM.base), + coins(10000_000_000, TEST_COIN_DENOM), deps.as_mut(), ); @@ -1777,7 +1780,7 @@ pub mod tests { #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), - coins(10000_000_000, MIX_DENOM.base), + coins(10000_000_000, TEST_COIN_DENOM), deps.as_mut(), ); @@ -1787,7 +1790,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &[coin(2000_000000, MIX_DENOM.base)], + &[coin(2000_000000, TEST_COIN_DENOM)], ), node_identity.clone(), ) diff --git a/contracts/mixnet/src/support/tests/fixtures.rs b/contracts/mixnet/src/support/tests/fixtures.rs index f8c5513c32..813a68e537 100644 --- a/contracts/mixnet/src/support/tests/fixtures.rs +++ b/contracts/mixnet/src/support/tests/fixtures.rs @@ -1,11 +1,13 @@ use crate::contract::INITIAL_MIXNODE_PLEDGE; use crate::mixnodes::storage as mixnodes_storage; use crate::{mixnodes::storage::StoredMixnodeBond, support::tests}; -use config::defaults::MIX_DENOM; use cosmwasm_std::{coin, Addr, Coin}; use mixnet_contract_common::reward_params::NodeRewardParams; use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode}; +pub const TEST_COIN_DENOM: &str = "unym"; +pub const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; + pub fn mix_node_fixture() -> MixNode { MixNode { host: "mix.node.org".to_string(), @@ -37,7 +39,7 @@ pub fn gateway_bond_fixture(owner: &str) -> GatewayBond { ..tests::fixtures::gateway_fixture() }; GatewayBond::new( - coin(50, MIX_DENOM.base), + coin(50, TEST_COIN_DENOM), Addr::unchecked(owner), 12_345, gateway, @@ -47,7 +49,7 @@ pub fn gateway_bond_fixture(owner: &str) -> GatewayBond { pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond { StoredMixnodeBond::new( - coin(50, MIX_DENOM.base), + coin(50, TEST_COIN_DENOM), Addr::unchecked(owner), Layer::One, 12_345, @@ -64,14 +66,14 @@ pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::Stor pub fn good_mixnode_pledge() -> Vec { vec![Coin { - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), amount: INITIAL_MIXNODE_PLEDGE, }] } pub fn good_gateway_pledge() -> Vec { vec![Coin { - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), amount: INITIAL_MIXNODE_PLEDGE, }] } diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index e8a1108317..19fe66dd35 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -17,7 +17,6 @@ pub mod test_helpers { use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::transactions::try_add_mixnode; use crate::support::tests; - use config::defaults::{DEFAULT_NETWORK, MIX_DENOM}; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; @@ -32,6 +31,9 @@ pub mod test_helpers { use mixnet_contract_common::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode}; use rand::thread_rng; + use super::fixtures::TEST_COIN_DENOM; + use super::fixtures::TEST_REWARDING_VALIDATOR_ADDRESS; + pub fn add_mixnode(sender: &str, stake: Vec, deps: DepsMut<'_>) -> String { let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng()); let owner_signature = keypair @@ -85,7 +87,8 @@ pub mod test_helpers { pub fn init_contract() -> OwnedDeps> { let mut deps = mock_dependencies(); 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 env = mock_env(); let info = mock_info("creator", &[]); @@ -111,7 +114,7 @@ pub mod test_helpers { let delegation = Delegation { owner: Addr::unchecked(owner.into()), node_identity: mix.into(), - amount: coin(12345, MIX_DENOM.base), + amount: coin(12345, TEST_COIN_DENOM), block_height: block_height, proxy: None, }; diff --git a/contracts/mixnet/src/support/tests/queries.rs b/contracts/mixnet/src/support/tests/queries.rs index c42dd9b267..e53caf148e 100644 --- a/contracts/mixnet/src/support/tests/queries.rs +++ b/contracts/mixnet/src/support/tests/queries.rs @@ -1,4 +1,3 @@ -use config::defaults::MIX_DENOM; use cosmwasm_std::{ from_binary, testing::{mock_env, MockApi, MockQuerier, MockStorage}, @@ -10,6 +9,8 @@ use mixnet_contract_common::{ use crate::contract::query; +use super::fixtures::TEST_COIN_DENOM; + pub fn get_mix_nodes(deps: &mut OwnedDeps) -> Vec { let result = query( deps.as_ref(), @@ -45,5 +46,5 @@ pub fn query_contract_balance( deps: OwnedDeps, ) -> Vec { let querier = deps.as_ref().querier; - vec![querier.query_balance(address, MIX_DENOM.base).unwrap()] + vec![querier.query_balance(address, TEST_COIN_DENOM).unwrap()] } diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index f1e76c09ac..0168eec05f 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -29,7 +29,6 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } -network-defaults = { path = "../../../common/network-defaults" } [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 3a9b7aadd0..c85d1362a7 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, BlockInfo, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, + to_binary, Binary, BlockInfo, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, Response, StdResult, }; @@ -16,7 +16,6 @@ use cw3_fixed_multisig::state::{next_id, Ballot, Proposal, Votes, BALLOTS, PROPO use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff}; use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; -use network_defaults::DEFAULT_NETWORK; use crate::error::ContractError; use crate::state::{Config, CONFIG}; @@ -40,11 +39,9 @@ pub fn instantiate( })?); // This might need to be changed via a migration, due to circular dependency // of deploying the two contracts - let coconut_bandwidth_addr = Addr::unchecked( - DEFAULT_NETWORK - .coconut_bandwidth_contract_address() - .ok_or(ContractError::InvalidCoconutBandwidth {})?, - ); + let coconut_bandwidth_addr = deps + .api + .addr_validate(&msg.coconut_bandwidth_contract_address)?; let total_weight = group_addr.total_weight(&deps.querier)?; msg.threshold.validate(total_weight)?; @@ -461,6 +458,8 @@ mod tests { const VOTER4: &str = "voter0004"; const VOTER5: &str = "voter0005"; const SOMEBODY: &str = "somebody"; + const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; fn member>(addr: T, weight: u64) -> Member { Member { @@ -513,9 +512,7 @@ mod tests { proposal: ExecuteMsg, voter: &str, ) -> AppResponse { - let proposer = DEFAULT_NETWORK - .coconut_bandwidth_contract_address() - .unwrap(); + let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(); let res = app .execute_contract(Addr::unchecked(proposer), flex_addr.clone(), &proposal, &[]) .unwrap(); @@ -541,6 +538,7 @@ mod tests { group_addr: group.to_string(), threshold, max_voting_period, + coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), }; app.instantiate_contract(flex_id, Addr::unchecked(OWNER), &msg, &[], "flex", None) .unwrap() @@ -576,9 +574,7 @@ mod tests { init_funds: Vec, multisig_as_group_admin: bool, ) -> (Addr, Addr) { - let coconut_bandwidth_contract = DEFAULT_NETWORK - .coconut_bandwidth_contract_address() - .unwrap(); + let coconut_bandwidth_contract = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(); // 1. Instantiate group contract with members (and OWNER as admin) and coconut bandwidth contract let members = vec![ member(coconut_bandwidth_contract, 0), @@ -658,6 +654,7 @@ mod tests { quorum: Decimal::percent(1), }, max_voting_period, + coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), }; let err = app .instantiate_contract( @@ -679,6 +676,7 @@ mod tests { group_addr: group_addr.to_string(), threshold: Threshold::AbsoluteCount { weight: 100 }, max_voting_period, + coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), }; let err = app .instantiate_contract( @@ -700,6 +698,7 @@ mod tests { group_addr: group_addr.to_string(), threshold: Threshold::AbsoluteCount { weight: 1 }, max_voting_period, + coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), }; let flex_addr = app .instantiate_contract( @@ -772,11 +771,7 @@ mod tests { }; let err = app .execute_contract( - Addr::unchecked( - DEFAULT_NETWORK - .coconut_bandwidth_contract_address() - .unwrap(), - ), + Addr::unchecked(TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string()), flex_addr.clone(), &proposal_wrong_exp, &[], @@ -851,9 +846,7 @@ mod tests { .unwrap_err(); assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - let proposer = DEFAULT_NETWORK - .coconut_bandwidth_contract_address() - .unwrap(); + let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(); let res = app .execute_contract( Addr::unchecked(&proposer), diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 5d371cbf8b..3f29aa7e94 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -16,7 +16,6 @@ crate-type = ["cdylib", "rlib"] [dependencies] mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } -config = { path = "../../common/config" } cosmwasm-std = { version = "1.0.0 "} cw-storage-plus = { version = "0.13.4", features = ["iterator"] } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 15626909fd..02e1b7ec42 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -1,13 +1,12 @@ use crate::errors::ContractError; use crate::storage::{ account_from_address, locked_pledge_cap, update_locked_pledge_cap, ADMIN, - MIXNET_CONTRACT_ADDRESS, + MIXNET_CONTRACT_ADDRESS, MIX_DENOM, }; use crate::traits::{ DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount, }; use crate::vesting::{populate_vesting_periods, Account}; -use config::defaults::MIX_DENOM; use cosmwasm_std::{ coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Timestamp, Uint128, @@ -36,6 +35,7 @@ pub fn instantiate( // ADMIN is set to the address that instantiated the contract, TODO: make this updatable ADMIN.save(deps.storage, &info.sender.to_string())?; MIXNET_CONTRACT_ADDRESS.save(deps.storage, &msg.mixnet_contract_address)?; + MIX_DENOM.save(deps.storage, &msg.mix_denom)?; Ok(Response::default()) } @@ -167,11 +167,9 @@ pub fn try_withdraw_vested_coins( info: MessageInfo, deps: DepsMut<'_>, ) -> Result { - if amount.denom != MIX_DENOM.base { - return Err(ContractError::WrongDenom( - amount.denom, - MIX_DENOM.base.to_string(), - )); + let mix_denom = MIX_DENOM.load(deps.storage)?; + if amount.denom != mix_denom { + return Err(ContractError::WrongDenom(amount.denom, mix_denom)); } let address = info.sender.clone(); @@ -245,7 +243,8 @@ pub fn try_bond_gateway( env: Env, deps: DepsMut<'_>, ) -> Result { - let pledge = validate_funds(&[amount])?; + let mix_denom = MIX_DENOM.load(deps.storage)?; + let pledge = validate_funds(&[amount], mix_denom)?; let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage) } @@ -285,7 +284,8 @@ pub fn try_bond_mixnode( env: Env, deps: DepsMut<'_>, ) -> Result { - let pledge = validate_funds(&[amount])?; + let mix_denom = MIX_DENOM.load(deps.storage)?; + let pledge = validate_funds(&[amount], mix_denom)?; let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_bond_mixnode(mix_node, owner_signature, pledge, &env, deps.storage) } @@ -345,7 +345,8 @@ fn try_delegate_to_mixnode( env: Env, deps: DepsMut<'_>, ) -> Result { - let amount = validate_funds(&[amount])?; + let mix_denom = MIX_DENOM.load(deps.storage)?; + let amount = validate_funds(&[amount], mix_denom)?; let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage) } @@ -396,6 +397,7 @@ fn try_create_periodic_vesting_account( if info.sender != ADMIN.load(deps.storage)? { return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); } + let mix_denom = MIX_DENOM.load(deps.storage)?; let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok(); if account_exists { @@ -406,7 +408,7 @@ fn try_create_periodic_vesting_account( let vesting_spec = vesting_spec.unwrap_or_default(); - let coin = validate_funds(&info.funds)?; + let coin = validate_funds(&info.funds, mix_denom)?; let owner_address = deps.api.addr_validate(owner_address)?; let staking_address = if let Some(staking_address) = staking_address { @@ -573,7 +575,7 @@ pub fn try_get_vested_coins( deps: Deps<'_>, ) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; - account.get_vested_coins(block_time, &env) + account.get_vested_coins(block_time, &env, deps.storage) } pub fn try_get_vesting_coins( @@ -583,7 +585,7 @@ pub fn try_get_vesting_coins( deps: Deps<'_>, ) -> Result { let account = account_from_address(vesting_account_address, deps.storage, deps.api)?; - account.get_vesting_coins(block_time, &env) + account.get_vesting_coins(block_time, &env, deps.storage) } pub fn try_get_start_time( @@ -630,7 +632,7 @@ pub fn try_get_delegated_vesting( account.get_delegated_vesting(block_time, &env, deps.storage) } -fn validate_funds(funds: &[Coin]) -> Result { +fn validate_funds(funds: &[Coin], mix_denom: String) -> Result { if funds.is_empty() || funds[0].amount.is_zero() { return Err(ContractError::EmptyFunds); } @@ -639,11 +641,8 @@ fn validate_funds(funds: &[Coin]) -> Result { return Err(ContractError::MultipleDenoms); } - if funds[0].denom != MIX_DENOM.base { - return Err(ContractError::WrongDenom( - funds[0].denom.clone(), - MIX_DENOM.base.to_string(), - )); + if funds[0].denom != mix_denom { + return Err(ContractError::WrongDenom(funds[0].denom.clone(), mix_denom)); } Ok(funds[0].clone()) diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index 9e845a58c6..400d90fc3e 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -17,6 +17,7 @@ const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw"); pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg"); pub const ADMIN: Item<'_, String> = Item::new("adm"); pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix"); +pub const MIX_DENOM: Item<'_, String> = Item::new("den"); pub const LOCKED_PLEDGE_CAP: Item<'_, Uint128> = Item::new("lck"); pub fn locked_pledge_cap(storage: &dyn Storage) -> Uint128 { diff --git a/contracts/vesting/src/support/tests.rs b/contracts/vesting/src/support/tests.rs index eee85a9070..8084e48739 100644 --- a/contracts/vesting/src/support/tests.rs +++ b/contracts/vesting/src/support/tests.rs @@ -2,15 +2,17 @@ pub mod helpers { use crate::contract::instantiate; use crate::vesting::{populate_vesting_periods, Account}; - use config::defaults::MIX_DENOM; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128}; use vesting_contract_common::messages::{InitMsg, VestingSpecification}; + pub const TEST_COIN_DENOM: &str = "unym"; + pub fn init_contract() -> OwnedDeps> { let mut deps = mock_dependencies(); let msg = InitMsg { mixnet_contract_address: "test".to_string(), + mix_denom: TEST_COIN_DENOM.to_string(), }; let env = mock_env(); let info = mock_info("admin", &[]); @@ -31,7 +33,7 @@ pub mod helpers { Some(Addr::unchecked("staking")), Coin { amount: Uint128::new(1_000_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, start_time_ts, periods, @@ -50,7 +52,7 @@ pub mod helpers { Some(Addr::unchecked("staking")), Coin { amount: Uint128::new(1_000_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, start_time, periods, diff --git a/contracts/vesting/src/traits/vesting_account.rs b/contracts/vesting/src/traits/vesting_account.rs index 840b5735f0..95f445114e 100644 --- a/contracts/vesting/src/traits/vesting_account.rs +++ b/contracts/vesting/src/traits/vesting_account.rs @@ -34,11 +34,13 @@ pub trait VestingAccount { &self, block_time: Option, env: &Env, + storage: &dyn Storage, ) -> Result; fn get_vesting_coins( &self, block_time: Option, env: &Env, + storage: &dyn Storage, ) -> Result; fn get_start_time(&self) -> Timestamp; diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 8dede6014e..48f32207ac 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -2,6 +2,7 @@ use crate::errors::ContractError; use crate::storage::locked_pledge_cap; use crate::storage::save_delegation; use crate::storage::MIXNET_CONTRACT_ADDRESS; +use crate::storage::MIX_DENOM; use crate::traits::DelegatingAccount; use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; @@ -28,7 +29,7 @@ impl DelegatingAccount for Account { let compound_delegator_reward_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new().add_message(compound_delegator_reward_msg)) @@ -46,7 +47,7 @@ impl DelegatingAccount for Account { let compound_delegator_reward_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new().add_message(compound_delegator_reward_msg)) @@ -118,7 +119,7 @@ impl DelegatingAccount for Account { let undelegate_from_mixnode = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new() diff --git a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs index 05b27f8786..33af1d6506 100644 --- a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs @@ -2,6 +2,7 @@ use super::PledgeData; use crate::errors::ContractError; use crate::storage::locked_pledge_cap; use crate::storage::MIXNET_CONTRACT_ADDRESS; +use crate::storage::MIX_DENOM; use crate::traits::GatewayBondingAccount; use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; @@ -76,7 +77,7 @@ impl GatewayBondingAccount for Account { let unbond_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new() diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index a031dc58f6..6312647a9a 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -1,6 +1,7 @@ use crate::errors::ContractError; use crate::storage::locked_pledge_cap; use crate::storage::MIXNET_CONTRACT_ADDRESS; +use crate::storage::MIX_DENOM; use crate::traits::MixnodeBondingAccount; use crate::traits::VestingAccount; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; @@ -24,7 +25,7 @@ impl MixnodeBondingAccount for Account { let compound_operator_reward_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new().add_message(compound_operator_reward_msg)) @@ -41,7 +42,7 @@ impl MixnodeBondingAccount for Account { let compound_operator_reward_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new().add_message(compound_operator_reward_msg)) @@ -60,7 +61,7 @@ impl MixnodeBondingAccount for Account { let update_mixnode_config_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new() @@ -130,7 +131,7 @@ impl MixnodeBondingAccount for Account { let unbond_msg = wasm_execute( MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, - vec![one_ucoin()], + vec![one_ucoin(MIX_DENOM.load(storage)?)], )?; Ok(Response::new() diff --git a/contracts/vesting/src/vesting/account/vesting_account.rs b/contracts/vesting/src/vesting/account/vesting_account.rs index a4ba2f062c..dc93627d1d 100644 --- a/contracts/vesting/src/vesting/account/vesting_account.rs +++ b/contracts/vesting/src/vesting/account/vesting_account.rs @@ -1,7 +1,6 @@ use crate::errors::ContractError; -use crate::storage::{delete_account, save_account, DELEGATIONS}; +use crate::storage::{delete_account, save_account, DELEGATIONS, MIX_DENOM}; use crate::traits::VestingAccount; -use config::defaults::MIX_DENOM; use cosmwasm_std::{Addr, Coin, Env, Order, Storage, Timestamp, Uint128}; use vesting_contract_common::{OriginalVestingResponse, Period}; @@ -33,7 +32,7 @@ impl VestingAccount for Account { // Returns 0 in case of underflow. Which is fine, as the amount of pledged and delegated tokens can be larger then vesting_coins due to rewards and vesting periods expiring Ok(Coin { amount: Uint128::new( - self.get_vesting_coins(block_time, env)? + self.get_vesting_coins(block_time, env, storage)? .amount .u128() .saturating_sub( @@ -47,7 +46,7 @@ impl VestingAccount for Account { .u128(), ), ), - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } @@ -63,7 +62,7 @@ impl VestingAccount for Account { .u128() .saturating_sub(self.locked_coins(block_time, env, storage)?.amount.u128()), ), - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } @@ -71,22 +70,24 @@ impl VestingAccount for Account { &self, block_time: Option, env: &Env, + storage: &dyn Storage, ) -> Result { let block_time = block_time.unwrap_or(env.block.time); let period = self.get_current_vesting_period(block_time); + let denom = MIX_DENOM.load(storage)?; let amount = match period { Period::Before => Coin { amount: Uint128::new(0), - denom: MIX_DENOM.base.to_string(), + denom, }, Period::In(idx) => Coin { amount: Uint128::new(self.tokens_per_period()? * idx as u128), - denom: MIX_DENOM.base.to_string(), + denom, }, Period::After => Coin { amount: self.coin.amount, - denom: MIX_DENOM.base.to_string(), + denom, }, }; Ok(amount) @@ -96,11 +97,12 @@ impl VestingAccount for Account { &self, block_time: Option, env: &Env, + storage: &dyn Storage, ) -> Result { Ok(Coin { amount: self.get_original_vesting().amount().amount - - self.get_vested_coins(block_time, env)?.amount, - denom: MIX_DENOM.base.to_string(), + - self.get_vested_coins(block_time, env, storage)?.amount, + denom: MIX_DENOM.load(storage)?, }) } @@ -130,7 +132,7 @@ impl VestingAccount for Account { let period = self.get_current_vesting_period(block_time); let withdrawn = self.load_withdrawn(storage)?; let max_available = self - .get_vested_coins(Some(block_time), env)? + .get_vested_coins(Some(block_time), env, storage)? .amount .saturating_sub(withdrawn); let start_time = match period { @@ -152,7 +154,7 @@ impl VestingAccount for Account { Ok(Coin { amount, - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } @@ -170,7 +172,7 @@ impl VestingAccount for Account { Ok(Coin { amount, - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } @@ -182,7 +184,7 @@ impl VestingAccount for Account { ) -> Result { let block_time = block_time.unwrap_or(env.block.time); let period = self.get_current_vesting_period(block_time); - let max_vested = self.get_vested_coins(Some(block_time), env)?; + let max_vested = self.get_vested_coins(Some(block_time), env, storage)?; let start_time = match period { Period::Before => 0, Period::After => u64::MAX, @@ -206,7 +208,7 @@ impl VestingAccount for Account { Ok(Coin { amount, - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } @@ -226,12 +228,12 @@ impl VestingAccount for Account { let amount = bond.amount().amount - bonded_free.amount; Ok(Coin { amount, - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } else { Ok(Coin { amount: Uint128::zero(), - denom: MIX_DENOM.base.to_string(), + denom: MIX_DENOM.load(storage)?, }) } } diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 0ac5ba7562..9dc389864d 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -39,12 +39,11 @@ mod tests { use crate::contract::execute; use crate::storage::load_account; use crate::support::tests::helpers::{ - init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, + init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM, }; use crate::traits::DelegatingAccount; use crate::traits::VestingAccount; use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount}; - use config::defaults::MIX_DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128}; use mixnet_contract_common::{Gateway, MixNode}; @@ -55,7 +54,7 @@ mod tests { fn test_account_creation() { let mut deps = init_contract(); let env = mock_env(); - let info = mock_info("not_admin", &coins(1_000_000_000_000, MIX_DENOM.base)); + let info = mock_info("not_admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); let msg = ExecuteMsg::CreateAccount { owner_address: "owner".to_string(), staking_address: Some("staking".to_string()), @@ -65,7 +64,7 @@ mod tests { let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); assert!(response.is_err()); - let info = mock_info("admin", &coins(1_000_000_000_000, MIX_DENOM.base)); + let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM)); let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); let created_account = load_account(&Addr::unchecked("owner"), &deps.storage) .unwrap() @@ -131,7 +130,7 @@ mod tests { let msg = ExecuteMsg::WithdrawVestedCoins { amount: Coin { amount: Uint128::new(1), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, }; let info = mock_info("new_owner", &[]); @@ -186,8 +185,12 @@ mod tests { Timestamp::from_seconds(account.start_time().seconds() + vesting_period + 1); let current_period = account.get_current_vesting_period(block_time); assert_eq!(current_period, Period::In(1)); - let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); - let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); + let vested_coins = account + .get_vested_coins(Some(block_time), &env, &deps.storage) + .unwrap(); + let vesting_coins = account + .get_vesting_coins(Some(block_time), &env, &deps.storage) + .unwrap(); assert_eq!( vested_coins.amount, Uint128::new( @@ -207,8 +210,12 @@ mod tests { Timestamp::from_seconds(account.start_time().seconds() + 5 * vesting_period + 1); let current_period = account.get_current_vesting_period(block_time); assert_eq!(current_period, Period::In(5)); - let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); - let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); + let vested_coins = account + .get_vested_coins(Some(block_time), &env, &deps.storage) + .unwrap(); + let vesting_coins = account + .get_vesting_coins(Some(block_time), &env, &deps.storage) + .unwrap(); assert_eq!( vested_coins.amount, Uint128::new( @@ -230,8 +237,12 @@ mod tests { ); let current_period = account.get_current_vesting_period(block_time); assert_eq!(current_period, Period::After); - let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap(); - let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap(); + let vested_coins = account + .get_vested_coins(Some(block_time), &env, &deps.storage) + .unwrap(); + let vesting_coins = account + .get_vesting_coins(Some(block_time), &env, &deps.storage) + .unwrap(); assert_eq!( vested_coins.amount, Uint128::new(account.get_original_vesting().amount().amount.u128()) @@ -245,8 +256,10 @@ mod tests { let env = mock_env(); let account = vesting_account_mid_fixture(&mut deps.storage, &env); - let vested_coins = account.get_vested_coins(None, &env).unwrap(); - let vesting_coins = account.get_vesting_coins(None, &env).unwrap(); + let vested_coins = account.get_vested_coins(None, &env, &deps.storage).unwrap(); + let vesting_coins = account + .get_vesting_coins(None, &env, &deps.storage) + .unwrap(); let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); @@ -262,7 +275,7 @@ mod tests { let delegation = Coin { amount: Uint128::new(90_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }; let ok = account.try_delegate_to_mixnode( @@ -273,8 +286,10 @@ mod tests { ); assert!(ok.is_ok()); - let vested_coins = account.get_vested_coins(None, &env).unwrap(); - let vesting_coins = account.get_vesting_coins(None, &env).unwrap(); + let vested_coins = account.get_vested_coins(None, &env, &deps.storage).unwrap(); + let vesting_coins = account + .get_vesting_coins(None, &env, &deps.storage) + .unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); @@ -333,8 +348,10 @@ mod tests { let withdrawn = account.load_withdrawn(&deps.storage).unwrap(); assert_eq!(withdrawn, Uint128::new(250_000_000_000)); - let vested_coins = account.get_vested_coins(None, &env).unwrap(); - let vesting_coins = account.get_vesting_coins(None, &env).unwrap(); + let vested_coins = account.get_vested_coins(None, &env, &deps.storage).unwrap(); + let vesting_coins = account + .get_vesting_coins(None, &env, &deps.storage) + .unwrap(); let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); @@ -352,8 +369,10 @@ mod tests { ); assert!(ok.is_ok()); - let vested_coins = account.get_vested_coins(None, &env).unwrap(); - let vesting_coins = account.get_vesting_coins(None, &env).unwrap(); + let vested_coins = account.get_vested_coins(None, &env, &deps.storage).unwrap(); + let vesting_coins = account + .get_vesting_coins(None, &env, &deps.storage) + .unwrap(); let locked_coins = account.locked_coins(None, &env, &mut deps.storage).unwrap(); assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000)); assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000)); @@ -388,7 +407,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(1_000_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -399,7 +418,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(90_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -411,7 +430,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(20_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -426,7 +445,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(500_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -522,7 +541,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(1_000_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -534,7 +553,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(90_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -550,7 +569,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(10_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -642,7 +661,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(1_000_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -654,7 +673,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(90_000_000_000), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, @@ -670,7 +689,7 @@ mod tests { "alice".to_string(), Coin { amount: Uint128::new(500_000_000_001), - denom: MIX_DENOM.base.to_string(), + denom: TEST_COIN_DENOM.to_string(), }, &env, &mut deps.storage, diff --git a/envs/mainnet.env b/envs/mainnet.env new file mode 100644 index 0000000000..dce1c3c9c0 --- /dev/null +++ b/envs/mainnet.env @@ -0,0 +1,20 @@ +CONFIGURED=true + +RUST_LOG=info +RUST_BACKTRACE=1 + +BECH32_PREFIX=n +MIX_DENOM=unym +MIX_DENOM_DISPLAY=nym +STAKE_DENOM=unyx +STAKE_DENOM_DISPLAY=nyx +DENOMS_EXPONENT=6 +MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g +VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw +BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy +STATISTICS_SERVICE_DOMAIN_ADDRESS="http://127.0.0.1:8090" +NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/" +API_VALIDATOR="https://validator.nymtech.net/api/" diff --git a/envs/qa.env b/envs/qa.env new file mode 100644 index 0000000000..842fb54c4d --- /dev/null +++ b/envs/qa.env @@ -0,0 +1,20 @@ +CONFIGURED=true + +RUST_LOG=info +RUST_BACKTRACE=1 + +BECH32_PREFIX=n +MIX_DENOM=unym +MIX_DENOM_DISPLAY=nym +STAKE_DENOM=unyx +STAKE_DENOM_DISPLAY=nyx +DENOMS_EXPONENT=6 +MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep +VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw +MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs +REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq +STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" +NYMD_VALIDATOR="https://qa-validator.nymtech.net" +API_VALIDATOR="https://qa-validator-api.nymtech.net/api" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 7e0400693f..923463518a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] chrono = { version = "0.4.19", features = ["serde"] } +clap = { version = "3.2.8", features = ["cargo", "derive"] } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" diff --git a/explorer-api/src/client.rs b/explorer-api/src/client.rs index 1f15891c16..129a394d31 100644 --- a/explorer-api/src/client.rs +++ b/explorer-api/src/client.rs @@ -1,6 +1,9 @@ -use network_defaults::{default_api_endpoints, default_nymd_endpoints, DEFAULT_NETWORK}; +use network_defaults::{ + var_names::{API_VALIDATOR, NYMD_VALIDATOR}, + NymNetworkDetails, +}; use reqwest::Url; -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; use validator_client::nymd::QueryNymdClient; // since this is just a query client, we don't need any locking mechanism to keep sequence numbers in check @@ -23,16 +26,17 @@ impl ThreadsafeValidatorClient { } pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient { - let nymd_url = default_nymd_endpoints()[0].clone(); - let api_url = default_api_endpoints()[0].clone(); + let nymd_url = Url::from_str(&std::env::var(NYMD_VALIDATOR).expect("nymd validator not set")) + .expect("nymd validator not in url format"); + let api_url = Url::from_str(&std::env::var(API_VALIDATOR).expect("nymd validator not set")) + .expect("nymd validator not in url format"); - let client_config = - validator_client::Config::try_from_nym_network_details(&DEFAULT_NETWORK.details()) - .expect("failed to construct valid validator client config with the provided network") - .with_urls(nymd_url, api_url); + let details = NymNetworkDetails::new_from_env(); + let client_config = validator_client::Config::try_from_nym_network_details(&details) + .expect("failed to construct valid validator client config with the provided network") + .with_urls(nymd_url, api_url); ThreadsafeValidatorClient(Arc::new( - validator_client::Client::new_query(client_config, DEFAULT_NETWORK) - .expect("Failed to connect to nymd!"), + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"), )) } diff --git a/explorer-api/src/commands/mod.rs b/explorer-api/src/commands/mod.rs new file mode 100644 index 0000000000..3c63e989cf --- /dev/null +++ b/explorer-api/src/commands/mod.rs @@ -0,0 +1,12 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; + +#[derive(Parser)] +#[clap(author = "Nymtech", version, about)] +pub(crate) struct Cli { + /// Path pointing to an env file that configures the explorer api. + #[clap(long)] + pub(crate) config_env_file: Option, +} diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index ed96dd4fff..a3a7f9ec26 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -3,10 +3,13 @@ extern crate rocket; #[macro_use] extern crate rocket_okapi; +use clap::Parser; use log::info; +use network_defaults::setup_env; pub(crate) mod cache; mod client; +pub(crate) mod commands; mod country_statistics; mod gateways; mod http; @@ -24,6 +27,8 @@ const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes #[tokio::main] async fn main() { setup_logging(); + let args = commands::Cli::parse(); + setup_env(args.config_env_file); let mut explorer_api = ExplorerApi::new(); explorer_api.run().await; } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 680e2770e7..eee994fbf2 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -156,6 +156,8 @@ pub async fn execute(args: &Init) { #[cfg(test)] mod tests { + use network_defaults::var_names::BECH32_PREFIX; + use crate::node::{storage::InMemStorage, Gateway}; use super::*; @@ -180,6 +182,7 @@ mod tests { #[cfg(all(feature = "eth", not(feature = "coconut")))] eth_endpoint: "".to_string(), }; + std::env::set_var(BECH32_PREFIX, "n"); let config = Config::new(&args.id); let config = override_config(config, OverrideConfig::from(args.clone())); diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 2e3272674d..e0bedb8976 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -7,6 +7,9 @@ use crate::{config::Config, Cli}; use clap::Subcommand; use colored::Colorize; use crypto::bech32_address_validation; +use network_defaults::var_names::{ + API_VALIDATOR, BECH32_PREFIX, CONFIGURED, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS, +}; use url::Url; pub(crate) mod init; @@ -109,14 +112,28 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi .parse() .expect("the provided statistics service url is invalid!"), ); + } else if std::env::var(CONFIGURED).is_ok() { + let raw_url = std::env::var(STATISTICS_SERVICE_DOMAIN_ADDRESS) + .expect("statistics service url not set"); + config = config.with_custom_statistics_service_url( + raw_url + .parse() + .expect("the provided statistics service url is invalid"), + ) } if let Some(raw_validators) = args.validator_apis { config = config.with_custom_validator_apis(parse_validators(&raw_validators)); + } else if std::env::var(CONFIGURED).is_ok() { + let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); + config = config.with_custom_validator_apis(parse_validators(&raw_validators)) } if let Some(raw_validators) = args.validators { config = config.with_custom_validator_nymd(parse_validators(&raw_validators)); + } else if std::env::var(CONFIGURED).is_ok() { + let raw_validators = std::env::var(NYMD_VALIDATOR).expect("nymd validator not set"); + config = config.with_custom_validator_nymd(parse_validators(&raw_validators)) } if let Some(wallet_address) = args.wallet_address { @@ -166,6 +183,7 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi /// Ensures that a given bech32 address is valid, or exits pub(crate) fn validate_bech32_address_or_exit(address: &str) { + let prefix = std::env::var(BECH32_PREFIX).expect("bech32 prefix not set"); if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = bech32_address_validation::try_bech32_decode(address) { @@ -176,7 +194,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { } if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = - bech32_address_validation::validate_bech32_prefix(address) + bech32_address_validation::validate_bech32_prefix(&prefix, address) { let error_message = format!("Error: wallet address type is wrong, {}", err).red(); println!("{}", error_message); diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index d7f3d6e947..cc62c5219e 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -3,7 +3,6 @@ use crate::config::{Config, MISSING_VALUE}; use clap::Args; -use config::defaults::default_api_endpoints; use config::NymConfig; use std::fmt::Display; use std::process; @@ -104,14 +103,7 @@ fn minor_0_12_upgrade( print_start_upgrade(&config_version, &to_version); - println!( - "Setting validator API endpoints to {:?}", - default_api_endpoints() - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validator_apis(default_api_endpoints()); + let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 6922cbe75d..d3acfacf52 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::*; +use config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; use config::NymConfig; use log::error; use serde::{Deserialize, Serialize}; @@ -422,9 +422,9 @@ impl Default for Gateway { #[cfg(not(feature = "coconut"))] eth_endpoint: "".to_string(), enabled_statistics: false, - statistics_service_url: default_statistics_service_url(), - validator_api_urls: default_api_endpoints(), - validator_nymd_urls: default_nymd_endpoints(), + statistics_service_url: Url::from_str("http://127.0.0.1").unwrap(), + validator_api_urls: vec![], + validator_nymd_urls: vec![], cosmos_mnemonic: bip39::Mnemonic::from_str("exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day").unwrap(), nym_root_directory: Config::default_root_directory(), persistent_storage: Default::default(), diff --git a/gateway/src/main.rs b/gateway/src/main.rs index a7ea4ff1da..0651b37237 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use clap::{crate_version, Parser}; -use network_defaults::DEFAULT_NETWORK; +use network_defaults::setup_env; use once_cell::sync::OnceCell; mod commands; @@ -19,13 +19,16 @@ fn long_version_static() -> &'static str { #[derive(Parser)] #[clap(author = "Nymtech", version, about, long_version = long_version_static())] struct Cli { + /// Path pointing to an env file that configures the gateway. + #[clap(long)] + pub(crate) config_env_file: Option, + #[clap(subcommand)] command: commands::Commands, } #[tokio::main] async fn main() { - dotenv::dotenv().ok(); setup_logging(); println!("{}", banner()); LONG_VERSION @@ -33,6 +36,7 @@ async fn main() { .expect("Failed to set long about text"); let args = Cli::parse(); + setup_env(args.config_env_file.clone()); commands::execute(args).await; } @@ -64,7 +68,6 @@ fn long_version() -> String { {:<20}{} {:<20}{} {:<20}{} -{:<20}{} "#, "Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP"), @@ -81,9 +84,7 @@ fn long_version() -> String { "rustc Channel:", env!("VERGEN_RUSTC_CHANNEL"), "cargo Profile:", - env!("VERGEN_CARGO_PROFILE"), - "Network:", - DEFAULT_NETWORK + env!("VERGEN_CARGO_PROFILE") ) } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 04f6fa4ac5..8f0ffcd098 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -6,7 +6,6 @@ use std::time::{Duration, SystemTime}; use log::*; use coconut_interface::{Credential, VerificationKey}; -use network_defaults::MIX_DENOM; use validator_client::{ nymd::{ cosmwasm_client::logs::find_attribute, @@ -24,6 +23,7 @@ const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct CoconutVerifier { api_clients: Vec, nymd_client: NymdClient, + mix_denom_base: String, aggregated_verification_key: VerificationKey, } @@ -31,6 +31,7 @@ impl CoconutVerifier { pub fn new( api_clients: Vec, nymd_client: NymdClient, + mix_denom_base: String, aggregated_verification_key: VerificationKey, ) -> Result { if api_clients.is_empty() { @@ -42,6 +43,7 @@ impl CoconutVerifier { Ok(CoconutVerifier { api_clients, nymd_client, + mix_denom_base, aggregated_verification_key, }) } @@ -58,7 +60,10 @@ impl CoconutVerifier { let res = self .nymd_client .spend_credential( - Coin::new(credential.voucher_value().into(), MIX_DENOM.base), + Coin::new( + credential.voucher_value().into(), + self.mix_denom_base.clone(), + ), credential.blinded_serial_number(), self.nymd_client.address().to_string(), None, @@ -91,7 +96,7 @@ impl CoconutVerifier { self.nymd_client .grant_allowance( &api_cosmos_addr, - vec![Coin::new(MAX_FEEGRANT_UNYM, MIX_DENOM.base)], + vec![Coin::new(MAX_FEEGRANT_UNYM, self.mix_denom_base.clone())], SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)), // It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")], diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index b2354e7a3b..9eb825792c 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -9,10 +9,10 @@ use crate::node::client_handling::websocket; use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::statistics::collector::GatewayStatisticsCollector; use crate::node::storage::Storage; -use config::defaults::DEFAULT_NETWORK; use crypto::asymmetric::{encryption, identity}; use log::*; use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use network_defaults::NymNetworkDetails; use rand::seq::SliceRandom; use rand::thread_rng; use statistics_common::collector::StatisticsSender; @@ -249,7 +249,8 @@ where .choose(&mut thread_rng()) .expect("The list of validators is empty"); - let client_config = nymd::Config::try_from_nym_network_details(&DEFAULT_NETWORK.details()) + let network_details = NymNetworkDetails::new_from_env(); + let client_config = nymd::Config::try_from_nym_network_details(&network_details) .expect("failed to construct valid validator client config with the provided network"); validator_client::nymd::NymdClient::connect_with_mnemonic( @@ -316,6 +317,7 @@ where let coconut_verifier = CoconutVerifier::new( self.all_api_clients(), nymd_client, + std::env::var(network_defaults::var_names::MIX_DENOM).expect("mix denom base not set"), validators_verification_key, ) .expect("Could not create coconut verifier"); diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 1d924bcf63..07d8302d73 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -6,6 +6,7 @@ use std::process; use crate::{config::Config, Cli}; use clap::Subcommand; use colored::Colorize; +use config::defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED}; use crypto::bech32_address_validation; use url::Url; @@ -92,6 +93,9 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config { if let Some(ref raw_validators) = args.validators { config = config.with_custom_validator_apis(parse_validators(raw_validators)); + } else if std::env::var(CONFIGURED).is_ok() { + let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); + config = config.with_custom_validator_apis(parse_validators(&raw_validators)) } if let Some(ref announce_host) = args.announce_host { @@ -112,6 +116,7 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config { /// Ensures that a given bech32 address is valid, or exits pub(crate) fn validate_bech32_address_or_exit(address: &str) { + let prefix = std::env::var(BECH32_PREFIX).expect("bech32 prefix not set"); if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = bech32_address_validation::try_bech32_decode(address) { @@ -122,7 +127,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { } if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = - bech32_address_validation::validate_bech32_prefix(address) + bech32_address_validation::validate_bech32_prefix(&prefix, address) { let error_message = format!("Error: wallet address type is wrong, {}", err).red(); println!("{}", error_message); diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index 0b0241e03c..85352e77c3 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -3,7 +3,6 @@ use crate::config::{missing_string_value, Config}; use clap::Args; -use config::defaults::default_api_endpoints; use config::NymConfig; use std::fmt::Display; use std::process; @@ -104,14 +103,7 @@ fn minor_0_12_upgrade( print_start_upgrade(&config_version, &to_version); - println!( - "Setting validator API endpoints to {:?}", - default_api_endpoints() - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validator_apis(default_api_endpoints()); + let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 7d3b520da9..14279ccdc2 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::*; +use config::defaults::{ + DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, +}; use config::NymConfig; use serde::{Deserialize, Deserializer, Serialize}; use std::net::{IpAddr, SocketAddr}; @@ -401,7 +403,7 @@ impl Default for MixNode { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_api_urls: default_api_endpoints(), + validator_api_urls: vec![], nym_root_directory: Config::default_root_directory(), wallet_address: "nymXXXXXXXX".to_string(), } diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 44637eace1..326ce7d66b 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -4,7 +4,7 @@ extern crate rocket; // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use ::config::defaults::DEFAULT_NETWORK; +use ::config::defaults::setup_env; use clap::{crate_version, Parser}; use lazy_static::lazy_static; @@ -24,17 +24,21 @@ fn long_version_static() -> &'static str { #[derive(Parser)] #[clap(author = "Nymtech", version, about, long_version = long_version_static())] struct Cli { + /// Path pointing to an env file that configures the gateway. + #[clap(long)] + pub(crate) config_env_file: Option, + #[clap(subcommand)] command: commands::Commands, } #[tokio::main] async fn main() { - dotenv::dotenv().ok(); setup_logging(); println!("{}", banner()); let args = Cli::parse(); + setup_env(args.config_env_file.clone()); commands::execute(args).await; } @@ -66,7 +70,6 @@ fn long_version() -> String { {:<20}{} {:<20}{} {:<20}{} -{:<20}{} "#, "Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP"), @@ -83,9 +86,7 @@ fn long_version() -> String { "rustc Channel:", env!("VERGEN_RUSTC_CHANNEL"), "cargo Profile:", - env!("VERGEN_CARGO_PROFILE"), - "Network:", - DEFAULT_NETWORK + env!("VERGEN_CARGO_PROFILE") ) } diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index 3c331dab3e..e63f3b9eea 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -680,6 +680,7 @@ name = "coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "multisig-contract-common", "schemars", "serde", ] @@ -805,8 +806,7 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ca04d3795c18023c221a2143b29de9c70668ecb22d17783bc02ee780c6c404" +source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" dependencies = [ "prost", "prost-types", @@ -816,8 +816,7 @@ dependencies = [ [[package]] name = "cosmrs" version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6989fdb6267eccb52762530b79ce0b385f4eaeb8b786522a95512e9bebb268c2" +source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d" dependencies = [ "bip32", "cosmos-sdk-proto", @@ -927,7 +926,6 @@ dependencies = [ "bls12_381", "coconut-interface", "crypto", - "network-defaults", "thiserror", "url", "validator-api-requests", @@ -3273,6 +3271,7 @@ name = "network-defaults" version = "0.1.0" dependencies = [ "cfg-if", + "dotenv", "hex-literal", "once_cell", "serde", @@ -3446,7 +3445,6 @@ dependencies = [ "credential-storage", "crypto", "dirs", - "dotenv", "futures", "gateway-client", "gateway-requests", @@ -6485,7 +6483,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vesting-contract" version = "1.0.1" dependencies = [ - "config", "cosmwasm-std", "cw-storage-plus", "mixnet-contract-common", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 3de7d067c8..e365222be2 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -727,6 +727,7 @@ name = "coconut-bandwidth-contract-common" version = "0.1.0" dependencies = [ "cosmwasm-std", + "multisig-contract-common", "schemars", "serde", ] @@ -2790,7 +2791,6 @@ dependencies = [ "cosmwasm-std", "fixed", "log", - "network-defaults", "schemars", "serde", "serde_repr", @@ -2887,6 +2887,7 @@ name = "network-defaults" version = "0.1.0" dependencies = [ "cfg-if", + "dotenv", "hex-literal", "once_cell", "serde", @@ -5585,7 +5586,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "vesting-contract" version = "1.0.1" dependencies = [ - "config", "cosmwasm-std", "cw-storage-plus", "mixnet-contract-common", diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 5c5b1ea5a3..64a9bd7fe1 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -78,7 +78,7 @@ pub enum BackendError { #[error("No balance available for address {0}")] NoBalance(String), #[error("The provided network is not supported (yet)")] - NetworkNotSupported(config::defaults::all::Network), + NetworkNotSupported, #[error("Could not access the local data storage directory")] UnknownStorageDirectory, #[error("The wallet file already exists")] diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 0f02c58f12..bb83b8a7fe 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -142,18 +142,16 @@ async fn _connect_with_mnemonic( )?; // Set the default account - let default_network: WalletNetwork = config::defaults::DEFAULT_NETWORK.into(); + let default_network = WalletNetwork::MAINNET; let client_for_default_network = clients .iter() - .find(|client| WalletNetwork::from(client.network.clone()) == default_network); + .find(|(network, _)| *network == default_network); let account_for_default_network = match client_for_default_network { - Some(client) => Ok(Account::new( + Some((_, client)) => Ok(Account::new( client.nymd.address().to_string(), default_network.mix_denom(), )), - None => Err(BackendError::NetworkNotSupported( - config::defaults::DEFAULT_NETWORK, - )), + None => Err(BackendError::NetworkNotSupported), }; // Register all the clients @@ -161,8 +159,7 @@ async fn _connect_with_mnemonic( let mut w_state = state.write().await; w_state.logout(); } - for client in clients { - let network: WalletNetwork = client.network.clone().into(); + for (network, client) in clients { let mut w_state = state.write().await; w_state.add_client(network, client); w_state.register_default_denoms(network); @@ -206,7 +203,7 @@ fn create_clients( default_api_urls: &HashMap, config: &Config, mnemonic: &Mnemonic, -) -> Result>, BackendError> { +) -> Result)>, BackendError> { let mut clients = Vec::new(); for network in WalletNetwork::iter() { let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) { @@ -253,10 +250,9 @@ fn create_clients( let config = validator_client::Config::try_from_nym_network_details(&network_details)? .with_urls(nymd_url, api_url); - let mut client = - validator_client::Client::new_signing(config, network.into(), mnemonic.clone())?; + let mut client = validator_client::Client::new_signing(config, mnemonic.clone())?; client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); - clients.push(client); + clients.push((network, client)); } Ok(clients) } diff --git a/service-providers/network-requester/src/statistics/collector.rs b/service-providers/network-requester/src/statistics/collector.rs index 6df70c3e39..fd9f5f5bc6 100644 --- a/service-providers/network-requester/src/statistics/collector.rs +++ b/service-providers/network-requester/src/statistics/collector.rs @@ -12,7 +12,6 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; -use network_defaults::DEFAULT_NETWORK; use nymsphinx::addressing::clients::Recipient; use ordered_buffer::OrderedMessageSender; use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Request}; @@ -59,6 +58,7 @@ pub struct StatsProviderConfigEntry { #[derive(Clone, Debug, Deserialize)] #[serde(deny_unknown_fields)] +#[allow(dead_code)] pub struct OptionalStatsProviderConfig { mainnet: Option, sandbox: Option, @@ -67,15 +67,7 @@ pub struct OptionalStatsProviderConfig { impl OptionalStatsProviderConfig { pub fn stats_client_address(&self) -> Option { - let entry_config = match DEFAULT_NETWORK { - network_defaults::all::Network::MAINNET => self.mainnet.clone(), - network_defaults::all::Network::SANDBOX => self.sandbox.clone(), - network_defaults::all::Network::QA => self.qa.clone(), - network_defaults::all::Network::CUSTOM { .. } => { - panic!("custom network is not supported") - } - }; - entry_config.map(|e| e.stats_client_address) + self.mainnet.clone().map(|e| e.stats_client_address) } } diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index 2f8105b2a9..f14ae1c501 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -19,7 +19,7 @@ use coconut_bandwidth_contract_common::spend_credential::{ use coconut_interface::{ Attribute, BlindSignRequest, BlindedSignature, KeyPair, Parameters, VerificationKey, }; -use config::defaults::{MIX_DENOM, VALIDATOR_API_VERSION}; +use config::defaults::VALIDATOR_API_VERSION; use credentials::coconut::params::{ ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, }; @@ -45,6 +45,7 @@ use self::comm::APICommunicationChannel; pub struct State { client: Arc, + mix_denom: String, key_pair: KeyPair, comm_channel: Arc, storage: ValidatorApiStorage, @@ -54,6 +55,7 @@ pub struct State { impl State { pub(crate) fn new( client: C, + mix_denom: String, key_pair: KeyPair, comm_channel: D, storage: ValidatorApiStorage, @@ -67,6 +69,7 @@ impl State { let rng = Arc::new(Mutex::new(OsRng)); Self { client, + mix_denom, key_pair, comm_channel, storage, @@ -160,6 +163,7 @@ impl InternalSignRequest { pub fn stage( client: C, + mix_denom: String, key_pair: KeyPair, comm_channel: D, storage: ValidatorApiStorage, @@ -168,7 +172,7 @@ impl InternalSignRequest { C: LocalClient + Send + Sync + 'static, D: APICommunicationChannel + Send + Sync + 'static, { - let state = State::new(client, key_pair, comm_channel, storage); + let state = State::new(client, mix_denom, key_pair, comm_channel, storage); AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... @@ -306,7 +310,7 @@ pub async fn verify_bandwidth_credential( vote_yes &= Coin::from(proposed_release_funds) == Coin::new( verify_credential_body.credential().voucher_value() as u128, - MIX_DENOM.base, + state.mix_denom.clone(), ); // Vote yes or no on the proposal based on the verification result diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index bba98e871d..9e45fd09bf 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -11,7 +11,7 @@ use coconut_bandwidth_contract_common::spend_credential::{ SpendCredential, SpendCredentialResponse, }; use coconut_interface::{hash_to_scalar, Credential, VerificationKey}; -use config::defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO}; +use config::defaults::VOUCHER_INFO; use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; use credentials::coconut::bandwidth::BandwidthVoucher; use credentials::coconut::params::{ @@ -47,6 +47,9 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, RwLock}; +const TEST_COIN_DENOM: &str = "unym"; +const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; + #[derive(Clone, Debug)] struct DummyClient { validator_address: AccountId, @@ -178,7 +181,7 @@ async fn check_signer_verif_key(key_pair: KeyPair) { db_dir.push(&verification_key.to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); let nymd_client = DummyClient::new( - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), @@ -187,6 +190,7 @@ async fn check_signer_verif_key(key_pair: KeyPair) { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, + TEST_COIN_DENOM.to_string(), key_pair, comm_channel, storage, @@ -271,7 +275,7 @@ async fn signed_before() { .unwrap() .insert(tx_hash.to_string(), tx_entry.clone()); let nymd_client = DummyClient::new( - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), &tx_db, &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), @@ -280,6 +284,7 @@ async fn signed_before() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, + TEST_COIN_DENOM.to_string(), key_pair, comm_channel, storage.clone(), @@ -335,7 +340,7 @@ async fn signed_before() { #[tokio::test] async fn state_functions() { let nymd_client = DummyClient::new( - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), @@ -346,7 +351,13 @@ async fn state_functions() { db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); - let state = State::new(nymd_client, key_pair, comm_channel, storage.clone()); + let state = State::new( + nymd_client, + TEST_COIN_DENOM.to_string(), + key_pair, + comm_channel, + storage.clone(), + ); let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E"); assert!(state.signed_before(&tx_hash).await.unwrap().is_none()); @@ -504,7 +515,7 @@ async fn blind_sign_correct() { .unwrap() .insert(tx_hash.to_string(), tx_entry.clone()); let nymd_client = DummyClient::new( - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), &tx_db, &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), @@ -513,6 +524,7 @@ async fn blind_sign_correct() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, + TEST_COIN_DENOM.to_string(), key_pair, comm_channel, storage.clone(), @@ -582,7 +594,7 @@ async fn signature_test() { db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); let nymd_client = DummyClient::new( - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(), + AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), &Arc::new(RwLock::new(HashMap::new())), @@ -591,6 +603,7 @@ async fn signature_test() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, + TEST_COIN_DENOM.to_string(), key_pair, comm_channel, storage.clone(), @@ -645,8 +658,7 @@ async fn signature_test() { #[tokio::test] async fn get_cosmos_address() { - let validator_address = - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(); + let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); let nymd_client = DummyClient::new( validator_address.clone(), &Arc::new(RwLock::new(HashMap::new())), @@ -662,6 +674,7 @@ async fn get_cosmos_address() { let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, + TEST_COIN_DENOM.to_string(), key_pair, comm_channel, storage.clone(), @@ -687,8 +700,7 @@ async fn get_cosmos_address() { #[tokio::test] async fn verification_of_bandwidth_credential() { // Setup variables - let validator_address = - AccountId::from_str(DEFAULT_NETWORK.rewarding_validator_address()).unwrap(); + let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); let proposal_db = Arc::new(RwLock::new(HashMap::new())); let spent_credential_db = Arc::new(RwLock::new(HashMap::new())); let nymd_client = DummyClient::new( @@ -713,6 +725,7 @@ async fn verification_of_bandwidth_credential() { let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client.clone(), + TEST_COIN_DENOM.to_string(), key_pair, comm_channel.clone(), storage1.clone(), @@ -806,7 +819,7 @@ async fn verification_of_bandwidth_credential() { ); // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract - let funds = Coin::new(voucher_value as u128, MIX_DENOM.base); + let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; @@ -901,7 +914,7 @@ async fn verification_of_bandwidth_credential() { // Test the endpoint with a proposal that has a different value for the funds to be released // then what's in the credential - let funds = Coin::new((voucher_value + 10) as u128, MIX_DENOM.base); + let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; @@ -939,7 +952,7 @@ async fn verification_of_bandwidth_credential() { ); // Test the endpoint with every dependency met - let funds = Coin::new(voucher_value as u128, MIX_DENOM.base); + let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); let msg = coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index cc34dbd92e..5eb67d3c4b 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::DEFAULT_NETWORK; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -108,9 +107,7 @@ impl Default for Base { local_validator: DEFAULT_LOCAL_VALIDATOR .parse() .expect("default local validator is malformed!"), - mixnet_contract_address: DEFAULT_NETWORK - .mixnet_contract_address() - .expect("mixnet contract address is unavailable"), + mixnet_contract_address: String::default(), mnemonic: "exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day".to_string(), } } @@ -279,7 +276,7 @@ impl Default for Rewarding { } } -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] #[serde(default)] pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. @@ -294,16 +291,6 @@ pub struct CoconutSigner { all_validator_apis: Vec, } -impl Default for CoconutSigner { - fn default() -> Self { - CoconutSigner { - enabled: false, - keypair_path: PathBuf::default(), - all_validator_apis: config::defaults::default_api_endpoints(), - } - } -} - impl Config { pub fn new() -> Self { Config::default() diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 662cb93269..c085d8315d 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -10,7 +10,10 @@ use crate::network_monitor::NetworkMonitorBuilder; use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; use crate::nymd_client::Client; use crate::storage::ValidatorApiStorage; -use ::config::defaults::DEFAULT_NETWORK; +use ::config::defaults::setup_env; +#[cfg(feature = "coconut")] +use ::config::defaults::var_names::API_VALIDATOR; +use ::config::defaults::var_names::{CONFIGURED, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; use ::config::NymConfig; use anyhow::Result; use clap::{crate_version, App, Arg, ArgMatches}; @@ -23,6 +26,8 @@ use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use rocket_okapi::mount_endpoints_and_merged_docs; use rocket_okapi::swagger_ui::make_swagger_ui; +use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::{fs, process}; @@ -50,6 +55,7 @@ mod swagger; mod coconut; const ID: &str = "id"; +const CONFIG_ENV_FILE: &str = "config-env-file"; const MONITORING_ENABLED: &str = "enable-monitor"; const REWARDING_ENABLED: &str = "enable-rewarding"; const MIXNET_CONTRACT_ARG: &str = "mixnet-contract"; @@ -98,7 +104,6 @@ fn long_version() -> String { {:<20}{} {:<20}{} {:<20}{} -{:<20}{} "#, "Build Timestamp:", env!("VERGEN_BUILD_TIMESTAMP"), @@ -115,9 +120,7 @@ fn long_version() -> String { "rustc Channel:", env!("VERGEN_RUSTC_CHANNEL"), "cargo Profile:", - env!("VERGEN_CARGO_PROFILE"), - "Network:", - DEFAULT_NETWORK + env!("VERGEN_CARGO_PROFILE") ) } @@ -127,6 +130,12 @@ fn parse_args<'a>() -> ArgMatches<'a> { .version(crate_version!()) .long_version(&*build_details) .author("Nymtech") + .arg( + Arg::with_name(CONFIG_ENV_FILE) + .help("Path pointing to an env file that configures the validator API") + .long(CONFIG_ENV_FILE) + .takes_value(true) + ) .arg( Arg::with_name(ID) .help("Id of the validator-api we want to run") @@ -273,6 +282,9 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { #[cfg(feature = "coconut")] if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { config = config.with_custom_validator_apis(parse_validators(raw_validators)); + } else if std::env::var(CONFIGURED).is_ok() { + let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set"); + config = config.with_custom_validator_apis(parse_validators(&raw_validators)) } if let Some(raw_validator) = matches.value_of(NYMD_VALIDATOR_ARG) { @@ -288,6 +300,10 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { if let Some(mixnet_contract) = matches.value_of(MIXNET_CONTRACT_ARG) { config = config.with_custom_mixnet_contract(mixnet_contract) + } else if std::env::var(CONFIGURED).is_ok() { + let mixnet_contract = + std::env::var(MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"); + config = config.with_custom_mixnet_contract(mixnet_contract) } if let Some(mnemonic) = matches.value_of(MNEMONIC_ARG) { @@ -413,6 +429,7 @@ fn expected_monitor_test_runs(config: &Config, interval_length: Duration) -> usi async fn setup_rocket( config: &Config, + _mix_denom: String, liftoff_notify: Arc, _nymd_client: Client, ) -> Result> { @@ -452,6 +469,7 @@ async fn setup_rocket( let keypair = KeyPair::try_from_bs58(keypair_bs58)?; rocket.attach(InternalSignRequest::stage( _nymd_client, + _mix_denom, keypair, QueryCommunicationChannel::new(config.get_all_validator_api_endpoints()), storage.clone().unwrap(), @@ -524,6 +542,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { if matches.is_present(WRITE_CONFIG_ARG) { return Ok(()); } + let mix_denom = std::env::var(MIX_DENOM).expect("mix denom not set"); let signing_nymd_client = Client::new_signing(&config); @@ -532,6 +551,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { // let's build our rocket! let rocket = setup_rocket( &config, + mix_denom, Arc::clone(&liftoff_notify), signing_nymd_client.clone(), ) @@ -605,8 +625,6 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { async fn main() -> Result<()> { println!("Starting validator api..."); - let _ = dotenv::dotenv(); - cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { // instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time console_subscriber::init(); @@ -614,5 +632,9 @@ async fn main() -> Result<()> { setup_logging(); let args = parse_args(); + let config_env_file = args + .value_of(CONFIG_ENV_FILE) + .map(|s| PathBuf::from_str(s).expect("invalid env config file")); + setup_env(config_env_file); run_validator_api(args).await } diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 06b17a7fd1..38eed11f45 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -11,7 +11,7 @@ use std::time::Duration; use tokio::sync::RwLock; use tokio::time::sleep; -use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; +use config::defaults::{NymNetworkDetails, DEFAULT_VALIDATOR_API_PORT}; use mixnet_contract_common::{ reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, IdentityKey, Interval, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus, @@ -51,17 +51,16 @@ impl Client { .parse() .unwrap(); let nymd_url = config.get_nymd_validator_url(); - let network = DEFAULT_NETWORK; - let details = network - .details() + + let details = NymNetworkDetails::new_from_env() .with_mixnet_contract(Some(config.get_mixnet_contract_address())); let client_config = validator_client::Config::try_from_nym_network_details(&details) .expect("failed to construct valid validator client config with the provided network") .with_urls(nymd_url, api_url); - let inner = validator_client::Client::new_query(client_config, network) - .expect("Failed to connect to nymd!"); + let inner = + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); Client(Arc::new(RwLock::new(inner))) } @@ -76,9 +75,7 @@ impl Client { .unwrap(); let nymd_url = config.get_nymd_validator_url(); - let network = DEFAULT_NETWORK; - let details = network - .details() + let details = NymNetworkDetails::new_from_env() .with_mixnet_contract(Some(config.get_mixnet_contract_address())); let client_config = validator_client::Config::try_from_nym_network_details(&details) @@ -90,7 +87,7 @@ impl Client { .parse() .expect("the mnemonic is invalid!"); - let inner = validator_client::Client::new_signing(client_config, network, mnemonic) + let inner = validator_client::Client::new_signing(client_config, mnemonic) .expect("Failed to connect to nymd!"); Client(Arc::new(RwLock::new(inner))) From a9fdbccb82a84a37d02578e620d4eb1c83523f54 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 21 Jul 2022 10:51:33 +0200 Subject: [PATCH 08/91] Universal compound rewards message that anyone can call (#1387) --- Makefile | 2 +- .../validator-client/src/nymd/mod.rs | 23 +++ .../mixnet-contract/src/msg.rs | 6 + contracts/mixnet/src/contract.rs | 15 +- .../mixnet/src/delegations/transactions.rs | 181 +----------------- contracts/mixnet/src/error.rs | 2 + contracts/mixnet/src/rewards/transactions.rs | 50 +++++ 7 files changed, 99 insertions(+), 180 deletions(-) diff --git a/Makefile b/Makefile index d1c2520d42..fcd78d82eb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -test-all: test cargo-test-expensive test: build clippy-all cargo-test wasm fmt +test-all: test cargo-test-expensive 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 diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index ba4c53cd6c..a279324ff8 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -1006,6 +1006,29 @@ impl NymdClient { .await } + #[execute("mixnet")] + fn _compound_reward( + &self, + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, + fee: Option, + ) -> (ExecuteMsg, Option) + where + C: SigningCosmWasmClient + Sync, + { + ( + ExecuteMsg::CompoundReward { + operator, + delegator, + mix_identity, + proxy, + }, + fee, + ) + } + #[execute("mixnet")] fn _compound_operator_reward(&self, fee: Option) -> (ExecuteMsg, Option) where diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 00d5361c40..ac1f460715 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -33,6 +33,12 @@ pub enum ExecuteMsg { CompoundDelegatorReward { mix_identity: IdentityKey, }, + CompoundReward { + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, + }, BondMixnode { mix_node: MixNode, owner_signature: String, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 923ed77ac9..e0e78068bd 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -112,6 +112,19 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { 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) } @@ -280,7 +293,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( diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 7ba841a2cc..0c0eaf0243 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -19,16 +19,7 @@ use mixnet_contract_common::{Delegation, IdentityKey}; use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; use vesting_contract_common::one_ucoin; -pub fn try_reconcile_all_delegation_events( - deps: DepsMut<'_>, - info: MessageInfo, -) -> Result { - let state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?; - // check if this is executed by the permitted validator, if not reject the transaction - if info.sender != state.rewarding_validator_address { - return Err(ContractError::Unauthorized); - } - +pub fn try_reconcile_all_delegation_events(deps: DepsMut<'_>) -> Result { _try_reconcile_all_delegation_events(deps.storage, deps.api) } @@ -1084,179 +1075,13 @@ mod tests { #[cfg(test)] mod removing_mix_stake_delegation { + use super::*; + use crate::support::tests; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Addr; - use crate::support::tests; - - use super::*; - - // TODO: Probably delete due to reconciliation logic - //#[ignore] - //#[test] - //fn fails_if_delegation_never_existed() { - // let mut deps = test_helpers::init_contract(); - // let env = mock_env(); - // let mixnode_owner = "bob"; - // let identity = test_helpers::add_mixnode( - // mixnode_owner, - // tests::fixtures::good_mixnode_pledge(), - // deps.as_mut(), - // ); - // let delegation_owner = Addr::unchecked("sender"); - // assert_eq!( - // Err(ContractError::NoMixnodeDelegationFound { - // identity: identity.clone(), - // address: delegation_owner.to_string(), - // }), - // try_remove_delegation_from_mixnode( - // deps.as_mut(), - // env, - // mock_info(delegation_owner.as_str(), &[]), - // identity, - // ) - // ); - //} - - // TODO: Update to work with reconciliation - //#[ignore] - //#[test] - //fn succeeds_if_delegation_existed() { - // let mut deps = test_helpers::init_contract(); - // let mixnode_owner = "bob"; - // let env = mock_env(); - // let identity = test_helpers::add_mixnode( - // mixnode_owner, - // tests::fixtures::good_mixnode_pledge(), - // deps.as_mut(), - // ); - // let delegation_owner = Addr::unchecked("sender"); - // try_delegate_to_mixnode( - // deps.as_mut(), - // mock_env(), - // mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)), - // identity.clone(), - // ) - // .unwrap(); - - // _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - // let _delegation = query_mixnode_delegation( - // &deps.storage, - // &deps.api, - // identity.clone(), - // delegation_owner.clone().into_string(), - // None, - // ) - // .unwrap(); - - // let expected_response = Response::new() - // .add_message(BankMsg::Send { - // to_address: delegation_owner.clone().into(), - // amount: coins(100, TEST_COIN_DENOM), - // }) - // .add_event(new_undelegation_event( - // &delegation_owner, - // &None, - // &identity, - // Uint128::new(100), - // )); - - // assert_eq!( - // Ok(expected_response), - // try_remove_delegation_from_mixnode( - // deps.as_mut(), - // env, - // mock_info(delegation_owner.as_str(), &[]), - // identity.clone(), - // ) - // ); - // assert!(storage::delegations() - // .may_load( - // &deps.storage, - // (identity.clone(), delegation_owner.as_bytes().to_vec(), 0), - // ) - // .unwrap() - // .is_none()); - - // // and total delegation is cleared - // assert_eq!( - // Uint128::zero(), - // mixnodes_storage::TOTAL_DELEGATION - // .load(&deps.storage, &identity) - // .unwrap() - // ) - //} - - // TODO: Update to work with reconciliation - //#[ignore] - //#[test] - //fn succeeds_if_delegation_existed_even_if_node_unbonded() { - // let mut deps = test_helpers::init_contract(); - // let mixnode_owner = "bob"; - // let env = mock_env(); - // let identity = test_helpers::add_mixnode( - // mixnode_owner, - // tests::fixtures::good_mixnode_pledge(), - // deps.as_mut(), - // ); - // let delegation_owner = Addr::unchecked("sender"); - // try_delegate_to_mixnode( - // deps.as_mut(), - // mock_env(), - // mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)), - // identity.clone(), - // ) - // .unwrap(); - - // _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - // let _delegation = query_mixnode_delegation( - // &deps.storage, - // &deps.api, - // identity.clone(), - // delegation_owner.clone().into_string(), - // None, - // ) - // .unwrap(); - - // let expected_response = Response::new() - // .add_message(BankMsg::Send { - // to_address: delegation_owner.clone().into(), - // amount: coins(100, TEST_COIN_DENOM), - // }) - // .add_event(new_undelegation_event( - // &delegation_owner, - // &None, - // &identity, - // Uint128::new(100), - // )); - - // try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); - - // assert_eq!( - // Ok(expected_response), - // try_remove_delegation_from_mixnode( - // deps.as_mut(), - // env, - // mock_info(delegation_owner.as_str(), &[]), - // identity.clone(), - // ) - // ); - - // _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - // assert!(test_helpers::read_delegation( - // &deps.storage, - // identity, - // delegation_owner.as_bytes(), - // mock_env().block.height - // ) - // .is_none()); - //} - #[test] fn total_delegation_is_preserved_if_only_some_undelegate() { let mut deps = test_helpers::init_contract(); diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index 25bdd91f3f..660dcad32a 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -178,4 +178,6 @@ pub enum ContractError { last_update_time: u64, current_block_time: u64, }, + #[error("`mix_identity` is required when `delegator` is set")] + MissingMixIdentity, } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 226bb822b2..82fddd449b 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -372,6 +372,56 @@ pub fn try_compound_delegator_reward_on_behalf( ) } +pub fn try_compound_reward( + deps: DepsMut<'_>, + env: Env, + operator: Option, + delegator: Option, + mix_identity: Option, + proxy: Option, +) -> Result { + let proxy = proxy.and_then(|p| deps.api.addr_validate(&p).ok()); + if let Some(operator_address) = operator { + let operator_address = deps.api.addr_validate(&operator_address)?; + let reward = _try_compound_operator_reward( + deps.storage, + deps.api, + env.block.height, + &operator_address, + proxy, + )?; + Ok( + Response::default().add_event(new_compound_operator_reward_event( + &operator_address, + reward, + )), + ) + } else if let Some(delegator_address) = delegator { + if mix_identity.is_none() { + return Err(ContractError::MissingMixIdentity); + } + + let delegator_address = deps.api.addr_validate(&delegator_address)?; + let reward = _try_compound_delegator_reward( + env.block.height, + deps, + delegator_address.as_str(), + mix_identity.as_ref().unwrap(), + proxy, + )?; + Ok( + Response::default().add_event(new_compound_delegator_reward_event( + &delegator_address, + &None, + reward, + &mix_identity.unwrap(), + )), + ) + } else { + Ok(Response::default()) + } +} + pub fn try_compound_delegator_reward( deps: DepsMut<'_>, env: Env, From b901655591c5d3db1c6c945295af81ba44dd7d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 21 Jul 2022 15:36:38 +0300 Subject: [PATCH 09/91] Fix message deserialization in socks5 client (#1470) --- clients/socks5/src/socks/mixnet_responses.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index 5ff06dada2..3146967711 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -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 From 5a7b19aeb6aa61ed35b660d5e2e0e7d9e643cb22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 21 Jul 2022 17:03:14 +0300 Subject: [PATCH 10/91] Nym connect use config from env (mainnet defaulted (#1471) --- clients/socks5/src/commands/mod.rs | 2 +- nym-connect/Cargo.lock | 1 - nym-connect/src-tauri/Cargo.toml | 2 +- nym-connect/src-tauri/src/config/mod.rs | 9 +++++++-- nym-connect/src-tauri/src/main.rs | 2 ++ nym-connect/src-tauri/src/state.rs | 2 +- nym-connect/src-tauri/src/tasks.rs | 2 +- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 9ff48199a7..f9df56bc64 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) { } } -fn parse_validators(raw: &str) -> Vec { +pub fn parse_validators(raw: &str) -> Vec { raw.split(',') .map(|raw_validator| { raw_validator diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index e63f3b9eea..1725bea89d 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -3199,7 +3199,6 @@ dependencies = [ "cosmwasm-std", "fixed", "log", - "network-defaults", "schemars", "serde", "serde_repr", diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index 23bfeed153..8166766b8c 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -39,7 +39,7 @@ tokio = { version = "1.19.1", features = ["sync", "time"] } url = "2.2" client-core = { path = "../../clients/client-core" } -config = { path = "../../common/config" } +config-common = { path = "../../common/config", package = "config" } nym-socks5-client = { path = "../../clients/socks5" } topology = { path = "../../common/topology" } diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index 5f4ce22e21..f1fb18bc5e 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -6,8 +6,8 @@ use tap::TapFallible; use tokio::sync::RwLock; use client_core::config::Config as BaseConfig; -use config::NymConfig; -use nym_socks5::client::config::Config as Socks5Config; +use config_common::NymConfig; +use nym_socks5::{client::config::Config as Socks5Config, commands::parse_validators}; use crate::{ error::{BackendError, Result}, @@ -134,6 +134,11 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str config .get_base_mut() .with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY); + if let Ok(raw_validators) = std::env::var(config_common::defaults::var_names::API_VALIDATOR) { + config + .get_base_mut() + .set_custom_validator_apis(parse_validators(&raw_validators)); + } let gateway = setup_gateway( &id, diff --git a/nym-connect/src-tauri/src/main.rs b/nym-connect/src-tauri/src/main.rs index 795533287c..e1117c7516 100644 --- a/nym-connect/src-tauri/src/main.rs +++ b/nym-connect/src-tauri/src/main.rs @@ -5,6 +5,7 @@ use std::sync::Arc; +use config_common::defaults::setup_env; use tauri::Menu; use tokio::sync::RwLock; @@ -24,6 +25,7 @@ mod window; fn main() { setup_logging(); + setup_env(None); println!("Starting up..."); // As per breaking change description here diff --git a/nym-connect/src-tauri/src/state.rs b/nym-connect/src-tauri/src/state.rs index d9a14dc9d0..2fce640487 100644 --- a/nym-connect/src-tauri/src/state.rs +++ b/nym-connect/src-tauri/src/state.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use ::config::NymConfig; +use ::config_common::NymConfig; use futures::SinkExt; use tap::TapFallible; use tauri::Manager; diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/src-tauri/src/tasks.rs index 8e44f1bb40..3729d4b4da 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/src-tauri/src/tasks.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; -use config::NymConfig; +use config_common::NymConfig; #[cfg(not(feature = "coconut"))] use nym_socks5::client::NymClient as Socks5NymClient; use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender}; From 93f931459ade63fcb37cb51e8fd213591ea9ab44 Mon Sep 17 00:00:00 2001 From: Fouad Date: Fri, 22 Jul 2022 09:40:06 +0100 Subject: [PATCH 11/91] fix type update issues with actions (bonding, delegating etc.) (#1469) --- .../components/Delegation/DelegateModal.tsx | 20 +++++++++----- .../components/Delegation/Modals.stories.tsx | 6 ++--- .../src/components/Rewards/CompoundModal.tsx | 8 +++--- .../Rewards/RedeemModal.stories.tsx | 8 +++--- .../src/components/Rewards/RedeemModal.tsx | 8 +++--- .../components/Send/SendDetails.stories.tsx | 1 + .../src/components/Send/SendDetailsModal.tsx | 5 +++- .../src/components/Send/SendInputModal.tsx | 5 +++- nym-wallet/src/components/Send/SendModal.tsx | 6 +++-- .../src/components/TokenPoolSelector.tsx | 6 +++-- nym-wallet/src/context/delegations.tsx | 2 +- nym-wallet/src/context/main.tsx | 7 +---- nym-wallet/src/context/mocks/main.tsx | 2 +- .../balance/components/TransferModal.tsx | 26 +++++++++++-------- nym-wallet/src/pages/balance/vesting.tsx | 12 +++++---- .../src/pages/bond/components/GatewayForm.tsx | 4 +-- .../src/pages/bond/components/MixnodeForm.tsx | 4 +-- .../src/pages/bond/components/SuccessView.tsx | 8 +++--- nym-wallet/src/pages/delegation/index.tsx | 9 +++---- nym-wallet/src/pages/receive/index.tsx | 4 +-- nym-wallet/src/requests/vesting.ts | 4 +-- .../components/currency/CurrencyFormField.tsx | 10 +++---- ts-packages/types/src/types/global.ts | 2 +- 23 files changed, 93 insertions(+), 74 deletions(-) diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index f9503e25e8..0efcef61f9 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -31,7 +31,7 @@ export const DelegateModal: React.FC<{ estimatedReward?: number; profitMarginPercentage?: number | null; nodeUptimePercentage?: number | null; - currency: string; + denom: CurrencyDenom; initialAmount?: string; hasVestingContract: boolean; sx?: SxProps; @@ -48,7 +48,7 @@ export const DelegateModal: React.FC<{ rewardInterval, accountBalance, estimatedReward, - currency, + denom, profitMarginPercentage, nodeUptimePercentage, initialAmount, @@ -103,7 +103,7 @@ export const DelegateModal: React.FC<{ } if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) { - errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`; + errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`; newValidatedValue = false; } @@ -118,7 +118,7 @@ export const DelegateModal: React.FC<{ const handleOk = async () => { if (onOk && amount && identityKey) { - onOk(identityKey, { amount, denom: currency as CurrencyDenom }, tokenPool, fee); + onOk(identityKey, { amount, denom }, tokenPool, fee); } }; @@ -170,7 +170,7 @@ export const DelegateModal: React.FC<{ onConfirm={handleOk} > - + ); } @@ -181,7 +181,7 @@ export const DelegateModal: React.FC<{ onClose={onClose} onOk={async () => { if (identityKey && amount) { - handleConfirm({ identity: identityKey, value: { amount, denom: currency as CurrencyDenom } }); + handleConfirm({ identity: identityKey, value: { amount, denom } }); } }} header={header || 'Delegate'} @@ -219,6 +219,7 @@ export const DelegateModal: React.FC<{ initialValue={amount} autoFocus={Boolean(initialIdentityKey)} onChanged={handleAmountChanged} + denom={denom} /> - - {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom} + {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} @@ -78,7 +79,8 @@ const VestingSchedule = () => { - {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom} + {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} + {clientDetails?.display_mix_denom.toUpperCase()} @@ -88,7 +90,7 @@ const VestingSchedule = () => { }; const TokenTransfer = () => { - const { userBalance, denom } = useContext(AppContext); + const { userBalance, clientDetails } = useContext(AppContext); const icon = useCallback( () => ( @@ -114,7 +116,7 @@ const TokenTransfer = () => { fontWeight="700" textTransform="uppercase" > - {userBalance.tokenAllocation?.spendable || 'n/a'} {denom} + {userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()} diff --git a/nym-wallet/src/pages/bond/components/GatewayForm.tsx b/nym-wallet/src/pages/bond/components/GatewayForm.tsx index e8a1520e69..966411fa3d 100644 --- a/nym-wallet/src/pages/bond/components/GatewayForm.tsx +++ b/nym-wallet/src/pages/bond/components/GatewayForm.tsx @@ -63,7 +63,7 @@ export const GatewayForm = ({ resolver: yupResolver(gatewayValidationSchema), defaultValues, }); - const { userBalance, clientDetails, denom } = useContext(AppContext); + const { userBalance, clientDetails } = useContext(AppContext); const { fee, getFee, resetFeeState, feeError } = useGetFee(); @@ -209,7 +209,7 @@ export const GatewayForm = ({ fullWidth label="Amount" onChanged={(val) => setValue('amount', val, { shouldValidate: true })} - denom={denom} + denom={clientDetails?.display_mix_denom} validationError={errors.amount?.amount?.message} /> diff --git a/nym-wallet/src/pages/bond/components/MixnodeForm.tsx b/nym-wallet/src/pages/bond/components/MixnodeForm.tsx index 91d38047cc..11a86b7532 100644 --- a/nym-wallet/src/pages/bond/components/MixnodeForm.tsx +++ b/nym-wallet/src/pages/bond/components/MixnodeForm.tsx @@ -66,7 +66,7 @@ export const MixnodeForm = ({ defaultValues, }); - const { userBalance, clientDetails, denom } = useContext(AppContext); + const { userBalance, clientDetails } = useContext(AppContext); const { fee, getFee, resetFeeState, feeError } = useGetFee(); @@ -216,7 +216,7 @@ export const MixnodeForm = ({ fullWidth label="Amount" onChanged={(val) => setValue('amount', val, { shouldValidate: true })} - denom={denom} + denom={clientDetails?.display_mix_denom} validationError={errors.amount?.amount?.message} /> diff --git a/nym-wallet/src/pages/bond/components/SuccessView.tsx b/nym-wallet/src/pages/bond/components/SuccessView.tsx index eadeeb4e25..35d37f6208 100644 --- a/nym-wallet/src/pages/bond/components/SuccessView.tsx +++ b/nym-wallet/src/pages/bond/components/SuccessView.tsx @@ -5,7 +5,7 @@ import { AppContext } from '../../../context/main'; import { useCheckOwnership } from '../../../hooks/useCheckOwnership'; export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => { - const { userBalance, denom } = useContext(AppContext); + const { userBalance, clientDetails } = useContext(AppContext); const { ownership } = useCheckOwnership(); return ( @@ -15,7 +15,9 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string subtitle="Successfully bonded to node with following details" caption={ ownership.vestingPledge - ? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${denom}` + ? `Your current locked balance is: ${ + userBalance.tokenAllocation?.locked + } ${clientDetails?.display_mix_denom.toUpperCase()}` : `Your current balance is: ${userBalance.balance?.printable_balance.toUpperCase()}` } /> @@ -26,7 +28,7 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string { primary: 'Node', secondary: details.address }, { primary: 'Amount', - secondary: `${details.amount} ${denom}`, + secondary: `${details.amount} ${clientDetails?.display_mix_denom.toUpperCase()}`, }, ]} /> diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index e3bf0d75a9..8d243e07e9 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -44,7 +44,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const { clientDetails, network, - denom, userBalance: { balance, originalVesting, fetchBalance }, } = useContext(AppContext); @@ -343,7 +342,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onOk={handleNewDelegation} header="Delegate" buttonText="Delegate stake" - currency={denom} + denom={clientDetails?.display_mix_denom || 'nym'} accountBalance={balance?.printable_balance} rewardInterval="weekly" hasVestingContract={Boolean(originalVesting)} @@ -359,7 +358,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { header="Delegate more" buttonText="Delegate more" identityKey={currentDelegationListActionItem.node_identity} - currency={denom} + denom={clientDetails?.display_mix_denom || 'nym'} accountBalance={balance?.printable_balance} nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent} profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent} @@ -386,7 +385,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onClose={() => setShowRedeemRewardsModal(false)} onOk={(identity, fee) => handleRedeem(identity, fee)} message="Redeem rewards" - currency={denom} + denom={clientDetails?.display_mix_denom || 'nym'} identityKey={currentDelegationListActionItem?.node_identity} amount={+currentDelegationListActionItem.accumulated_rewards.amount} usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens} @@ -399,7 +398,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { onClose={() => setShowCompoundRewardsModal(false)} onOk={(identity, fee) => handleCompound(identity, fee)} message="Compound rewards" - currency={denom} + denom={clientDetails?.display_mix_denom || 'nym'} identityKey={currentDelegationListActionItem?.node_identity} amount={+currentDelegationListActionItem.accumulated_rewards.amount} usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens} diff --git a/nym-wallet/src/pages/receive/index.tsx b/nym-wallet/src/pages/receive/index.tsx index 6d69f41cfe..cc97096409 100644 --- a/nym-wallet/src/pages/receive/index.tsx +++ b/nym-wallet/src/pages/receive/index.tsx @@ -6,11 +6,11 @@ import { AppContext } from '../../context/main'; import { PageLayout } from '../../layouts'; export const Receive = () => { - const { clientDetails, denom } = useContext(AppContext); + const { clientDetails } = useContext(AppContext); return ( - + You can receive tokens by providing this address to the sender diff --git a/nym-wallet/src/requests/vesting.ts b/nym-wallet/src/requests/vesting.ts index 16d5aa768d..15f141d7ab 100644 --- a/nym-wallet/src/requests/vesting.ts +++ b/nym-wallet/src/requests/vesting.ts @@ -57,8 +57,8 @@ export const vestingBondMixNode = async ({ export const vestingUnbondMixnode = async (fee?: Fee) => invokeWrapper('vesting_unbond_mixnode', { fee }); -export const withdrawVestedCoins = async (amount: DecCoin) => - invokeWrapper('withdraw_vested_coins', { amount }); +export const withdrawVestedCoins = async (amount: DecCoin, fee?: Fee) => + invokeWrapper('withdraw_vested_coins', { amount, fee }); export const vestingUpdateMixnode = async (profitMarginPercent: number) => invokeWrapper('vesting_update_mixnode', { profitMarginPercent }); diff --git a/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx b/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx index f03174776f..34fb118c34 100644 --- a/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx +++ b/ts-packages/react-components/src/components/currency/CurrencyFormField.tsx @@ -18,7 +18,7 @@ export const CurrencyFormField: React.FC<{ validationError?: string; placeholder?: string; label?: string; - denom?: string; + denom?: CurrencyDenom; onChanged?: (newValue: DecCoin) => void; onValidate?: (newValue: string | undefined, isValid: boolean, error?: string) => void; sx?: SxProps; @@ -35,7 +35,7 @@ export const CurrencyFormField: React.FC<{ onValidate, sx, showCoinMark = true, - denom = 'NYM', + denom = 'nym', }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [value, setValue] = React.useState(initialValue); @@ -113,7 +113,7 @@ export const CurrencyFormField: React.FC<{ if (onChanged) { const newMajorCurrencyAmount: DecCoin = { amount: newValue, - denom: denom as CurrencyDenom, + denom, }; onChanged(newMajorCurrencyAmount); } @@ -132,8 +132,8 @@ export const CurrencyFormField: React.FC<{ required, endAdornment: showCoinMark && ( - {denom === 'unym' && } - {denom !== 'unym' && NYMT} + {denom === 'nym' && } + {denom !== 'nym' && NYMT} ), ...{ diff --git a/ts-packages/types/src/types/global.ts b/ts-packages/types/src/types/global.ts index 1ec545138c..3c020800e2 100644 --- a/ts-packages/types/src/types/global.ts +++ b/ts-packages/types/src/types/global.ts @@ -1,4 +1,4 @@ -import { MixNode, DecCoin } from './rust'; +import { MixNode, DecCoin, CurrencyDenom } from './rust'; export type TNodeType = 'mixnode' | 'gateway'; From 83c33985704d5f63669dd735e368409dfea0c540 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 8 Jul 2022 18:49:11 +0100 Subject: [PATCH 12/91] nym-connect: copy changes --- nym-connect/src/layouts/DefaultLayout.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/nym-connect/src/layouts/DefaultLayout.tsx b/nym-connect/src/layouts/DefaultLayout.tsx index 42b6357442..9c329206d3 100644 --- a/nym-connect/src/layouts/DefaultLayout.tsx +++ b/nym-connect/src/layouts/DefaultLayout.tsx @@ -22,11 +22,14 @@ export const DefaultLayout: React.FC<{ }; return ( - - Connect, your privacy will be 100% protected thanks to the Nym Mixnet + + This is experimental software.
+ Do not rely on it for strong anonymity (yet).
- - You are not protected now + + Connect to the +
+ Nym mixnet for privacy.
Date: Fri, 22 Jul 2022 10:15:18 +0100 Subject: [PATCH 13/91] nym-connect: update CHANGELOG and bump version to 1.0.1 --- CHANGELOG.md | 21 +++++++++++++++------ nym-connect/Cargo.lock | 2 +- nym-connect/src-tauri/Cargo.toml | 2 +- nym-connect/src-tauri/tauri.conf.json | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251ce93fd1..570e00c8da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -42,7 +38,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ### 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`. @@ -74,10 +69,24 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#1393]: https://github.com/nymtech/nym/pull/1393 [#1404]: https://github.com/nymtech/nym/pull/1404 [#1419]: https://github.com/nymtech/nym/pull/1419 -[#1427]: https://github.com/nymtech/nym/pull/1427 [#1457]: https://github.com/nymtech/nym/pull/1457 [#1463]: https://github.com/nymtech/nym/pull/1463 +## [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 diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index 1725bea89d..a94e7ab621 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -3404,7 +3404,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.0.0" +version = "1.0.1" dependencies = [ "bip39", "client-core", diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index 8166766b8c..a529005681 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.0.0" +version = "1.0.1" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/src-tauri/tauri.conf.json b/nym-connect/src-tauri/tauri.conf.json index b2699a20b7..97ea3f1af8 100644 --- a/nym-connect/src-tauri/tauri.conf.json +++ b/nym-connect/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.0.0" + "version": "1.0.1" }, "build": { "distDir": "../dist", From 0233499036808face4196d7ddaa78468f7da26cc Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 22 Jul 2022 10:34:20 +0100 Subject: [PATCH 14/91] GitHub Actions: add prod deploy step for Network Explorer UI --- .github/workflows/network-explorer.yml | 12 ++++++++++++ explorer/.env.prod | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml index e2f424798e..a4731dffd1 100644 --- a/.github/workflows/network-explorer.yml +++ b/.github/workflows/network-explorer.yml @@ -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: startsWith(github.ref, 'refs/tags/nym-explorer-') == true && 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/" diff --git a/explorer/.env.prod b/explorer/.env.prod index 17c51ea14e..c6d0a35f6d 100644 --- a/explorer/.env.prod +++ b/explorer/.env.prod @@ -1,5 +1,5 @@ -EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://sandbox-validator.nymtech.net -BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net -CURRENCY_DENOM=unymt -CURRENCY_STAKING_DENOM=unyxt +EXPLORER_API_URL=https://explorer.nymtech.net/api/v1 +VALIDATOR_API_URL=https://validator.nymtech.net +BIG_DIPPER_URL=https://blocks.nymtech.net +CURRENCY_DENOM=unym +CURRENCY_STAKING_DENOM=unyx From 33e161bd599101ab08dd221f75d546d6b00f2476 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 22 Jul 2022 12:19:58 +0100 Subject: [PATCH 15/91] GitHub Actions: make explorer deployment only a manual step --- .github/workflows/network-explorer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/network-explorer.yml b/.github/workflows/network-explorer.yml index a4731dffd1..2f5b10de94 100644 --- a/.github/workflows/network-explorer.yml +++ b/.github/workflows/network-explorer.yml @@ -77,7 +77,7 @@ jobs: with: args: .github/workflows/support-files/notifications/entry_point.sh - name: Deploy - if: startsWith(github.ref, 'refs/tags/nym-explorer-') == true && github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' uses: easingthemes/ssh-deploy@main env: SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }} From 8bb42c2b1b657a53b1eead140d34e98e2ed69f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 25 Jul 2022 14:05:22 +0300 Subject: [PATCH 16/91] Add migration code for mixnet and vesting contracts (#1477) --- .../mixnet-contract/src/msg.rs | 4 +- .../vesting-contract/src/messages.rs | 4 +- contracts/mixnet/src/contract.rs | 2 + contracts/mixnet/src/lib.rs | 1 + contracts/mixnet/src/queued_migrations.rs | 39 +++++++++++++++++++ contracts/vesting/src/contract.rs | 2 + contracts/vesting/src/lib.rs | 1 + contracts/vesting/src/queued_migrations.rs | 17 ++++++++ 8 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 contracts/mixnet/src/queued_migrations.rs create mode 100644 contracts/vesting/src/queued_migrations.rs diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index ac1f460715..ddbec18188 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -214,4 +214,6 @@ pub enum QueryMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub struct MigrateMsg {} +pub struct MigrateMsg { + pub mixnet_denom: String, +} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 668d24db66..a9dd627557 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -12,7 +12,9 @@ pub struct InitMsg { #[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 { diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index e0e78068bd..0e90ecd6c8 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -27,6 +27,7 @@ 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::queued_migrations::migrate_config_from_env; use crate::rewards::queries::{ query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply, }; @@ -463,6 +464,7 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result, _env: Env, _msg: MigrateMsg) -> Result { + migrate_config_from_env(_deps, _env, _msg)?; Ok(Default::default()) } diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index 281675a90d..5b0d0c8d0c 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -9,5 +9,6 @@ mod gateways; mod interval; mod mixnet_contract_settings; mod mixnodes; +mod queued_migrations; mod rewards; mod support; diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs new file mode 100644 index 0000000000..758d91980d --- /dev/null +++ b/contracts/mixnet/src/queued_migrations.rs @@ -0,0 +1,39 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::{Addr, DepsMut, Env, Response}; +use cw_storage_plus::Item; +use mixnet_contract_common::{ContractStateParams, MigrateMsg}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::error::ContractError; +use crate::mixnet_contract_settings::models::ContractState; +use crate::mixnet_contract_settings::storage::CONTRACT_STATE; + +pub fn migrate_config_from_env( + deps: DepsMut<'_>, + _env: Env, + msg: MigrateMsg, +) -> Result { + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] + pub struct OldContractState { + pub owner: Addr, + pub mix_denom: String, + pub rewarding_validator_address: Addr, + pub params: ContractStateParams, + } + const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config"); + + let old_state = OLD_CONTRACT_STATE.load(deps.storage)?; + let new_state = ContractState { + owner: old_state.owner, + mix_denom: msg.mixnet_denom, + rewarding_validator_address: old_state.rewarding_validator_address, + params: old_state.params, + }; + + CONTRACT_STATE.save(deps.storage, &new_state)?; + + Ok(Default::default()) +} diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 02e1b7ec42..b1dad8944c 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -1,4 +1,5 @@ use crate::errors::ContractError; +use crate::queued_migrations::migrate_config_from_env; use crate::storage::{ account_from_address, locked_pledge_cap, update_locked_pledge_cap, ADMIN, MIXNET_CONTRACT_ADDRESS, MIX_DENOM, @@ -41,6 +42,7 @@ pub fn instantiate( #[entry_point] pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + migrate_config_from_env(_deps, _env, _msg)?; Ok(Response::default()) } diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs index 4e57232fe5..4e44a69be8 100644 --- a/contracts/vesting/src/lib.rs +++ b/contracts/vesting/src/lib.rs @@ -1,5 +1,6 @@ pub mod contract; mod errors; +mod queued_migrations; mod storage; mod support; mod traits; diff --git a/contracts/vesting/src/queued_migrations.rs b/contracts/vesting/src/queued_migrations.rs new file mode 100644 index 0000000000..75d63ba648 --- /dev/null +++ b/contracts/vesting/src/queued_migrations.rs @@ -0,0 +1,17 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::{DepsMut, Env, Response}; +use vesting_contract_common::MigrateMsg; + +use crate::{errors::ContractError, storage::MIX_DENOM}; + +pub fn migrate_config_from_env( + deps: DepsMut<'_>, + _env: Env, + msg: MigrateMsg, +) -> Result { + MIX_DENOM.save(deps.storage, &msg.mix_denom)?; + + Ok(Default::default()) +} From c81623a61add18cb652f12e6d80c23bde3e70ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 25 Jul 2022 16:59:45 +0300 Subject: [PATCH 17/91] Add gateway id in the gateway stats (#1478) * Add gateway id in the gateway stats * Update CHANGELOG --- CHANGELOG.md | 2 ++ common/statistics/src/lib.rs | 8 ++++++-- gateway/src/node/mod.rs | 1 + gateway/src/node/statistics/collector.rs | 13 +++++++++++-- .../migrations/20220512120000_mixnet_statistics.sql | 1 + .../network-statistics/src/api/routes.rs | 2 ++ .../network-statistics/src/storage/manager.rs | 4 +++- .../network-statistics/src/storage/mod.rs | 6 +++++- .../network-statistics/src/storage/models.rs | 1 + 9 files changed, 32 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 570e00c8da..61f0798fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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 @@ -71,6 +72,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#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 ## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) diff --git a/common/statistics/src/lib.rs b/common/statistics/src/lib.rs index 9e107fc119..5423106648 100644 --- a/common/statistics/src/lib.rs +++ b/common/statistics/src/lib.rs @@ -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, + } } } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 9eb825792c..379aae5fe2 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -336,6 +336,7 @@ where if self.config.get_enabled_statistics() { let statistics_service_url = self.config.get_statistics_service_url(); let stats_collector = GatewayStatisticsCollector::new( + self.identity_keypair.public_key().to_base58_string(), active_clients_store.clone(), statistics_service_url, ); diff --git a/gateway/src/node/statistics/collector.rs b/gateway/src/node/statistics/collector.rs index 18f290d006..62014d0d16 100644 --- a/gateway/src/node/statistics/collector.rs +++ b/gateway/src/node/statistics/collector.rs @@ -14,13 +14,19 @@ use statistics_common::{ use crate::node::client_handling::active_clients::ActiveClientsStore; pub(crate) struct GatewayStatisticsCollector { + gateway_id: String, active_clients_store: ActiveClientsStore, statistics_service_url: Url, } impl GatewayStatisticsCollector { - pub fn new(active_clients_store: ActiveClientsStore, statistics_service_url: Url) -> Self { + pub fn new( + gateway_id: String, + active_clients_store: ActiveClientsStore, + statistics_service_url: Url, + ) -> Self { GatewayStatisticsCollector { + gateway_id, active_clients_store, statistics_service_url, } @@ -35,7 +41,10 @@ impl StatisticsCollector for GatewayStatisticsCollector { timestamp: DateTime, ) -> StatsMessage { let inbox_count = self.active_clients_store.size() as u32; - let stats_data = vec![StatsData::Gateway(StatsGatewayData { inbox_count })]; + let stats_data = vec![StatsData::Gateway(StatsGatewayData::new( + self.gateway_id.clone(), + inbox_count, + ))]; StatsMessage { stats_data, interval_seconds: interval.as_secs() as u32, diff --git a/service-providers/network-statistics/migrations/20220512120000_mixnet_statistics.sql b/service-providers/network-statistics/migrations/20220512120000_mixnet_statistics.sql index 5e6b2aa348..da13b953ca 100644 --- a/service-providers/network-statistics/migrations/20220512120000_mixnet_statistics.sql +++ b/service-providers/network-statistics/migrations/20220512120000_mixnet_statistics.sql @@ -16,6 +16,7 @@ CREATE TABLE service_statistics CREATE TABLE gateway_statistics ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + gateway_id VARCHAR NOT NULL, inbox_count INTEGER NOT NULL, timestamp DATETIME NOT NULL ); \ No newline at end of file diff --git a/service-providers/network-statistics/src/api/routes.rs b/service-providers/network-statistics/src/api/routes.rs index 7316d2c375..0a1c558128 100644 --- a/service-providers/network-statistics/src/api/routes.rs +++ b/service-providers/network-statistics/src/api/routes.rs @@ -35,6 +35,7 @@ pub struct ServiceStatistic { #[derive(Clone, Serialize, Deserialize, Debug)] pub struct GatewayStatistic { + pub gateway_id: String, pub inbox_count: u32, pub timestamp: String, } @@ -70,6 +71,7 @@ pub(crate) async fn post_all_statistics( .into_iter() .map(|data| { GenericStatistic::Gateway(GatewayStatistic { + gateway_id: data.gateway_id, inbox_count: data.inbox_count as u32, timestamp: data.timestamp.to_string(), }) diff --git a/service-providers/network-statistics/src/storage/manager.rs b/service-providers/network-statistics/src/storage/manager.rs index 07e0cc751b..f77a5aace4 100644 --- a/service-providers/network-statistics/src/storage/manager.rs +++ b/service-providers/network-statistics/src/storage/manager.rs @@ -52,11 +52,13 @@ impl StorageManager { /// * `timestamp`: The moment in time when the data started being collected. pub(super) async fn insert_gateway_statistics( &self, + gateway_id: String, inbox_count: u32, timestamp: DateTime, ) -> Result<(), sqlx::Error> { sqlx::query!( - "INSERT INTO gateway_statistics(inbox_count, timestamp) VALUES (?, ?)", + "INSERT INTO gateway_statistics(gateway_id, inbox_count, timestamp) VALUES (?, ?, ?)", + gateway_id, inbox_count, timestamp, ) diff --git a/service-providers/network-statistics/src/storage/mod.rs b/service-providers/network-statistics/src/storage/mod.rs index 6ae4e08646..ea468c8606 100644 --- a/service-providers/network-statistics/src/storage/mod.rs +++ b/service-providers/network-statistics/src/storage/mod.rs @@ -71,7 +71,11 @@ impl NetworkStatisticsStorage { } statistics_common::StatsData::Gateway(gateway_data) => { self.manager - .insert_gateway_statistics(gateway_data.inbox_count, timestamp) + .insert_gateway_statistics( + gateway_data.gateway_id, + gateway_data.inbox_count, + timestamp, + ) .await? } } diff --git a/service-providers/network-statistics/src/storage/models.rs b/service-providers/network-statistics/src/storage/models.rs index f04a333495..d179914540 100644 --- a/service-providers/network-statistics/src/storage/models.rs +++ b/service-providers/network-statistics/src/storage/models.rs @@ -17,6 +17,7 @@ pub(crate) struct ServiceStatistics { pub(crate) struct GatewayStatistics { #[allow(dead_code)] pub(crate) id: i64, + pub(crate) gateway_id: String, pub(crate) inbox_count: i64, pub(crate) timestamp: NaiveDateTime, } From ba5e3d4efa87eebeafada79db84e9c9c220c38e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 26 Jul 2022 11:53:10 +0300 Subject: [PATCH 18/91] Remove service entries instead of zeroing them (#1479) --- .../network-requester/src/statistics/collector.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/service-providers/network-requester/src/statistics/collector.rs b/service-providers/network-requester/src/statistics/collector.rs index fd9f5f5bc6..96e7292d82 100644 --- a/service-providers/network-requester/src/statistics/collector.rs +++ b/service-providers/network-requester/src/statistics/collector.rs @@ -194,17 +194,10 @@ impl StatisticsCollector for ServiceStatisticsCollector { } async fn reset_stats(&mut self) { - self.request_stats_data - .write() - .await - .client_processed_bytes - .iter_mut() - .for_each(|(_, b)| *b = 0); + self.request_stats_data.write().await.client_processed_bytes = HashMap::new(); self.response_stats_data .write() .await - .client_processed_bytes - .iter_mut() - .for_each(|(_, b)| *b = 0); + .client_processed_bytes = HashMap::new(); } } From 8a2c95d04410d11edbdfc16eb3016bab05299d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C8=99u?= Date: Tue, 26 Jul 2022 15:00:27 +0300 Subject: [PATCH 19/91] Mixnode option doc fix --- mixnode/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 326ce7d66b..2e30e00976 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -24,7 +24,7 @@ fn long_version_static() -> &'static str { #[derive(Parser)] #[clap(author = "Nymtech", version, about, long_version = long_version_static())] struct Cli { - /// Path pointing to an env file that configures the gateway. + /// Path pointing to an env file that configures the mixnode. #[clap(long)] pub(crate) config_env_file: Option, From c5ece9787246d4cc71601941c6668626190946dd Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 28 Jul 2022 15:19:31 +0200 Subject: [PATCH 20/91] 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 --- Makefile | 2 +- .../mixnet-contract/src/msg.rs | 26 ++++++- contracts/mixnet/src/contract.rs | 68 ++++++++++++++++-- .../mixnet/src/delegations/transactions.rs | 37 ++++++++-- contracts/mixnet/src/error.rs | 5 ++ .../mixnet/src/mixnodes/bonding_queries.rs | 14 +++- contracts/mixnet/src/mixnodes/storage.rs | 4 ++ contracts/mixnet/src/mixnodes/transactions.rs | 70 ++++++++++++------- contracts/mixnet/src/queued_migrations.rs | 13 ++-- contracts/mixnet/src/rewards/transactions.rs | 24 ++++++- 10 files changed, 211 insertions(+), 52 deletions(-) diff --git a/Makefile b/Makefile index fcd78d82eb..fb1393d11a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -test: build clippy-all cargo-test wasm fmt +test: clippy-all cargo-test wasm fmt test-all: test cargo-test-expensive no-clippy: build cargo-test wasm fmt happy: fmt clippy-happy test diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index ddbec18188..6d71de8887 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -122,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 {}, @@ -212,8 +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 mixnet_denom: String, + nodes_to_remove: Option>, +} + +impl MigrateMsg { + pub fn nodes_to_remove(&self) -> Vec { + self.nodes_to_remove.clone().unwrap_or_default() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct NodeToRemove { + owner: String, + proxy: Option, +} + +impl NodeToRemove { + pub fn owner(&self) -> &str { + &self.owner + } + + pub fn proxy(&self) -> Option<&String> { + self.proxy.as_ref() + } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 0e90ecd6c8..3eaa1f6035 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -27,6 +27,7 @@ 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, @@ -34,10 +35,10 @@ use crate::rewards::queries::{ 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; @@ -141,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, @@ -235,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, @@ -335,6 +342,9 @@ pub fn execute( #[entry_point] pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { 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)?) } @@ -462,10 +472,54 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result 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 { + 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 { - migrate_config_from_env(_deps, _env, _msg)?; - Ok(Default::default()) +pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result { + 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)] diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 0c0eaf0243..c5e462929e 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -621,7 +621,14 @@ mod tests { deps.as_mut(), ); let delegation_owner = Addr::unchecked("sender"); - try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + let api = deps.api.clone(); + try_remove_mixnode( + &mock_env(), + deps.as_mut().storage, + &api, + mock_info(mixnode_owner, &[]), + ) + .unwrap(); assert_eq!( Err(ContractError::MixNodeBondNotFound { identity: identity.clone() @@ -644,7 +651,14 @@ mod tests { tests::fixtures::good_mixnode_pledge(), deps.as_mut(), ); - try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + let api = deps.api.clone(); + try_remove_mixnode( + &mock_env(), + deps.as_mut().storage, + &api, + mock_info(mixnode_owner, &[]), + ) + .unwrap(); let identity = test_helpers::add_mixnode( mixnode_owner, tests::fixtures::good_mixnode_pledge(), @@ -908,7 +922,14 @@ mod tests { identity.clone(), ) .unwrap(); - try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + let api = deps.api.clone(); + try_remove_mixnode( + &mock_env(), + deps.as_mut().storage, + &api, + mock_info(mixnode_owner, &[]), + ) + .unwrap(); assert_eq!( Err(ContractError::MixNodeBondNotFound { identity: identity.clone() @@ -1049,8 +1070,14 @@ mod tests { .unwrap(); _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap(); - - try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + let api = deps.api.clone(); + try_remove_mixnode( + &mock_env(), + deps.as_mut().storage, + &api, + mock_info(mixnode_owner, &[]), + ) + .unwrap(); let expected = Delegation::new( delegation_owner.clone(), diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index 660dcad32a..e1d3a22335 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -180,4 +180,9 @@ pub enum ContractError { }, #[error("`mix_identity` is required when `delegator` is set")] MissingMixIdentity, + #[error("Compounding has been disabled temporarily")] + CompoundDisabled, + + #[error("Mixnode {identity} has been blacklisted on the network")] + MixnodeBlacklisted { identity: String }, } diff --git a/contracts/mixnet/src/mixnodes/bonding_queries.rs b/contracts/mixnet/src/mixnodes/bonding_queries.rs index 2519045675..3c5085e04e 100644 --- a/contracts/mixnet/src/mixnodes/bonding_queries.rs +++ b/contracts/mixnet/src/mixnodes/bonding_queries.rs @@ -8,6 +8,13 @@ use mixnet_contract_common::{ IdentityKey, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse, }; +pub fn get_blacklisted_nodes(deps: Deps<'_>) -> Vec { + storage::MIXNODES_BOND_BLACKLIST + .keys(deps.storage, None, None, Order::Ascending) + .filter_map(|i| i.ok()) + .collect() +} + pub fn query_mixnode_at_height( deps: Deps<'_>, mix_identity: String, @@ -252,10 +259,13 @@ pub(crate) mod tests { let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap(); assert!(res.mixnode.is_some()); + let api = deps.api.clone(); + // but after unbonding it, he doesn't own one anymore crate::mixnodes::transactions::try_remove_mixnode( - env, - deps.as_mut(), + &env, + deps.as_mut().storage, + &api, mock_info("fred", &[]), ) .unwrap(); diff --git a/contracts/mixnet/src/mixnodes/storage.rs b/contracts/mixnet/src/mixnodes/storage.rs index d2722879a9..ad54790b8b 100644 --- a/contracts/mixnet/src/mixnodes/storage.rs +++ b/contracts/mixnet/src/mixnodes/storage.rs @@ -19,6 +19,7 @@ const MIXNODES_PK_CHECKPOINTS: &str = "mn__check"; const MIXNODES_PK_CHANGELOG: &str = "mn__change"; const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno"; const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns"; +const MIXNODES_BOND_BLACKLIST_NAMESPACE: &str = "mbb"; const LAST_PM_UPDATE_NAMESPACE: &str = "lpm"; @@ -32,6 +33,9 @@ pub(crate) const TOTAL_DELEGATION: Map<'_, IdentityKeyRef<'_>, Uint128> = pub(crate) const LAST_PM_UPDATE_TIME: Map<'_, IdentityKeyRef<'_>, u64> = Map::new(LAST_PM_UPDATE_NAMESPACE); +pub(crate) const MIXNODES_BOND_BLACKLIST: Map<'_, IdentityKeyRef<'_>, u8> = + Map::new(MIXNODES_BOND_BLACKLIST_NAMESPACE); + pub(crate) struct MixnodeBondIndex<'a> { pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>, diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 9f0e91c890..0b8b6153e3 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -8,12 +8,12 @@ use crate::mixnodes::layer_queries::query_layer_distribution; use crate::mixnodes::storage::StoredMixnodeBond; use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature}; use cosmwasm_std::{ - wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128, + wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128, }; use mixnet_contract_common::events::{ new_checkpoint_mixnodes_event, new_mixnode_bonding_event, new_mixnode_unbonding_event, }; -use mixnet_contract_common::MixNode; +use mixnet_contract_common::{IdentityKeyRef, MixNode}; use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg; use vesting_contract_common::one_ucoin; @@ -87,6 +87,15 @@ pub fn try_add_mixnode_on_behalf( ) } +pub fn is_blacklisted( + storage: &dyn Storage, + identity: IdentityKeyRef, +) -> Result { + Ok(storage::MIXNODES_BOND_BLACKLIST + .may_load(storage, identity)? + .is_some()) +} + fn _try_add_mixnode( deps: DepsMut<'_>, env: Env, @@ -100,6 +109,12 @@ fn _try_add_mixnode( // if the client has an active bonded mixnode or gateway, don't allow bonding ensure_no_existing_bond(deps.storage, &owner)?; + if is_blacklisted(deps.storage, &mix_node.identity_key)? { + return Err(ContractError::MixnodeBlacklisted { + identity: mix_node.identity_key, + }); + }; + // We don't have to check lower bound as its an u8 if mix_node.profit_margin_percent > 100 { return Err(ContractError::InvalidProfitMarginPercent( @@ -167,45 +182,47 @@ fn _try_add_mixnode( } pub fn try_remove_mixnode_on_behalf( - env: Env, - deps: DepsMut<'_>, + env: &Env, + storage: &mut dyn Storage, + api: &dyn Api, info: MessageInfo, owner: String, ) -> Result { let proxy = info.sender; - _try_remove_mixnode(env, deps, &owner, Some(proxy)) + _try_remove_mixnode(env, storage, api, &owner, Some(proxy), true) } pub fn try_remove_mixnode( - env: Env, - deps: DepsMut<'_>, + env: &Env, + storage: &mut dyn Storage, + api: &dyn Api, info: MessageInfo, ) -> Result { - _try_remove_mixnode(env, deps, info.sender.as_ref(), None) + _try_remove_mixnode(env, storage, api, info.sender.as_ref(), None, true) } pub(crate) fn _try_remove_mixnode( - env: Env, - deps: DepsMut<'_>, + env: &Env, + storage: &mut dyn Storage, + api: &dyn Api, owner: &str, proxy: Option, + collect_rewards: bool, ) -> Result { - let owner = deps.api.addr_validate(owner)?; + let owner = api.addr_validate(owner)?; - crate::rewards::transactions::_try_compound_operator_reward( - deps.storage, - deps.api, - env.block.height, - &owner, - None, - )?; + if collect_rewards { + crate::rewards::transactions::_try_compound_operator_reward( + storage, + api, + env.block.height, + &owner, + None, + )?; + } // try to find the node of the sender - let mixnode_bond = match storage::mixnodes() - .idx - .owner - .item(deps.storage, owner.clone())? - { + let mixnode_bond = match storage::mixnodes().idx.owner.item(storage, owner.clone())? { Some(record) => record.1, None => return Err(ContractError::NoAssociatedMixNodeBond { owner }), }; @@ -225,10 +242,10 @@ pub(crate) fn _try_remove_mixnode( }; // remove the bond - storage::mixnodes().remove(deps.storage, mixnode_bond.identity(), env.block.height)?; + storage::mixnodes().remove(storage, mixnode_bond.identity(), env.block.height)?; // decrement layer count - mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?; + mixnet_params_storage::decrement_layer_count(storage, mixnode_bond.layer)?; let mut response = Response::new(); @@ -238,8 +255,7 @@ pub(crate) fn _try_remove_mixnode( amount: mixnode_bond.pledge_amount(), }; - let track_unbond_message = - wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(deps.storage)?)])?; + let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(storage)?)])?; response = response.add_message(track_unbond_message); } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 758d91980d..a0d329fdf6 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cosmwasm_std::{Addr, DepsMut, Env, Response}; +use cosmwasm_std::{Addr, Response, Storage}; use cw_storage_plus::Item; use mixnet_contract_common::{ContractStateParams, MigrateMsg}; use schemars::JsonSchema; @@ -12,9 +12,8 @@ use crate::mixnet_contract_settings::models::ContractState; use crate::mixnet_contract_settings::storage::CONTRACT_STATE; pub fn migrate_config_from_env( - deps: DepsMut<'_>, - _env: Env, - msg: MigrateMsg, + storage: &mut dyn Storage, + msg: &MigrateMsg, ) -> Result { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct OldContractState { @@ -25,15 +24,15 @@ pub fn migrate_config_from_env( } const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config"); - let old_state = OLD_CONTRACT_STATE.load(deps.storage)?; + let old_state = OLD_CONTRACT_STATE.load(storage)?; let new_state = ContractState { owner: old_state.owner, - mix_denom: msg.mixnet_denom, + mix_denom: msg.mixnet_denom.clone(), rewarding_validator_address: old_state.rewarding_validator_address, params: old_state.params, }; - CONTRACT_STATE.save(deps.storage, &new_state)?; + CONTRACT_STATE.save(storage, &new_state)?; Ok(Default::default()) } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 82fddd449b..e0f59de6e9 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -13,6 +13,7 @@ use crate::error::ContractError; use crate::mixnet_contract_settings::storage::mix_denom; use crate::mixnodes::storage::mixnodes; use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond}; +use crate::mixnodes::transactions::is_blacklisted; use crate::rewards::helpers; use crate::support::helpers::{is_authorized, operator_cost_at_epoch}; use cosmwasm_std::{ @@ -462,7 +463,7 @@ pub fn _try_compound_delegator_reward( proxy.as_ref(), ); let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?; - let mut compounded_delegation = reward; + let mut total_delegation_delegate = Uint128::zero(); // Might want to introduce paging here let delegation_heights = delegation_map @@ -474,7 +475,7 @@ pub fn _try_compound_delegator_reward( for h in delegation_heights { let delegation = delegation_map.load(deps.storage, (mix_identity.to_string(), key.clone(), h))?; - compounded_delegation += delegation.amount.amount; + total_delegation_delegate += delegation.amount.amount; delegation_map.replace( deps.storage, (mix_identity.to_string(), key.clone(), h), @@ -483,7 +484,22 @@ pub fn _try_compound_delegator_reward( )?; } + let compounded_delegation = total_delegation_delegate + reward; + if compounded_delegation != Uint128::zero() { + mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>( + deps.storage, + mix_identity, + |total_delegation| { + // since we know that the target node exists and because the total_delegation bucket + // entry is created whenever the node itself is added, the unwrap here is fine + // as the entry MUST exist + Ok(total_delegation + .unwrap() + .saturating_sub(total_delegation_delegate)) + }, + )?; + _try_delegate_to_mixnode( deps.branch(), block_height, @@ -522,6 +538,10 @@ pub fn calculate_delegator_reward( key: Vec, mix_identity: &str, ) -> Result { + if is_blacklisted(storage, mix_identity)? { + return Ok(Uint128::zero()); + }; + let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT .load(storage, (key.clone(), mix_identity.to_string())) .unwrap_or(0); From 207c6cf2c727518a632db7e47cb5a04644d42eb9 Mon Sep 17 00:00:00 2001 From: rachyandco Date: Fri, 29 Jul 2022 10:13:07 +0200 Subject: [PATCH 21/91] Correcting a typo (#1475) Co-authored-by: Rachyandco --- nym-wallet/src/components/Delegation/DelegationActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/components/Delegation/DelegationActions.tsx b/nym-wallet/src/components/Delegation/DelegationActions.tsx index 6a325c3309..a48b3cda6a 100644 --- a/nym-wallet/src/components/Delegation/DelegationActions.tsx +++ b/nym-wallet/src/components/Delegation/DelegationActions.tsx @@ -144,7 +144,7 @@ export const DelegationsActionsMenu: React.FC<{ /> R
} onClick={() => handleActionSelect?.('redeem')} disabled={disableRedeemingRewards} From e631219a73fc36980827a53194c3082b0423176f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 1 Aug 2022 13:30:31 +0300 Subject: [PATCH 22/91] explorer-api: handle SIGTERM (#1482) * explorer-api: handle SIGTERM * Update CHANGELOG --- CHANGELOG.md | 2 + Cargo.lock | 1 + explorer-api/Cargo.toml | 1 + .../src/country_statistics/distribution.rs | 19 ++++-- .../src/country_statistics/geolocate.rs | 19 ++++-- explorer-api/src/main.rs | 59 +++++++++++++++---- explorer-api/src/tasks.rs | 36 ++++++----- 7 files changed, 98 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61f0798fcd..a789cbba5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ 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]). ### Changed @@ -73,6 +74,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#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 ## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) diff --git a/Cargo.lock b/Cargo.lock index f9935dfc1f..e7bcd513f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,6 +1606,7 @@ dependencies = [ "schemars", "serde", "serde_json", + "task", "thiserror", "tokio", "validator-client", diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 923463518a..f65a46db03 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -26,4 +26,5 @@ tokio = {version = "1.19.1", features = ["full"] } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } +task = { path = "../common/task" } validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } diff --git a/explorer-api/src/country_statistics/distribution.rs b/explorer-api/src/country_statistics/distribution.rs index ebbc1eca9b..f48e87eebb 100644 --- a/explorer-api/src/country_statistics/distribution.rs +++ b/explorer-api/src/country_statistics/distribution.rs @@ -1,4 +1,5 @@ use log::info; +use task::ShutdownListener; use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution; use crate::COUNTRY_DATA_REFRESH_INTERVAL; @@ -7,11 +8,12 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct CountryStatisticsDistributionTask { state: ExplorerApiStateContext, + shutdown: ShutdownListener, } impl CountryStatisticsDistributionTask { - pub(crate) fn new(state: ExplorerApiStateContext) -> Self { - CountryStatisticsDistributionTask { state } + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + CountryStatisticsDistributionTask { state, shutdown } } pub(crate) fn start(mut self) { @@ -20,10 +22,15 @@ impl CountryStatisticsDistributionTask { let mut interval_timer = tokio::time::interval(std::time::Duration::from_secs( COUNTRY_DATA_REFRESH_INTERVAL, )); - loop { - // wait for the next interval tick - interval_timer.tick().await; - self.calculate_nodes_per_country().await; + while !self.shutdown.is_shutdown() { + tokio::select! { + _ = interval_timer.tick() => { + self.calculate_nodes_per_country().await; + } + _ = self.shutdown.recv() => { + trace!("Listener: Received shutdown"); + } + } } }); } diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index 41b7eb6f3a..fd84a8c380 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -5,15 +5,17 @@ use crate::mix_nodes::location::{GeoLocation, Location}; use crate::state::ExplorerApiStateContext; use log::{info, warn}; use reqwest::Error as ReqwestError; +use task::ShutdownListener; use thiserror::Error; pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, + shutdown: ShutdownListener, } impl GeoLocateTask { - pub(crate) fn new(state: ExplorerApiStateContext) -> Self { - GeoLocateTask { state } + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + GeoLocateTask { state, shutdown } } pub(crate) fn start(mut self) { @@ -27,10 +29,15 @@ impl GeoLocateTask { info!("Spawning mix node locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); - loop { - // wait for the next interval tick - interval_timer.tick().await; - self.locate_mix_nodes().await; + while !self.shutdown.is_shutdown() { + tokio::select! { + _ = interval_timer.tick() => { + self.locate_mix_nodes().await; + } + _ = self.shutdown.recv() => { + trace!("Listener: Received shutdown"); + } + } } }); } diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index a3a7f9ec26..7e6779794b 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -6,6 +6,7 @@ extern crate rocket_okapi; use clap::Parser; use log::info; use network_defaults::setup_env; +use task::ShutdownNotifier; pub(crate) mod cache; mod client; @@ -50,29 +51,63 @@ impl ExplorerApi { let validator_api_url = self.state.inner.validator_client.api_endpoint(); info!("Using validator API - {}", validator_api_url); + let shutdown = ShutdownNotifier::default(); + // spawn concurrent tasks - crate::tasks::ExplorerApiTasks::new(self.state.clone()).start(); + crate::tasks::ExplorerApiTasks::new(self.state.clone(), shutdown.subscribe()).start(); country_statistics::distribution::CountryStatisticsDistributionTask::new( self.state.clone(), + shutdown.subscribe(), ) .start(); - country_statistics::geolocate::GeoLocateTask::new(self.state.clone()).start(); + country_statistics::geolocate::GeoLocateTask::new(self.state.clone(), shutdown.subscribe()) + .start(); + + // Rocket handles shutdown on it's own, but its shutdown handling should be incorporated + // with that of the rest of the tasks. + // Currently it's runtime is forcefully terminated once the explorer-api exits. http::start(self.state.clone()); // wait for user to press ctrl+C - self.wait_for_interrupt().await + self.wait_for_interrupt(shutdown).await } - async fn wait_for_interrupt(&self) { - if let Err(e) = tokio::signal::ctrl_c().await { - error!( - "There was an error while capturing SIGINT - {:?}. We will terminate regardless", - e - ); + async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) { + wait_for_signal().await; + + log::info!("Sending shutdown"); + shutdown.signal_shutdown().ok(); + log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); + shutdown.wait_for_shutdown().await; + log::info!("Stopping explorer API"); + } +} + +#[cfg(unix)] +async fn wait_for_signal() { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); + let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); + + tokio::select! { + _ = tokio::signal::ctrl_c() => { + log::info!("Received SIGINT"); + }, + _ = sigterm.recv() => { + log::info!("Received SIGTERM"); } - info!( - "Received SIGINT - the mixnode will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)." - ); + _ = sigquit.recv() => { + log::info!("Received SIGQUIT"); + } + } +} + +#[cfg(not(unix))] +async fn wait_for_signal() { + tokio::select! { + _ = tokio::signal::ctrl_c() => { + log::info!("Received SIGINT"); + }, } } diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index 83ed0d7d6a..bac19b7ee9 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -4,6 +4,7 @@ use std::future::Future; use mixnet_contract_common::GatewayBond; +use task::ShutdownListener; use validator_client::models::MixNodeBondAnnotated; use validator_client::nymd::error::NymdError; use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse}; @@ -14,11 +15,12 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct ExplorerApiTasks { state: ExplorerApiStateContext, + shutdown: ShutdownListener, } impl ExplorerApiTasks { - pub(crate) fn new(state: ExplorerApiStateContext) -> Self { - ExplorerApiTasks { state } + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + ExplorerApiTasks { state, shutdown } } // a helper to remove duplicate code when grabbing active/rewarded/all mixnodes @@ -128,24 +130,28 @@ impl ExplorerApiTasks { } } - pub(crate) fn start(self) { + pub(crate) fn start(mut self) { info!("Spawning mix nodes task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(CACHE_REFRESH_RATE); - loop { - // wait for the next interval tick - interval_timer.tick().await; + while !self.shutdown.is_shutdown() { + tokio::select! { + _ = interval_timer.tick() => { + info!("Updating validator cache..."); + self.update_validators_cache().await; + info!("Done"); - info!("Updating validator cache..."); - self.update_validators_cache().await; - info!("Done"); + info!("Updating gateway cache..."); + self.update_gateways_cache().await; + info!("Done"); - info!("Updating gateway cache..."); - self.update_gateways_cache().await; - info!("Done"); - - info!("Updating mix node cache..."); - self.update_mixnode_cache().await; + info!("Updating mix node cache..."); + self.update_mixnode_cache().await; + } + _ = self.shutdown.recv() => { + trace!("Listener: Received shutdown"); + } + } } }); } From ba1818a903bfd1647033ef5c1c8540ec5ecd105f Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 1 Aug 2022 14:19:04 +0200 Subject: [PATCH 23/91] Chitchat example (#1464) * Chitchat example * Cleanup --- nym-chitchat/Cargo.lock | 1983 +++++++++++++++++++++++++++++++++++ nym-chitchat/Cargo.toml | 28 + nym-chitchat/README.md | 15 + nym-chitchat/run-servers.sh | 15 + nym-chitchat/src/main.rs | 123 +++ 5 files changed, 2164 insertions(+) create mode 100644 nym-chitchat/Cargo.lock create mode 100644 nym-chitchat/Cargo.toml create mode 100644 nym-chitchat/README.md create mode 100755 nym-chitchat/run-servers.sh create mode 100644 nym-chitchat/src/main.rs diff --git a/nym-chitchat/Cargo.lock b/nym-chitchat/Cargo.lock new file mode 100644 index 0000000000..408a2c30f8 --- /dev/null +++ b/nym-chitchat/Cargo.lock @@ -0,0 +1,1983 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "assert_cmd" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chitchat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e16bc6492a6744d6e322e22562c3ab89404ba326aedb65a570799d2e042687" +dependencies = [ + "anyhow", + "async-trait", + "bytes 1.2.0", + "rand", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "chitchat-test" +version = "0.3.0" +dependencies = [ + "anyhow", + "assert_cmd", + "chitchat", + "cool-id-generator", + "env_logger", + "once_cell", + "poem", + "poem-openapi", + "predicates", + "reqwest", + "serde", + "serde_json", + "structopt", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" +dependencies = [ + "aes-gcm", + "base64 0.13.0", + "hkdf", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "time 0.3.11", + "version_check", +] + +[[package]] +name = "cool-id-generator" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "946cf88fb171128bc025588b9b408982814ace1a1fb31ffb9bfa63d955f0b43d" +dependencies = [ + "rand", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes 1.2.0", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64 0.13.0", + "bitflags", + "bytes 1.2.0", + "headers-core", + "http", + "httpdate", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes 1.2.0", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes 1.2.0", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes 1.2.0", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "multer" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30ba6d97eb198c5e8a35d67d5779d6680cca35652a60ee90fc23dc431d4fde8" +dependencies = [ + "bytes 1.2.0", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin", + "tokio", + "version_check", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "poem" +version = "1.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e479ed477ea11939bea754109eaa024e55bf1639e19c1753fb250026351da8" +dependencies = [ + "async-trait", + "bytes 1.2.0", + "chrono", + "cookie", + "futures-util", + "headers", + "http", + "hyper", + "mime", + "multer", + "parking_lot", + "percent-encoding", + "pin-project-lite", + "poem-derive", + "regex", + "rfc7239", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tempfile", + "thiserror", + "time 0.3.11", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "typed-headers", +] + +[[package]] +name = "poem-derive" +version = "1.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d454ac05b5299eb8e6261214fcb823f0c3c1b02f47167f8809c9dc2db0ee033" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "poem-openapi" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c27cd1c97d45a9552b0c80bab1d51f93a1050c3f7f91e9312239b3b87c336b0f" +dependencies = [ + "base64 0.13.0", + "bytes 1.2.0", + "derive_more", + "futures-util", + "mime", + "num-traits", + "poem", + "poem-openapi-derive", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "thiserror", + "tokio", +] + +[[package]] +name = "poem-openapi-derive" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5105e426a8a747fefc4be06ab7705cb63bc3ab1b2db373867e1d83b1d95877a" +dependencies = [ + "Inflector", + "darling", + "http", + "indexmap", + "mime", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn", + "thiserror", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64 0.13.0", + "bytes 1.2.0", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rfc7239" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "087317b3cf7eb481f13bd9025d729324b7cd068d6f470e2d76d049e191f5ba47" +dependencies = [ + "uncased", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" + +[[package]] +name = "serde" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57aec3cfa4c296db7255446efb4928a6be304b431a806216105542a67b6ca82e" +dependencies = [ + "autocfg", + "bytes 1.2.0", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes 1.2.0", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typed-headers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3179a61e9eccceead5f1574fd173cf2e162ac42638b9bf214c6ad0baf7efa24a" +dependencies = [ + "base64 0.11.0", + "bytes 0.5.6", + "chrono", + "http", + "mime", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/nym-chitchat/Cargo.toml b/nym-chitchat/Cargo.toml new file mode 100644 index 0000000000..eae29e5738 --- /dev/null +++ b/nym-chitchat/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "chitchat-test" +version = "0.3.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chitchat = "0.4" +poem = "1" +poem-openapi = {version="2", features = ["swagger-ui"] } +structopt = "0.3" +tokio = { version = "1.14.0", features = ["net", "sync", "rt-multi-thread", "macros", "time"] } +serde = { version="1", features=["derive"] } +serde_json = "1" +anyhow = "1" +once_cell = "1" +tracing = "0.1" +tracing-subscriber = "0.3" +cool-id-generator = "1" +env_logger = "0.9" + +[dev-dependencies] +assert_cmd = "2" +predicates = "2" +reqwest = { version = "0.11", default-features=false, features = ["blocking", "json"] } + +[workspace] diff --git a/nym-chitchat/README.md b/nym-chitchat/README.md new file mode 100644 index 0000000000..2fd61cd169 --- /dev/null +++ b/nym-chitchat/README.md @@ -0,0 +1,15 @@ +# Chitchat test + +Runs simple chitchat servers, mostly copied over from https://github.com/quickwit-oss/chitchat + +## Example + +```bash +# Starts 5 servers and joins them into a cluster on localhost ports 10000-10004 +# All servers print cluster state on `/` ie 127.0.0.1:10000 +# `/docs` endpoint has an open api with a key value setter, set it on one node and observe how the state propagates to the other nodes +# NodeState is a regular BTreeMap +./run-servers.sh + +# run killall chitchat-test after you're done, as the servers will continue to run forever in the background +``` diff --git a/nym-chitchat/run-servers.sh b/nym-chitchat/run-servers.sh new file mode 100755 index 0000000000..9e08abac3c --- /dev/null +++ b/nym-chitchat/run-servers.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +killall chitchat-test + +cargo build --release + +for i in $(seq 10000 10004) +do + listen_addr="127.0.0.1:$i"; + echo ${listen_addr}; + cargo run --release -- --listen_addr ${listen_addr} --seed 127.0.0.1:10000 --node_id node_$i & +done; + +read +kill 0 diff --git a/nym-chitchat/src/main.rs b/nym-chitchat/src/main.rs new file mode 100644 index 0000000000..dc0899913d --- /dev/null +++ b/nym-chitchat/src/main.rs @@ -0,0 +1,123 @@ +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; + +use chitchat::transport::UdpTransport; +use chitchat::{spawn_chitchat, Chitchat, ChitchatConfig, FailureDetectorConfig, NodeId}; +use cool_id_generator::Size; +use poem::listener::TcpListener; +use poem::{Route, Server}; +use poem_openapi::param::Query; +use poem_openapi::payload::Json; +use poem_openapi::{OpenApi, OpenApiService}; +use structopt::StructOpt; +use tokio::sync::Mutex; + +use chitchat::ClusterStateSnapshot; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ApiResponse { + pub cluster_id: String, + pub cluster_state: ClusterStateSnapshot, + pub live_nodes: Vec, + pub dead_nodes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SetKeyValueResponse { + pub status: bool, +} + +struct Api { + chitchat: Arc>, +} + +#[OpenApi] +impl Api { + /// Chitchat state + #[oai(path = "/", method = "get")] + async fn index(&self) -> Json { + let chitchat_guard = self.chitchat.lock().await; + let response = ApiResponse { + cluster_id: chitchat_guard.cluster_id().to_string(), + cluster_state: chitchat_guard.state_snapshot(), + live_nodes: chitchat_guard.live_nodes().cloned().collect::>(), + dead_nodes: chitchat_guard.dead_nodes().cloned().collect::>(), + }; + Json(serde_json::to_value(&response).unwrap()) + } + + /// Set a key & value on this node (with no validation). + #[oai(path = "/set_kv/", method = "get")] + async fn set_kv(&self, key: Query, value: Query) -> Json { + let mut chitchat_guard = self.chitchat.lock().await; + + let cc_state = chitchat_guard.self_node_state(); + cc_state.set(key.as_str(), value.as_str()); + + Json(serde_json::to_value(&SetKeyValueResponse { status: true }).unwrap()) + } +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "chitchat", about = "Chitchat test server.")] +struct Opt { + /// Defines the socket addr on which we should listen to. + #[structopt(long = "listen_addr", default_value = "127.0.0.1:10000")] + listen_addr: SocketAddr, + /// Defines the socket_address (host:port) other servers should use to + /// reach this server. + /// + /// It defaults to the listen address, but this is only valid + /// when all server are running on the same server. + #[structopt(long = "public_addr")] + public_addr: Option, + + /// Node id. Has to be unique. If None, the node_id will be generated from + /// the public_addr and a random suffix. + #[structopt(long = "node_id")] + node_id: Option, + + #[structopt(long = "seed")] + seeds: Vec, + + #[structopt(long = "interval_ms", default_value = "500")] + interval: u64, +} + +fn generate_server_id(public_addr: SocketAddr) -> String { + let cool_id = cool_id_generator::get_id(Size::Medium); + format!("server:{}-{}", public_addr, cool_id) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt::init(); + let opt = Opt::from_args(); + println!("{:?}", opt); + let public_addr = opt.public_addr.unwrap_or(opt.listen_addr); + let node_id_str = opt + .node_id + .unwrap_or_else(|| generate_server_id(public_addr)); + let node_id = NodeId::new(node_id_str, public_addr); + let config = ChitchatConfig { + node_id, + cluster_id: "testing".to_string(), + gossip_interval: Duration::from_millis(opt.interval), + listen_addr: opt.listen_addr, + seed_nodes: opt.seeds.clone(), + failure_detector_config: FailureDetectorConfig::default(), + }; + let chitchat_handler = spawn_chitchat(config, Vec::new(), &UdpTransport).await?; + let chitchat = chitchat_handler.chitchat(); + let api = Api { chitchat }; + let api_service = OpenApiService::new(api, "Hello World", "1.0") + .server(&format!("http://{}/", opt.listen_addr)); + let docs = api_service.swagger_ui(); + let app = Route::new().nest("/", api_service).nest("/docs", docs); + Server::new(TcpListener::bind(&opt.listen_addr)) + .run(app) + .await?; + Ok(()) +} From 1167f50543b20f23f9c44fbd0da37a4594aa75b9 Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Tue, 2 Aug 2022 12:04:47 +0200 Subject: [PATCH 24/91] [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 --- .../components/Delegation/DelegateModal.tsx | 3 +- .../src/components/Modals/LoadingModal.tsx | 3 +- .../src/components/Modals/SimpleModal.tsx | 16 ++++--- nym-wallet/src/components/Nav.tsx | 7 ++-- .../src/components/Receive/ReceiveModal.tsx | 42 +++++++++++++++++++ nym-wallet/src/components/Receive/index.tsx | 12 ++++++ .../src/components/Send/SendDetailsModal.tsx | 3 +- .../src/components/Send/SendErrorModal.tsx | 2 +- .../src/components/Send/SendInputModal.tsx | 3 +- .../src/components/Send/SendSuccessModal.tsx | 3 +- nym-wallet/src/context/main.tsx | 7 ++++ nym-wallet/src/context/mocks/main.tsx | 2 + nym-wallet/src/pages/index.ts | 1 - nym-wallet/src/pages/receive/index.tsx | 26 ------------ nym-wallet/src/routes/app.tsx | 5 ++- 15 files changed, 85 insertions(+), 50 deletions(-) create mode 100644 nym-wallet/src/components/Receive/ReceiveModal.tsx create mode 100644 nym-wallet/src/components/Receive/index.tsx delete mode 100644 nym-wallet/src/pages/receive/index.tsx diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index 0efcef61f9..c19afe974d 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; -import { Box, Typography } from '@mui/material'; -import { SxProps } from '@mui/system'; +import { Box, Typography, SxProps } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { CurrencyDenom, FeeDetails, DecCoin } from '@nymproject/types'; diff --git a/nym-wallet/src/components/Modals/LoadingModal.tsx b/nym-wallet/src/components/Modals/LoadingModal.tsx index 8422390e3b..a50bec77eb 100644 --- a/nym-wallet/src/components/Modals/LoadingModal.tsx +++ b/nym-wallet/src/components/Modals/LoadingModal.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Box, CircularProgress, Modal, Stack, Typography } from '@mui/material'; -import { SxProps } from '@mui/system'; +import { Box, CircularProgress, Modal, Stack, Typography, SxProps } from '@mui/material'; const modalStyle: SxProps = { position: 'absolute', diff --git a/nym-wallet/src/components/Modals/SimpleModal.tsx b/nym-wallet/src/components/Modals/SimpleModal.tsx index 37b02c9adf..986087c724 100644 --- a/nym-wallet/src/components/Modals/SimpleModal.tsx +++ b/nym-wallet/src/components/Modals/SimpleModal.tsx @@ -60,12 +60,16 @@ export const SimpleModal: React.FC<{ {children} - - {onBack && } - - + {(onOk || onBack) && ( + + {onBack && } + {onOk && ( + + )} + + )}
); diff --git a/nym-wallet/src/components/Nav.tsx b/nym-wallet/src/components/Nav.tsx index b8119316d9..387de1ac7a 100644 --- a/nym-wallet/src/components/Nav.tsx +++ b/nym-wallet/src/components/Nav.tsx @@ -2,14 +2,14 @@ import React, { useState, useContext } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material'; import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material'; -import { AppContext } from '../context/main'; +import { AppContext } from '../context'; import { Bond, Delegate, Unbond } from '../svg-icons'; export const Nav = () => { const location = useLocation(); const navigate = useNavigate(); - const { isAdminAddress, handleShowSendModal } = useContext(AppContext); + const { isAdminAddress, handleShowSendModal, handleShowReceiveModal } = useContext(AppContext); const [routesSchema] = useState([ { @@ -25,9 +25,8 @@ export const Nav = () => { }, { label: 'Receive', - route: '/receive', Icon: ArrowBack, - onClick: () => navigate('/receive'), + onClick: handleShowReceiveModal, }, { label: 'Bond', diff --git a/nym-wallet/src/components/Receive/ReceiveModal.tsx b/nym-wallet/src/components/Receive/ReceiveModal.tsx new file mode 100644 index 0000000000..a37360f969 --- /dev/null +++ b/nym-wallet/src/components/Receive/ReceiveModal.tsx @@ -0,0 +1,42 @@ +import React, { useContext } from 'react'; +import { AppContext } from 'src/context'; +import { Box, Stack, Typography, SxProps } from '@mui/material'; +import QRCode from 'qrcode.react'; +import { SimpleModal } from '../Modals/SimpleModal'; +import { ClientAddress } from '../ClientAddress'; + +export const ReceiveModal = ({ + onClose, + open, + sx, + backdropProps, +}: { + onClose: () => void; + open: boolean; + sx?: SxProps; + backdropProps?: object; +}) => { + const { clientDetails } = useContext(AppContext); + return ( + + + + Your address: + + + + `1px solid ${t.palette.nym.highlight}`, borderRadius: 2, p: 2 }}> + {clientDetails && } + + + + + ); +}; diff --git a/nym-wallet/src/components/Receive/index.tsx b/nym-wallet/src/components/Receive/index.tsx new file mode 100644 index 0000000000..1dd7e38aba --- /dev/null +++ b/nym-wallet/src/components/Receive/index.tsx @@ -0,0 +1,12 @@ +import React, { useContext } from 'react'; +import { AppContext } from 'src/context'; +import { ReceiveModal } from './ReceiveModal'; + +export const Receive = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => { + const { showReceiveModal, handleShowReceiveModal } = useContext(AppContext); + + if (showReceiveModal) + return ; + + return null; +}; diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index a96a4e090e..efbf64dd6c 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Stack } from '@mui/material'; -import { SxProps } from '@mui/system'; +import { Stack, SxProps } from '@mui/material'; import { CurrencyDenom } from '@nymproject/types'; import { FeeDetails, DecCoin } from '@nymproject/types'; import { SimpleModal } from '../Modals/SimpleModal'; diff --git a/nym-wallet/src/components/Send/SendErrorModal.tsx b/nym-wallet/src/components/Send/SendErrorModal.tsx index 890b8cdf16..18fb5b4b74 100644 --- a/nym-wallet/src/components/Send/SendErrorModal.tsx +++ b/nym-wallet/src/components/Send/SendErrorModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SxProps } from '@mui/system'; +import { SxProps } from '@mui/material'; import { SimpleModal } from '../Modals/SimpleModal'; export const SendErrorModal = ({ diff --git a/nym-wallet/src/components/Send/SendInputModal.tsx b/nym-wallet/src/components/Send/SendInputModal.tsx index 855fe4d67c..c6a02414a5 100644 --- a/nym-wallet/src/components/Send/SendInputModal.tsx +++ b/nym-wallet/src/components/Send/SendInputModal.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Stack, TextField, Typography } from '@mui/material'; -import { SxProps } from '@mui/system'; +import { Stack, TextField, Typography, SxProps } from '@mui/material'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { CurrencyDenom, DecCoin } from '@nymproject/types'; import { validateAmount } from 'src/utils'; diff --git a/nym-wallet/src/components/Send/SendSuccessModal.tsx b/nym-wallet/src/components/Send/SendSuccessModal.tsx index 5c93d4cc05..29f3ffa1e9 100644 --- a/nym-wallet/src/components/Send/SendSuccessModal.tsx +++ b/nym-wallet/src/components/Send/SendSuccessModal.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Stack, Typography } from '@mui/material'; -import { SxProps } from '@mui/system'; +import { Stack, Typography, SxProps } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { TTransactionDetails } from './types'; import { ConfirmationModal } from '../Modals/ConfirmationModal'; diff --git a/nym-wallet/src/context/main.tsx b/nym-wallet/src/context/main.tsx index 317c7ccf0e..47d98bd058 100644 --- a/nym-wallet/src/context/main.tsx +++ b/nym-wallet/src/context/main.tsx @@ -49,8 +49,10 @@ export type TAppContext = { loginType?: TLoginType; showSettings: boolean; showSendModal: boolean; + showReceiveModal: boolean; handleShowSettings: () => void; handleShowSendModal: () => void; + handleShowReceiveModal: () => void; setIsLoading: (isLoading: boolean) => void; setError: (value?: string) => void; switchNetwork: (network: Network) => void; @@ -81,6 +83,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const [isAdminAddress, setIsAdminAddress] = useState(false); const [showSettings, setShowSettings] = useState(false); const [showSendModal, setShowSendModal] = useState(false); + const [showReceiveModal, setShowReceiveModal] = useState(false); const userBalance = useGetBalance(clientDetails); const navigate = useNavigate(); @@ -234,6 +237,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const switchNetwork = (_network: Network) => setNetwork(_network); const handleShowSettings = () => setShowSettings((show) => !show); const handleShowSendModal = () => setShowSendModal((show) => !show); + const handleShowReceiveModal = () => setShowReceiveModal((show) => !show); const handleSwitchMode = () => setMode((currentMode) => { const newMode = currentMode === 'light' ? 'dark' : 'light'; @@ -270,7 +274,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { onAccountChange, handleShowSettings, showSendModal, + showReceiveModal, handleShowSendModal, + handleShowReceiveModal, handleSwitchMode, }), [ @@ -290,6 +296,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { showTerminal, showSettings, showSendModal, + showReceiveModal, ], ); diff --git a/nym-wallet/src/context/mocks/main.tsx b/nym-wallet/src/context/mocks/main.tsx index c0af920aee..c9f32dedd8 100644 --- a/nym-wallet/src/context/mocks/main.tsx +++ b/nym-wallet/src/context/mocks/main.tsx @@ -40,6 +40,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => { showTerminal: false, showSettings: false, showSendModal: true, + showReceiveModal: false, network: 'SANDBOX', loginType: 'mnemonic', setIsLoading: () => undefined, @@ -54,6 +55,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => { onAccountChange: () => undefined, handleShowSettings: () => undefined, handleShowSendModal: () => undefined, + handleShowReceiveModal: () => undefined, }), [], ); diff --git a/nym-wallet/src/pages/index.ts b/nym-wallet/src/pages/index.ts index b02d0413c9..b734a45260 100644 --- a/nym-wallet/src/pages/index.ts +++ b/nym-wallet/src/pages/index.ts @@ -2,7 +2,6 @@ export * from './Admin'; export * from './balance'; export * from './bond'; export * from './internal-docs'; -export * from './receive'; export * from './auth'; export * from './settings'; export * from './unbond'; diff --git a/nym-wallet/src/pages/receive/index.tsx b/nym-wallet/src/pages/receive/index.tsx deleted file mode 100644 index cc97096409..0000000000 --- a/nym-wallet/src/pages/receive/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useContext } from 'react'; -import QRCode from 'qrcode.react'; -import { Alert, Box, Stack } from '@mui/material'; -import { ClientAddress, NymCard } from '../../components'; -import { AppContext } from '../../context/main'; -import { PageLayout } from '../../layouts'; - -export const Receive = () => { - const { clientDetails } = useContext(AppContext); - - return ( - - - - - You can receive tokens by providing this address to the sender - - - - - {clientDetails && } - - - - ); -}; diff --git a/nym-wallet/src/routes/app.tsx b/nym-wallet/src/routes/app.tsx index 19f4c769e4..97169027c2 100644 --- a/nym-wallet/src/routes/app.tsx +++ b/nym-wallet/src/routes/app.tsx @@ -3,16 +3,17 @@ import { Route, Routes } from 'react-router-dom'; import { ApplicationLayout } from 'src/layouts'; import { Terminal } from 'src/pages/terminal'; import { Send } from 'src/components/Send'; -import { Bond, Balance, InternalDocs, Receive, Unbond, DelegationPage, Admin, Settings } from '../pages'; +import { Receive } from '../components/Receive'; +import { Bond, Balance, InternalDocs, Unbond, DelegationPage, Admin, Settings } from '../pages'; export const AppRoutes = () => ( + } /> - } /> } /> } /> } /> From 07893828d8394a755ad4684c13c7741da0d1b16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 3 Aug 2022 11:58:20 +0300 Subject: [PATCH 25/91] 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 --- CHANGELOG.md | 2 ++ .../network-requester/src/allowed_hosts.rs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a789cbba5f..b12ec2d2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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 @@ -75,6 +76,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#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) diff --git a/service-providers/network-requester/src/allowed_hosts.rs b/service-providers/network-requester/src/allowed_hosts.rs index 294a75edab..3746d9a706 100644 --- a/service-providers/network-requester/src/allowed_hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts.rs @@ -109,9 +109,14 @@ impl OutboundRequestFilter { } /// Attempts to get the root domain, shorn of subdomains, using publicsuffix. + /// If the domain is itself a suffix, then just use the full address as root. fn get_domain_root(&self, host: &str) -> Option { match self.domain_list.parse_domain(host) { - Ok(d) => d.root().map(|root| root.to_string()), + Ok(d) => Some( + d.root() + .map(|root| root.to_string()) + .unwrap_or_else(|| d.full().to_string()), + ), Err(_) => { log::warn!("Error parsing domain: {:?}", host); None // domain couldn't be parsed @@ -348,9 +353,12 @@ mod tests { } #[test] - fn returns_none_on_nonsense_domains() { + fn returns_full_on_suffix_domains() { let filter = setup(); - assert_eq!(None, filter.get_domain_root("flappappa")); + assert_eq!( + Some("s3.amazonaws.com".to_string()), + filter.get_domain_root("s3.amazonaws.com") + ); } } From 9c19ae322d9f4265ea90ae6f6abc46bedc34e363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 3 Aug 2022 12:45:21 +0300 Subject: [PATCH 26/91] Bump regex version to fix dependabot (#1488) --- contracts/Cargo.lock | 8 ++++---- nym-wallet/Cargo.lock | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 5438f45804..e0676f0832 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1392,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" diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index e365222be2..9f57f987f3 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3996,9 +3996,9 @@ 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 = [ "aho-corasick", "memchr", @@ -4016,9 +4016,9 @@ dependencies = [ [[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 = "remove_dir_all" From d92df9ada3256e5db9e90b3e9675d462806264ca Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 12:24:21 +0200 Subject: [PATCH 27/91] fixing dark mode colours --- .../balance/components/vesting-timeline.tsx | 24 +++++++++++--- nym-wallet/src/pages/balance/vesting.tsx | 32 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx index 47fea9e9b1..dd8d4e7d8c 100644 --- a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx +++ b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/no-array-index-key */ import React, { useContext } from 'react'; +import { useTheme } from '@mui/material/styles'; import { Box, Tooltip, Typography } from '@mui/material'; import { format } from 'date-fns'; import { AppContext } from '../../../context/main'; @@ -21,6 +22,9 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc userBalance: { currentVestingPeriod, vestingAccountInfo }, } = useContext(AppContext); + const theme = useTheme(); + const { mode } = theme.palette; + const nextPeriod = typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) @@ -29,19 +33,31 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc return ( - - + + {vestingAccountInfo?.periods.map((period, i, arr) => ( = calculateMarkerPosition(arr.length, i) ? '#121726' : '#B9B9B9'} + color={ + +percentageComplete.toFixed(2) >= calculateMarkerPosition(arr.length, i) + ? mode === 'light' + ? 'text.dark' + : theme.palette.success.main + : '#B9B9B9' + } tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} key={i} /> ))} diff --git a/nym-wallet/src/pages/balance/vesting.tsx b/nym-wallet/src/pages/balance/vesting.tsx index 2ff85cf4bf..a3a52a44b1 100644 --- a/nym-wallet/src/pages/balance/vesting.tsx +++ b/nym-wallet/src/pages/balance/vesting.tsx @@ -65,20 +65,44 @@ const VestingSchedule = () => { ))}
- + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + borderBottom: 'none', + textTransform: 'uppercase', + }} + > {userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} {clientDetails?.display_mix_denom.toUpperCase()} - + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + borderBottom: 'none', + }} + > {vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)} - + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + borderBottom: 'none', + }} + > {`${vestedPercentage}%`} - + (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'), + borderBottom: 'none', + textTransform: 'uppercase', + }} + align="right" + > {userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '} {clientDetails?.display_mix_denom.toUpperCase()} From d7220b1fecbe40deaaa075e21d7ec75c00b8053b Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 13:42:48 +0200 Subject: [PATCH 28/91] adding info text in filters and renaming --- explorer/src/components/Filters/Filters.tsx | 2 ++ explorer/src/components/Filters/filterSchema.ts | 6 +++++- explorer/src/typeDefs/filters.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index c62b09a53f..355be16223 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -25,6 +25,7 @@ import { useIsMobile } from '../../hooks/useIsMobile'; const FilterItem = ({ label, id, + tooltipInfo, value, marks, scale, @@ -36,6 +37,7 @@ const FilterItem = ({ }) => ( {label} + {tooltipInfo} onChange(id, newValue as number[])} diff --git a/explorer/src/components/Filters/filterSchema.ts b/explorer/src/components/Filters/filterSchema.ts index 1d6b742f90..0dce1c327e 100644 --- a/explorer/src/components/Filters/filterSchema.ts +++ b/explorer/src/components/Filters/filterSchema.ts @@ -18,6 +18,8 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ { label: '90', value: 90 }, { label: '100', value: 100 }, ], + tooltipInfo: + 'As a delegator you want to chose nodes with lower profit margin, meaning more payout for their delegators', }, stakeSaturation: { label: 'Stake saturation (%)', @@ -43,9 +45,10 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ }, ], max: upperSaturationValue, + tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards", }, stake: { - label: 'Stake (NYM)', + label: 'Routing Score (%)', id: EnumFilterKey.stake, value: [20, 90], min: 20, @@ -92,6 +95,7 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ label: '1B', }, ], + tooltipInfo: 'The higher the routing score the better the performance of the node and so its rewards', }, }); diff --git a/explorer/src/typeDefs/filters.ts b/explorer/src/typeDefs/filters.ts index 8ec2a918c9..07add70a85 100644 --- a/explorer/src/typeDefs/filters.ts +++ b/explorer/src/typeDefs/filters.ts @@ -15,6 +15,7 @@ export type TFilterItem = { min?: number; max?: number; scale?: (value: number) => number; + tooltipInfo?: string; }; export type TFilters = { [key in EnumFilterKey]: TFilterItem }; From 4ff80bbab2327197a15e23d8d7d872f6d5768e6e Mon Sep 17 00:00:00 2001 From: Gala Date: Tue, 2 Aug 2022 14:33:48 +0200 Subject: [PATCH 29/91] fixing dark mode progress bar --- .../balance/components/vesting-timeline.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx index 47fea9e9b1..c97786bfc0 100644 --- a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx +++ b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/no-array-index-key */ import React, { useContext } from 'react'; +import { useTheme } from '@mui/material/styles'; import { Box, Tooltip, Typography } from '@mui/material'; import { format } from 'date-fns'; import { AppContext } from '../../../context/main'; @@ -21,6 +22,9 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc userBalance: { currentVestingPeriod, vestingAccountInfo }, } = useContext(AppContext); + const theme = useTheme(); + const { mode } = theme.palette; + const nextPeriod = typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) @@ -30,11 +34,23 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc - + {vestingAccountInfo?.periods.map((period, i, arr) => ( = calculateMarkerPosition(arr.length, i) ? '#121726' : '#B9B9B9'} + color={ + +percentageComplete.toFixed(2) >= calculateMarkerPosition(arr.length, i) + ? mode === 'light' + ? '#121726' + : '#6cd925' + : '#B9B9B9' + } tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} key={i} /> From 06c4dd601dfef2c37ecaf3dde0ac5575f092f118 Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 14:06:33 +0200 Subject: [PATCH 30/91] filter by use routing score instead of stake parameter --- .../src/components/Filters/filterSchema.ts | 73 ++++++------------- explorer/src/context/main.tsx | 4 +- explorer/src/typeDefs/filters.ts | 2 +- 3 files changed, 24 insertions(+), 55 deletions(-) diff --git a/explorer/src/components/Filters/filterSchema.ts b/explorer/src/components/Filters/filterSchema.ts index 0dce1c327e..40f230e734 100644 --- a/explorer/src/components/Filters/filterSchema.ts +++ b/explorer/src/components/Filters/filterSchema.ts @@ -47,64 +47,33 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ max: upperSaturationValue, tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards", }, - stake: { - label: 'Routing Score (%)', - id: EnumFilterKey.stake, - value: [20, 90], - min: 20, - max: 90, + routingScore: { + label: 'Routing score (%)', + id: EnumFilterKey.routingScore, + value: [0, 100], marks: [ - { - value: 0, - label: '1', - }, - { - value: 10, - label: '10', - }, - { - value: 20, - label: '100', - }, - { - value: 30, - label: '1k', - }, - { - value: 40, - label: '10k', - }, - { - value: 50, - label: '100k', - }, - { - value: 60, - label: '1M', - }, - { - value: 70, - label: '10M', - }, - { - value: 80, - label: '100M', - }, - { - value: 90, - label: '1B', - }, + { label: '0', value: 0 }, + { label: '10', value: 10 }, + { label: '20', value: 20 }, + { label: '30', value: 30 }, + { label: '40', value: 40 }, + { label: '50', value: 50 }, + { label: '60', value: 60 }, + { label: '70', value: 70 }, + { label: '80', value: 80 }, + { label: '90', value: 90 }, + { label: '100', value: 100 }, ], tooltipInfo: 'The higher the routing score the better the performance of the node and so its rewards', }, }); -const formatStakeValuesToMinorDenom = ([value_1, value_2]: number[]) => { - const lowerValue = 10 ** (value_1 / 10) * 1_000_000; - const upperValue = 10 ** (value_2 / 10) * 1_000_000; +// const formatStakeValuesToMinorDenom = ([value_1, value_2]: number[]) => { +// const lowerValue = 10 ** (value_1 / 10) * 1_000_000; +// const upperValue = 10 ** (value_2 / 10) * 1_000_000; - return [lowerValue, upperValue]; -}; +// return [lowerValue, upperValue]; +// }; const formatStakeSaturationValues = ([value_1, value_2]: number[]) => { const lowerValue = value_1 / 100; @@ -114,7 +83,7 @@ const formatStakeSaturationValues = ([value_1, value_2]: number[]) => { }; export const formatOnSave = (filters: TFilters) => ({ - stake: formatStakeValuesToMinorDenom(filters.stake.value), + routingScore: filters.routingScore.value, profitMargin: filters.profitMargin.value, stakeSaturation: formatStakeSaturationValues(filters.stakeSaturation.value), }); diff --git a/explorer/src/context/main.tsx b/explorer/src/context/main.tsx index 7fca785209..9255eb20e3 100644 --- a/explorer/src/context/main.tsx +++ b/explorer/src/context/main.tsx @@ -102,8 +102,8 @@ export const MainContextProvider: React.FC = ({ children }) => { m.mix_node.profit_margin_percent <= filters.profitMargin[1] && m.stake_saturation >= filters.stakeSaturation[0] && m.stake_saturation <= filters.stakeSaturation[1] && - +m.pledge_amount.amount + +m.total_delegation.amount >= filters.stake[0] && - +m.pledge_amount.amount + +m.total_delegation.amount <= filters.stake[1], + m.avg_uptime >= filters.routingScore[0] && + m.avg_uptime <= filters.routingScore[1], ); setMixnodes({ data: filtered, isLoading: false }); }; diff --git a/explorer/src/typeDefs/filters.ts b/explorer/src/typeDefs/filters.ts index 07add70a85..2152f3a35f 100644 --- a/explorer/src/typeDefs/filters.ts +++ b/explorer/src/typeDefs/filters.ts @@ -4,7 +4,7 @@ import { Mark } from '@mui/base'; export enum EnumFilterKey { profitMargin = 'profitMargin', stakeSaturation = 'stakeSaturation', - stake = 'stake', + routingScore = 'routingScore', } export type TFilterItem = { From c8533e3ec8cad2f91e3f4d788857bffa44c85f85 Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 14:15:03 +0200 Subject: [PATCH 31/91] cleaning --- .../src/components/Filters/filterSchema.ts | 7 ------- .../balance/components/vesting-timeline.tsx | 20 ++----------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/explorer/src/components/Filters/filterSchema.ts b/explorer/src/components/Filters/filterSchema.ts index 40f230e734..a994b698a4 100644 --- a/explorer/src/components/Filters/filterSchema.ts +++ b/explorer/src/components/Filters/filterSchema.ts @@ -68,13 +68,6 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({ }, }); -// const formatStakeValuesToMinorDenom = ([value_1, value_2]: number[]) => { -// const lowerValue = 10 ** (value_1 / 10) * 1_000_000; -// const upperValue = 10 ** (value_2 / 10) * 1_000_000; - -// return [lowerValue, upperValue]; -// }; - const formatStakeSaturationValues = ([value_1, value_2]: number[]) => { const lowerValue = value_1 / 100; const upperValue = value_2 / 100; diff --git a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx index c97786bfc0..47fea9e9b1 100644 --- a/nym-wallet/src/pages/balance/components/vesting-timeline.tsx +++ b/nym-wallet/src/pages/balance/components/vesting-timeline.tsx @@ -1,6 +1,5 @@ /* eslint-disable react/no-array-index-key */ import React, { useContext } from 'react'; -import { useTheme } from '@mui/material/styles'; import { Box, Tooltip, Typography } from '@mui/material'; import { format } from 'date-fns'; import { AppContext } from '../../../context/main'; @@ -22,9 +21,6 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc userBalance: { currentVestingPeriod, vestingAccountInfo }, } = useContext(AppContext); - const theme = useTheme(); - const { mode } = theme.palette; - const nextPeriod = typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) @@ -34,23 +30,11 @@ export const VestingTimeline: React.FC<{ percentageComplete: number }> = ({ perc - + {vestingAccountInfo?.periods.map((period, i, arr) => ( = calculateMarkerPosition(arr.length, i) - ? mode === 'light' - ? '#121726' - : '#6cd925' - : '#B9B9B9' - } + color={+percentageComplete.toFixed(2) >= calculateMarkerPosition(arr.length, i) ? '#121726' : '#B9B9B9'} tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')} key={i} /> From c1fa92869a4f10e5d396537e15356b93fd544abf Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 16:49:47 +0200 Subject: [PATCH 32/91] fix light bg color --- nym-wallet/src/theme/theme.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/theme/theme.tsx b/nym-wallet/src/theme/theme.tsx index 6749d3d864..893c9fff46 100644 --- a/nym-wallet/src/theme/theme.tsx +++ b/nym-wallet/src/theme/theme.tsx @@ -61,7 +61,7 @@ const darkMode: NymPaletteVariant = { const lightMode: NymPaletteVariant = { mode: 'light', background: { - main: '#E5E5E5', + main: '#F4F6F8', paper: '#FFFFFF', warn: '#FFE600', grey: '#F5F5F5', From 14f9bf7234822befd436ee25974d0f4868778ce5 Mon Sep 17 00:00:00 2001 From: Gala Date: Wed, 3 Aug 2022 16:50:02 +0200 Subject: [PATCH 33/91] left nav spacing --- nym-wallet/src/components/Nav.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Nav.tsx b/nym-wallet/src/components/Nav.tsx index 387de1ac7a..0f660fa754 100644 --- a/nym-wallet/src/components/Nav.tsx +++ b/nym-wallet/src/components/Nav.tsx @@ -86,17 +86,24 @@ export const Nav = () => { } }) .map(({ Icon, onClick, label, route }) => ( - + - + Date: Wed, 3 Aug 2022 17:20:46 +0200 Subject: [PATCH 34/91] hover bg color in dark and light mode --- nym-wallet/src/components/Nav.tsx | 22 ++++++++++++++++++++-- nym-wallet/src/layouts/AppLayout.tsx | 8 ++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/nym-wallet/src/components/Nav.tsx b/nym-wallet/src/components/Nav.tsx index 0f660fa754..f8c10bd7b3 100644 --- a/nym-wallet/src/components/Nav.tsx +++ b/nym-wallet/src/components/Nav.tsx @@ -68,9 +68,16 @@ export const Nav = () => { display: 'flex', alignItems: 'center', justifyContent: 'flex-start', + marginLeft: 12, + marginRight: 12, }} > - + {routesSchema .filter(({ mode }) => { if (!mode) { @@ -86,7 +93,18 @@ export const Nav = () => { } }) .map(({ Icon, onClick, label, route }) => ( - + (theme.palette.mode === 'light' ? '#F9F9F9' : '#36393E') }, + }} + > { sx={{ background: (t) => t.palette.nym.nymWallet.nav.background, overflow: 'auto', - py: 3, - px: 5, + py: 5, + px: 0, }} display="flex" flexDirection="column" justifyContent="space-between" > - + {appVersion && ( - + Version {appVersion} )} From a57545521d3955852b0f9e162c8004e47955e41e Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 11:28:04 +0200 Subject: [PATCH 35/91] some refactor --- nym-wallet/src/components/Nav.tsx | 8 ++++---- nym-wallet/src/layouts/AppLayout.tsx | 1 - nym-wallet/src/theme/mui-theme.d.ts | 13 ++++++++----- nym-wallet/src/theme/theme.tsx | 6 ++++++ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/nym-wallet/src/components/Nav.tsx b/nym-wallet/src/components/Nav.tsx index f8c10bd7b3..f450f17bbd 100644 --- a/nym-wallet/src/components/Nav.tsx +++ b/nym-wallet/src/components/Nav.tsx @@ -99,10 +99,10 @@ export const Nav = () => { onClick={onClick} sx={{ cursor: 'pointer', - py: '16px', - paddingLeft: '32px', - borderRadius: '8px', - '&:hover': { backgroundColor: (theme) => (theme.palette.mode === 'light' ? '#F9F9F9' : '#36393E') }, + py: 2, + paddingLeft: 3.5, + borderRadius: 1, + '&:hover': { backgroundColor: (theme) => theme.palette.nym.nymWallet.hover.background }, }} > { background: (t) => t.palette.nym.nymWallet.nav.background, overflow: 'auto', py: 5, - px: 0, }} display="flex" flexDirection="column" diff --git a/nym-wallet/src/theme/mui-theme.d.ts b/nym-wallet/src/theme/mui-theme.d.ts index 83e2c36d79..1a26d34aff 100644 --- a/nym-wallet/src/theme/mui-theme.d.ts +++ b/nym-wallet/src/theme/mui-theme.d.ts @@ -64,6 +64,9 @@ declare module '@mui/material/styles' { nav: { background: string; }; + hover: { + background: string; + }; } /** @@ -82,7 +85,7 @@ declare module '@mui/material/styles' { /** * Add anything not palette related to the theme here */ - interface NymTheme {} + interface NymTheme { } /** * This augments the definitions of the MUI Theme with the Nym theme, as well as @@ -90,8 +93,8 @@ declare module '@mui/material/styles' { * * IMPORTANT: only add extensions to the interfaces above, do not modify the lines below */ - interface Theme extends NymTheme {} - interface ThemeOptions extends Partial {} - interface Palette extends NymPaletteAndNymWalletPalette {} - interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions {} + interface Theme extends NymTheme { } + interface ThemeOptions extends Partial { } + interface Palette extends NymPaletteAndNymWalletPalette { } + interface PaletteOptions extends NymPaletteAndNymWalletPaletteOptions { } } diff --git a/nym-wallet/src/theme/theme.tsx b/nym-wallet/src/theme/theme.tsx index 893c9fff46..3e479c1b25 100644 --- a/nym-wallet/src/theme/theme.tsx +++ b/nym-wallet/src/theme/theme.tsx @@ -56,6 +56,9 @@ const darkMode: NymPaletteVariant = { nav: { background: '#292E34', }, + hover: { + background: '#36393E', + }, }; const lightMode: NymPaletteVariant = { @@ -80,6 +83,9 @@ const lightMode: NymPaletteVariant = { nav: { background: '#FFFFFF', }, + hover: { + background: '#F9F9F9', + }, }; /** From b957b939cf6b1018158725e1620883ce015f194b Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Thu, 4 Aug 2022 11:01:23 +0100 Subject: [PATCH 36/91] Typo fix --- nym-connect/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-connect/README.md b/nym-connect/README.md index 5433a366d6..5a247235ab 100644 --- a/nym-connect/README.md +++ b/nym-connect/README.md @@ -32,7 +32,7 @@ yarn install ## Development mode -You can compile nym-connectin development mode by running the following command inside the `nym-connect` directory: +You can compile nym-connect in development mode by running the following command inside the `nym-connect` directory: ``` yarn dev From 651c3141829175d3a86bfda813e9f59fd45c2a07 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 4 Aug 2022 13:20:00 +0200 Subject: [PATCH 37/91] feat(wallet-delegation): new confirmation modal ui --- .../components/Delegation/DelegateModal.tsx | 32 ++++--- .../Delegation/DelegationModal.stories.tsx | 16 ---- .../components/Delegation/DelegationModal.tsx | 89 +++++-------------- nym-wallet/src/pages/delegation/index.tsx | 6 +- 4 files changed, 43 insertions(+), 100 deletions(-) diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index c19afe974d..78bce2559d 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -184,23 +184,24 @@ export const DelegateModal: React.FC<{ } }} header={header || 'Delegate'} - subHeader="Delegate to mixnode" okLabel={buttonText || 'Delegate stake'} okDisabled={!isValidated} sx={sx} backdropProps={backdropProps} > - + + + - + @@ -241,7 +242,7 @@ export const DelegateModal: React.FC<{ divider /> + + Est. fee for this transaction will be cauculated in the next page + ); }; diff --git a/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx b/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx index 635c48f153..8694938a63 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.stories.tsx @@ -29,9 +29,6 @@ const transactionForDarkTheme = { url: 'https://sandbox-blocks.nymtech.net/transactions/11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CFO', hash: '11ED7B9E21534A9421834F52FED5103DC6E982949C06335F5E12EFC71DAF0CF0', }; -const balance = '104 NYMT'; -const balanceVested = '12 NYMT'; -const recipient = 'nymt1923pujepxfnv8dqyxqrl078s4ysf3xn2p7z2xa'; const Content: React.FC<{ children: React.ReactElement; handleClick: () => void }> = ({ children, @@ -78,8 +75,6 @@ export const DelegateSuccess = () => { status="success" action="delegate" message="You delegated 5 NYM" - recipient={recipient} - balance={balance} transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]} {...storybookStyles(theme)} /> @@ -99,8 +94,6 @@ export const UndelegateSuccess = () => { status="success" action="undelegate" message="You undelegated 5 NYM" - recipient={recipient} - balance={balance} transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]} {...storybookStyles(theme)} /> @@ -120,8 +113,6 @@ export const RedeemSuccess = () => { status="success" action="redeem" message="42 NYM" - recipient={recipient} - balance={balance} transactions={ theme.palette.mode === 'light' ? [transaction, transaction] @@ -145,9 +136,6 @@ export const RedeemWithVestedSuccess = () => { status="success" action="redeem" message="42 NYM" - recipient={recipient} - balance={balance} - balanceVested={balanceVested} transactions={ theme.palette.mode === 'light' ? [transaction, transaction] @@ -171,8 +159,6 @@ export const RedeemAllSuccess = () => { status="success" action="redeem-all" message="42 NYM" - recipient={recipient} - balance={balance} transactions={ theme.palette.mode === 'light' ? [transaction, transaction] @@ -196,8 +182,6 @@ export const Error = () => { status="error" action="redeem-all" message="Minim esse veniam Lorem id velit Lorem eu eu est. Excepteur labore sunt do proident proident sint aliquip consequat Lorem sint non nulla ad excepteur." - recipient={recipient} - balance={balance} transactions={theme.palette.mode === 'light' ? [transaction] : [transactionForDarkTheme]} {...storybookStyles(theme)} /> diff --git a/nym-wallet/src/components/Delegation/DelegationModal.tsx b/nym-wallet/src/components/Delegation/DelegationModal.tsx index 13846a67a2..ea48817f4f 100644 --- a/nym-wallet/src/components/Delegation/DelegationModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegationModal.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Box, Button, Modal, Typography, SxProps } from '@mui/material'; +import { Box, Button, Modal, Typography, SxProps, Stack } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { Console } from 'src/utils/console'; import { modalStyle } from '../Modals/styles'; import { LoadingModal } from '../Modals/LoadingModal'; +import { ConfirmationModal } from '../Modals/ConfirmationModal'; export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound'; @@ -15,9 +16,9 @@ const actionToHeader = (action: ActionType): string => { case 'redeem-all': return 'All rewards redeemed successfully'; case 'delegate': - return 'Delegation complete'; + return 'Delegation successful'; case 'undelegate': - return 'Undelegation complete'; + return 'Undelegation successful'; case 'compound': return 'Rewards compounded successfully'; default: @@ -29,9 +30,6 @@ export type DelegationModalProps = { status: 'loading' | 'success' | 'error'; action: ActionType; message?: string; - recipient?: string; - balance?: string; - balanceVested?: string; transactions?: { url: string; hash: string; @@ -45,20 +43,7 @@ export const DelegationModal: React.FC< sx?: SxProps; backdropProps?: object; } -> = ({ - status, - action, - message, - recipient, - balance, - balanceVested, - transactions, - open, - onClose, - children, - sx, - backdropProps, -}) => { +> = ({ status, action, message, transactions, open, onClose, children, sx, backdropProps }) => { if (status === 'loading') return ; if (status === 'error') { @@ -81,54 +66,28 @@ export const DelegationModal: React.FC< } transactions?.map((transaction) => Console.log('action', action, 'status', status, 'key', transaction.hash)); - return ( - - - theme.palette.success.main} mb={1}> - {actionToHeader(action)} - - - {message} - - {recipient && ( - theme.palette.text.secondary}> - Recipient: {recipient} - + return ( + {})} + title={actionToHeader(action)} + confirmButton="Done" + > + + {message && {message}} + {transactions?.length === 1 && ( + )} - {balanceVested ? ( - <> - theme.palette.text.secondary}> - Your current balance: {balance?.toUpperCase()} - - theme.palette.text.secondary}> - ({balanceVested.toUpperCase()} is unlocked in your vesting account) - - - ) : ( - theme.palette.text.secondary}> - Your current balance: {balance?.toUpperCase()} - - )} - {transactions && ( - theme.palette.text.secondary}> - Check the transaction {transactions.length > 1 ? 'hashes' : 'hash'}: - {transactions.map((transaction) => ( - + {transactions && transactions.length > 1 && ( + + View the transactions on blockchain: + {transactions.map(({ url, hash }) => ( + ))} - + )} - {children} - - - + + ); }; diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index 8d243e07e9..2fac87149b 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -138,7 +138,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { setConfirmationModalProps({ status: 'success', action: 'delegate', - message: 'Delegations can take up to one hour to process', + message: 'This operation can take up to one hour to process', ...balances, transactions: [ { url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`, hash: tx.transaction_hash }, @@ -240,11 +240,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { try { const txs = await claimRewards(identityKey, fee); - const bal = await userBalance(); setConfirmationModalProps({ status: 'success', action: 'redeem', - balance: bal?.printable_balance || '-', transactions: txs.map((tx) => ({ url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`, hash: tx.transaction_hash, @@ -270,11 +268,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { try { const txs = await compoundRewards(identityKey, fee); - const bal = await userBalance(); setConfirmationModalProps({ status: 'success', action: 'compound', - balance: bal?.printable_balance || '-', transactions: txs.map((tx) => ({ url: `${urls(network).blockExplorer}/transaction/${tx.transaction_hash}`, hash: tx.transaction_hash, From 3f544dbc69b77def87c79fcba6969991146731b1 Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 14:59:17 +0200 Subject: [PATCH 38/91] adding Advanced filtering text and hover fix --- explorer/src/components/Filters/Filters.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 355be16223..9d79fda9cc 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -125,6 +125,7 @@ export const Filters = () => { message="Filters applied" TransitionComponent={Slide} transitionDuration={250} + id="hello" > { Filters applied - + + Advanced filtering From c80d8d354acf3d4f6e2b2cef299fc41b6a1a093e Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 15:17:44 +0200 Subject: [PATCH 39/91] adding nodes number info --- explorer/src/components/Filters/Filters.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 9d79fda9cc..c53fca153b 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -52,7 +52,7 @@ const FilterItem = ({ ); export const Filters = () => { - const { filterMixnodes, fetchMixnodes } = useMainContext(); + const { filterMixnodes, fetchMixnodes, mixnodes } = useMainContext(); const { status } = useParams<{ status: MixnodeStatusWithAll | undefined }>(); const isMobile = useIsMobile(); @@ -125,7 +125,6 @@ export const Filters = () => { message="Filters applied" TransitionComponent={Slide} transitionDuration={250} - id="hello" > { Clear } - sx={{ width: 300 }} + sx={{ width: 300, alignItems: 'center' }} > - Filters applied + {mixnodes?.data?.length} mix nodes matched your criteria From 57a9f18f5a6951ce14e824c4283f2154fe92138c Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 16:30:33 +0200 Subject: [PATCH 40/91] fixing padding marging and wording --- explorer/src/components/Filters/Filters.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index c53fca153b..2f324451da 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -125,24 +125,29 @@ export const Filters = () => { message="Filters applied" TransitionComponent={Slide} transitionDuration={250} + sx={{ + '& .MuiAlert-action': { + p: 0, + mr: 0, + }, + }} > - Clear + } - sx={{ width: 300, alignItems: 'center' }} + sx={{ width: 400, alignItems: 'center' }} > - {mixnodes?.data?.length} mix nodes matched your criteria + {mixnodes?.data?.length} mixnodes matched your criteria - - Advanced filtering - - + } onClick={handleToggleShowFilters}> + Advanced filters + Mixnode filters From 2e495f87ab0f36e0206e3062849a14d9e592d237 Mon Sep 17 00:00:00 2001 From: pierre Date: Thu, 4 Aug 2022 16:35:14 +0200 Subject: [PATCH 41/91] refactor(wallet-delegation): ModalListItem component, pass colon in label value --- .../src/components/ConfirmTX.stories.tsx | 6 +++--- .../components/Delegation/DelegateModal.tsx | 20 +++++++++---------- .../components/Delegation/UndelegateModal.tsx | 2 +- nym-wallet/src/components/Modals/ModalFee.tsx | 2 +- .../src/components/Modals/ModalListItem.tsx | 20 ++++++++++--------- .../src/components/Send/SendDetailsModal.tsx | 8 ++++---- .../src/components/Send/SendInputModal.tsx | 4 ++-- .../balance/components/TransferModal.tsx | 4 ++-- .../bond/components/ConfirmationModal.tsx | 4 ++-- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/nym-wallet/src/components/ConfirmTX.stories.tsx b/nym-wallet/src/components/ConfirmTX.stories.tsx index 2eec07b7e3..77bbf4fdcf 100644 --- a/nym-wallet/src/components/ConfirmTX.stories.tsx +++ b/nym-wallet/src/components/ConfirmTX.stories.tsx @@ -10,9 +10,9 @@ export default { const Template: ComponentStory = (args) => ( - - - + + + ); diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index 78bce2559d..be32256409 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -168,8 +168,8 @@ export const DelegateModal: React.FC<{ onPrev={resetFeeState} onConfirm={handleOk} > - - + + ); } @@ -189,7 +189,7 @@ export const DelegateModal: React.FC<{ sx={sx} backdropProps={backdropProps} > - + - + - + - - Est. fee for this transaction will be cauculated in the next page - + ); }; diff --git a/nym-wallet/src/components/Delegation/UndelegateModal.tsx b/nym-wallet/src/components/Delegation/UndelegateModal.tsx index 1c05390090..227cc7b9f5 100644 --- a/nym-wallet/src/components/Delegation/UndelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/UndelegateModal.tsx @@ -55,7 +55,7 @@ export const UndelegateModal: React.FC<{ /> - + diff --git a/nym-wallet/src/components/Modals/ModalFee.tsx b/nym-wallet/src/components/Modals/ModalFee.tsx index c64fa0286e..31458a5356 100644 --- a/nym-wallet/src/components/Modals/ModalFee.tsx +++ b/nym-wallet/src/components/Modals/ModalFee.tsx @@ -13,5 +13,5 @@ const getValue = ({ fee, isLoading, error }: TFeeProps) => { }; export const ModalFee = ({ fee, isLoading, error }: TFeeProps) => ( - + ); diff --git a/nym-wallet/src/components/Modals/ModalListItem.tsx b/nym-wallet/src/components/Modals/ModalListItem.tsx index f0458a05ec..955e61089b 100644 --- a/nym-wallet/src/components/Modals/ModalListItem.tsx +++ b/nym-wallet/src/components/Modals/ModalListItem.tsx @@ -7,20 +7,22 @@ export const ModalListItem: React.FC<{ divider?: boolean; hidden?: boolean; strong?: boolean; - value: React.ReactNode; + value?: React.ReactNode; }> = ({ label, value, hidden, divider, strong }) => ( diff --git a/nym-wallet/src/components/Send/SendDetailsModal.tsx b/nym-wallet/src/components/Send/SendDetailsModal.tsx index efbf64dd6c..6523b5ed91 100644 --- a/nym-wallet/src/components/Send/SendDetailsModal.tsx +++ b/nym-wallet/src/components/Send/SendDetailsModal.tsx @@ -39,11 +39,11 @@ export const SendDetailsModal = ({ backdropProps={backdropProps} > - - - + + + diff --git a/nym-wallet/src/components/Send/SendInputModal.tsx b/nym-wallet/src/components/Send/SendInputModal.tsx index c6a02414a5..f84e248141 100644 --- a/nym-wallet/src/components/Send/SendInputModal.tsx +++ b/nym-wallet/src/components/Send/SendInputModal.tsx @@ -77,8 +77,8 @@ export const SendInputModal = ({ - - + + Est. fee for this transaction will be show on the next page diff --git a/nym-wallet/src/pages/balance/components/TransferModal.tsx b/nym-wallet/src/pages/balance/components/TransferModal.tsx index 02a8318064..451a9899e8 100644 --- a/nym-wallet/src/pages/balance/components/TransferModal.tsx +++ b/nym-wallet/src/pages/balance/components/TransferModal.tsx @@ -87,12 +87,12 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => { ) : ( <> } divider /> diff --git a/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx b/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx index 32c6634c5c..44dab1bd5f 100644 --- a/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx +++ b/nym-wallet/src/pages/bond/components/ConfirmationModal.tsx @@ -20,8 +20,8 @@ export const ConfirmationModal = ({ }) => ( - - + + From 3a79f43a8dfb404c0fb373be16cf51aa09a84ea3 Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 17:23:32 +0200 Subject: [PATCH 42/91] styling --- explorer/src/components/Filters/Filters.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 2f324451da..3a7614c987 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -126,9 +126,11 @@ export const Filters = () => { TransitionComponent={Slide} transitionDuration={250} sx={{ + '& .MuiAlert-outlined': { pr: '11px' }, '& .MuiAlert-action': { p: 0, - mr: 0, + marginRight: 0, + alignItems: 'center', }, }} > @@ -136,11 +138,10 @@ export const Filters = () => { severity="info" variant={isMobile ? 'standard' : 'outlined'} action={ - From eee1abe593d58a9d90e6f297d82237d1abc52bfa Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 4 Aug 2022 18:15:12 +0200 Subject: [PATCH 44/91] removing extra styles --- explorer/src/components/Filters/Filters.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 3c78f27906..1a04688428 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -6,7 +6,6 @@ import { DialogContent, DialogActions, DialogTitle, - IconButton, Slider, Typography, Box, @@ -125,20 +124,12 @@ export const Filters = () => { message="Filters applied" TransitionComponent={Slide} transitionDuration={250} - sx={{ - '& .MuiAlert-outlined': { pr: '11px' }, - '& .MuiAlert-action': { - p: 0, - marginRight: 0, - alignItems: 'center', - }, - }} > + } From 067f3e6f1a3df923d9794d057dce19bf634bfd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 5 Aug 2022 15:59:34 +0300 Subject: [PATCH 45/91] validator-api: handle SIGTERM (#1496) * validator-api: handle SIGTERM * Update CHANGELOG --- CHANGELOG.md | 2 + Cargo.lock | 1 + validator-api/Cargo.toml | 1 + validator-api/src/contract_cache/mod.rs | 27 +++++--- validator-api/src/main.rs | 62 +++++++++++++++---- validator-api/src/network_monitor/mod.rs | 9 ++- .../src/network_monitor/monitor/mod.rs | 13 ++-- .../src/network_monitor/monitor/receiver.rs | 8 ++- .../src/node_status_api/uptime_updater.rs | 30 +++++---- 9 files changed, 110 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12ec2d2a3..e0696c4e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - 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]) +- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1496]). ### Changed @@ -77,6 +78,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#1478]: https://github.com/nymtech/nym/pull/1478 [#1482]: https://github.com/nymtech/nym/pull/1482 [#1487]: https://github.com/nymtech/nym/pull/1487 +[#1496]: https://github.com/nymtech/nym/pull/1496 ## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22) diff --git a/Cargo.lock b/Cargo.lock index e7bcd513f7..f8ffd17c35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3325,6 +3325,7 @@ dependencies = [ "serde", "serde_json", "sqlx", + "task", "thiserror", "time 0.3.9", "tokio", diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index f1277fd6d1..8ec660cdb7 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -58,6 +58,7 @@ gateway-client = { path="../common/client-libs/gateway-client" } mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" } multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nymsphinx = { path="../common/nymsphinx" } +task = { path = "../common/task" } topology = { path="../common/topology" } validator-api-requests = { path = "validator-api-requests" } validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] } diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index d46b6f0e15..593fe63b88 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -14,6 +14,7 @@ use okapi::openapi3::OpenApi; use rocket::Route; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; +use task::ShutdownListener; use rocket::fairing::AdHoc; use serde::Serialize; @@ -252,20 +253,26 @@ impl ValidatorCacheRefresher { Ok(()) } - pub(crate) async fn run(&self) + pub(crate) async fn run(&self, mut shutdown: ShutdownListener) where C: CosmWasmClient + Sync, { let mut interval = time::interval(self.caching_interval); - loop { - interval.tick().await; - if let Err(err) = self.refresh_cache().await { - error!("Failed to refresh validator cache - {}", err); - } else { - // relaxed memory ordering is fine here. worst case scenario network monitor - // will just have to wait for an additional backoff to see the change. - // And so this will not really incur any performance penalties by setting it every loop iteration - self.cache.initialised.store(true, Ordering::Relaxed) + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.tick() => { + if let Err(err) = self.refresh_cache().await { + error!("Failed to refresh validator cache - {}", err); + } else { + // relaxed memory ordering is fine here. worst case scenario network monitor + // will just have to wait for an additional backoff to see the change. + // And so this will not really incur any performance penalties by setting it every loop iteration + self.cache.initialised.store(true, Ordering::Relaxed) + } + } + _ = shutdown.recv() => { + trace!("UpdateHandler: Received shutdown"); + } } } } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index c085d8315d..b027b77e12 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -31,6 +31,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::{fs, process}; +use task::ShutdownNotifier; use tokio::sync::Notify; // use validator_client::nymd::SigningNymdClient; // use validator_client::ValidatorClientError; @@ -226,14 +227,44 @@ fn parse_args<'a>() -> ArgMatches<'a> { base_app.get_matches() } -async fn wait_for_interrupt() { - if let Err(e) = tokio::signal::ctrl_c().await { - error!( - "There was an error while capturing SIGINT - {:?}. We will terminate regardless", - e - ); +async fn wait_for_interrupt(mut shutdown: ShutdownNotifier) { + wait_for_signal().await; + + log::info!("Sending shutdown"); + shutdown.signal_shutdown().ok(); + + log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); + shutdown.wait_for_shutdown().await; + + log::info!("Stopping nym validator API"); +} + +#[cfg(unix)] +async fn wait_for_signal() { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); + let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel"); + + tokio::select! { + _ = tokio::signal::ctrl_c() => { + log::info!("Received SIGINT"); + }, + _ = sigterm.recv() => { + log::info!("Received SIGTERM"); + } + _ = sigquit.recv() => { + log::info!("Received SIGQUIT"); + } + } +} + +#[cfg(not(unix))] +async fn wait_for_signal() { + tokio::select! { + _ = tokio::signal::ctrl_c() => { + log::info!("Received SIGINT"); + }, } - println!("Received SIGINT - the network monitor will terminate now"); } fn setup_logging() { @@ -547,6 +578,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { let signing_nymd_client = Client::new_signing(&config); let liftoff_notify = Arc::new(Notify::new()); + let shutdown = ShutdownNotifier::default(); // let's build our rocket! let rocket = setup_rocket( @@ -569,7 +601,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { // setup our daily uptime updater. Note that if network monitor is disabled, then we have // no data for the updates and hence we don't need to start it up let uptime_updater = HistoricalUptimeUpdater::new(storage.clone()); - tokio::spawn(async move { uptime_updater.run().await }); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { uptime_updater.run(shutdown_listener).await }); // spawn the cache refresher let validator_cache_refresher = ValidatorCacheRefresher::new( @@ -578,7 +611,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { validator_cache.clone(), Some(storage.clone()), ); - tokio::spawn(async move { validator_cache_refresher.run().await }); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await }); // spawn rewarded set updater let mut rewarded_set_updater = @@ -593,11 +627,15 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { None, ); + let shutdown_listener = shutdown.subscribe(); // spawn our cacher - tokio::spawn(async move { validator_cache_refresher.run().await }); + tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await }); } // launch the rocket! + // Rocket handles shutdown on it's own, but its shutdown handling should be incorporated + // with that of the rest of the tasks. + // Currently it's runtime is forcefully terminated once the validator-api exits. let shutdown_handle = rocket.shutdown(); tokio::spawn(rocket.launch()); @@ -610,12 +648,12 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { // we're ready to go! spawn the network monitor! let runnables = monitor_builder.build().await; - runnables.spawn_tasks(); + runnables.spawn_tasks(&shutdown); } else { info!("Network monitoring is disabled."); } - wait_for_interrupt().await; + wait_for_interrupt(shutdown).await; shutdown_handle.notify(); Ok(()) diff --git a/validator-api/src/network_monitor/mod.rs b/validator-api/src/network_monitor/mod.rs index 0a15b87053..49d90deccb 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/validator-api/src/network_monitor/mod.rs @@ -6,6 +6,7 @@ use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; use std::sync::Arc; +use task::ShutdownNotifier; use crate::config::Config; use crate::contract_cache::ValidatorCache; @@ -131,11 +132,13 @@ impl NetworkMonitorRunnables { // TODO: note, that is not exactly doing what we want, because when // `ReceivedProcessor` is constructed, it already spawns a future // this needs to be refactored! - pub(crate) fn spawn_tasks(self) { + pub(crate) fn spawn_tasks(self, shutdown: &ShutdownNotifier) { let mut packet_receiver = self.packet_receiver; let mut monitor = self.monitor; - tokio::spawn(async move { packet_receiver.run().await }); - tokio::spawn(async move { monitor.run().await }); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { packet_receiver.run(shutdown_listener).await }); + let shutdown_listener = shutdown.subscribe(); + tokio::spawn(async move { monitor.run(shutdown_listener).await }); } } diff --git a/validator-api/src/network_monitor/monitor/mod.rs b/validator-api/src/network_monitor/monitor/mod.rs index f6fd3fe881..2cd1950f96 100644 --- a/validator-api/src/network_monitor/monitor/mod.rs +++ b/validator-api/src/network_monitor/monitor/mod.rs @@ -12,6 +12,7 @@ use crate::storage::ValidatorApiStorage; use log::{debug, error, info}; use std::collections::{HashMap, HashSet}; use std::process; +use task::ShutdownListener; use tokio::time::{sleep, Duration, Instant}; pub(crate) mod gateway_clients_cache; @@ -296,7 +297,7 @@ impl Monitor { self.test_nonce += 1; } - pub(crate) async fn run(&mut self) { + pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { self.received_processor.start_receiving(); // wait for validator cache to be ready @@ -308,9 +309,13 @@ impl Monitor { .spawn_gateways_pinger(self.gateway_ping_interval); let mut run_interval = tokio::time::interval(self.run_interval); - loop { - run_interval.tick().await; - self.test_run().await; + while !shutdown.is_shutdown() { + tokio::select! { + _ = run_interval.tick() => self.test_run().await, + _ = shutdown.recv() => { + trace!("UpdateHandler: Received shutdown"); + } + } } } } diff --git a/validator-api/src/network_monitor/monitor/receiver.rs b/validator-api/src/network_monitor/monitor/receiver.rs index f837cb9f80..36913241db 100644 --- a/validator-api/src/network_monitor/monitor/receiver.rs +++ b/validator-api/src/network_monitor/monitor/receiver.rs @@ -7,6 +7,7 @@ use crypto::asymmetric::identity; use futures::channel::mpsc; use futures::StreamExt; use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver}; +use task::ShutdownListener; pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender; pub(crate) type GatewayClientUpdateReceiver = mpsc::UnboundedReceiver; @@ -55,8 +56,8 @@ impl PacketReceiver { .expect("packet processor seems to have crashed!"); } - pub(crate) async fn run(&mut self) { - loop { + pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { + while !shutdown.is_shutdown() { tokio::select! { // unwrap here is fine as it can only return a `None` if the PacketSender has died // and if that was the case, then the entire monitor is already in an undefined state @@ -67,6 +68,9 @@ impl PacketReceiver { Some((_gateway_id, message)) = self.gateways_reader.stream_map().next() => { self.process_gateway_messages(message) } + _ = shutdown.recv() => { + trace!("UpdateHandler: Received shutdown"); + } } } } diff --git a/validator-api/src/node_status_api/uptime_updater.rs b/validator-api/src/node_status_api/uptime_updater.rs index 417a9a30b2..56e7655aae 100644 --- a/validator-api/src/node_status_api/uptime_updater.rs +++ b/validator-api/src/node_status_api/uptime_updater.rs @@ -7,6 +7,7 @@ use crate::node_status_api::models::{ use crate::node_status_api::ONE_DAY; use crate::storage::ValidatorApiStorage; use log::error; +use task::ShutdownListener; use time::OffsetDateTime; use tokio::time::sleep; @@ -67,18 +68,23 @@ impl HistoricalUptimeUpdater { Ok(()) } - pub(crate) async fn run(&self) { - loop { - // start any updates a day after starting the task so that we would have complete data - sleep(ONE_DAY).await; - if let Err(err) = self.update_uptimes().await { - // normally that would have been a warning rather than an error, - // however, in this case it implies some underlying issues with our database - // that might affect the entire program - error!( - "We failed to update daily uptimes of active nodes - {}", - err - ) + pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { + while !shutdown.is_shutdown() { + tokio::select! { + _ = sleep(ONE_DAY) => { + if let Err(err) = self.update_uptimes().await { + // normally that would have been a warning rather than an error, + // however, in this case it implies some underlying issues with our database + // that might affect the entire program + error!( + "We failed to update daily uptimes of active nodes - {}", + err + ) + } + } + _ = shutdown.recv() => { + trace!("UpdateHandler: Received shutdown"); + } } } } From 4c8e59e6fc41e69c2f1e0db568ca93466f62ee9f Mon Sep 17 00:00:00 2001 From: tommy Date: Fri, 5 Aug 2022 17:11:35 +0200 Subject: [PATCH 46/91] fix .env files for explorer. --- explorer/.env.prod | 1 + explorer/.env.qa | 3 ++- explorer/src/api/constants.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/explorer/.env.prod b/explorer/.env.prod index c6d0a35f6d..e053bd085a 100644 --- a/explorer/.env.prod +++ b/explorer/.env.prod @@ -1,5 +1,6 @@ EXPLORER_API_URL=https://explorer.nymtech.net/api/v1 VALIDATOR_API_URL=https://validator.nymtech.net +VALIDATOR_URL=https://rpc.nyx.nodes.guru BIG_DIPPER_URL=https://blocks.nymtech.net CURRENCY_DENOM=unym CURRENCY_STAKING_DENOM=unyx diff --git a/explorer/.env.qa b/explorer/.env.qa index d345c26284..5764039056 100644 --- a/explorer/.env.qa +++ b/explorer/.env.qa @@ -1,5 +1,6 @@ EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://qa-validator.nymtech.net +VALIDATOR_API_URL=https://qa-validator-api.nymtech.net +VALIDATOR_URL=https://qa-validator.nymtech.net BIG_DIPPER_URL=https://qa-blocks.nymtech.net CURRENCY_DENOM=unymt CURRENCY_STAKING_DENOM=unyxt diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index 5cff9cc000..109586c0fd 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -1,6 +1,7 @@ // master APIs export const API_BASE_URL = process.env.EXPLORER_API_URL; export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL; +export const VALIDATOR_URL = process.env.VALIDATOR_URL; export const BIG_DIPPER = process.env.BIG_DIPPER_URL; // specific API routes @@ -9,7 +10,7 @@ export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`; -export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`; +export const VALIDATORS_API = `${VALIDATOR_URL}/validators`; export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. From fa354016e089990e46da8b00531180169c327161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 8 Aug 2022 09:49:52 +0100 Subject: [PATCH 47/91] Removed useless into() conversion --- common/types/src/currency.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/types/src/currency.rs b/common/types/src/currency.rs index be58e48d44..d2bafe589c 100644 --- a/common/types/src/currency.rs +++ b/common/types/src/currency.rs @@ -507,7 +507,7 @@ mod test { for (expected, raw_display) in values { let coin = DecCoin { - denom: Network::MAINNET.mix_denom().display.into(), + denom: Network::MAINNET.mix_denom().display, amount: raw_display.parse().unwrap(), }; let base = reg.attempt_convert_to_base_coin(coin).unwrap(); From e8e2f195e6adb4f2c0f7afa272bc279c7785cf61 Mon Sep 17 00:00:00 2001 From: Gala Date: Mon, 8 Aug 2022 14:09:33 +0200 Subject: [PATCH 48/91] fixing text color --- explorer/src/components/Filters/Filters.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/explorer/src/components/Filters/Filters.tsx b/explorer/src/components/Filters/Filters.tsx index 1a04688428..2334477c02 100644 --- a/explorer/src/components/Filters/Filters.tsx +++ b/explorer/src/components/Filters/Filters.tsx @@ -128,6 +128,7 @@ export const Filters = () => { t.palette.info.light }} action={ - t.palette.nym.text.muted }}> + theme.palette.nym.text.muted }}> How to set up multiple accounts @@ -29,7 +29,7 @@ export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handle (t.palette.mode === 'dark' ? { bgcolor: (t) => t.palette.background.paper } : {})} + sx={(t) => (t.palette.mode === 'dark' ? { bgcolor: (theme) => theme.palette.background.paper } : {})} > In order to create multiple accounts your wallet needs a password. Follow steps below to create password. diff --git a/nym-wallet/src/components/ActionsMenu.tsx b/nym-wallet/src/components/ActionsMenu.tsx new file mode 100644 index 0000000000..63e8193833 --- /dev/null +++ b/nym-wallet/src/components/ActionsMenu.tsx @@ -0,0 +1,42 @@ +import React, { useRef } from 'react'; +import { MoreVertSharp } from '@mui/icons-material'; +import { IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from '@mui/material'; + +export const ActionsMenu: React.FC<{ open: boolean; onOpen: () => void; onClose: () => void }> = ({ + children, + open, + onOpen, + onClose, +}) => { + const anchorEl: any = useRef(); + + return ( + <> + + + + + {children} + + + ); +}; + +export const ActionsMenuItem = ({ + title, + description, + onClick, + Icon, + disabled, +}: { + title: string; + description?: string; + onClick?: () => void; + Icon?: React.ReactNode; + disabled?: boolean; +}) => ( + + {Icon} + + +); diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 3bc8d6bebb..9aedf14d1a 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -7,13 +7,11 @@ import ModeNightOutlinedIcon from '@mui/icons-material/ModeNightOutlined'; import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'; import { AppContext } from '../context/main'; import { NetworkSelector } from './NetworkSelector'; -import { Node as NodeIcon } from '../svg-icons/node'; import { MultiAccounts } from './Accounts'; import { config } from '../config'; export const AppBar = () => { - const { logOut, handleShowTerminal, appEnv, handleShowSettings, showSettings, mode, handleSwitchMode } = - useContext(AppContext); + const { logOut, handleShowTerminal, appEnv, mode, handleSwitchMode } = useContext(AppContext); const navigate = useNavigate(); return ( @@ -31,7 +29,7 @@ export const AppBar = () => { - {mode === 'light' ? ( + {mode === 'dark' ? ( ) : ( @@ -45,15 +43,6 @@ export const AppBar = () => { )} - - - - - { - const { showSettings, handleShowTerminal, appEnv, handleShowSettings, logOut, mode, handleSwitchMode } = - useContext(AppContext); - - return ( - - - - - - - - - - - - - - - {mode === 'light' ? ( - - ) : ( - - )} - - - {(appEnv?.SHOW_TERMINAL || config.IS_DEV_MODE) && ( - - - - - - )} - - - - - - - - - - - - - - - ); -}; diff --git a/nym-wallet/src/components/AppBar/index.ts b/nym-wallet/src/components/AppBar/index.ts deleted file mode 100644 index 60f2f0e90b..0000000000 --- a/nym-wallet/src/components/AppBar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './AppBar'; diff --git a/nym-wallet/src/components/Bonding/Bond.tsx b/nym-wallet/src/components/Bonding/Bond.tsx new file mode 100644 index 0000000000..3392cde43f --- /dev/null +++ b/nym-wallet/src/components/Bonding/Bond.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Box, Button, Typography } from '@mui/material'; +import { NymCard } from '../NymCard'; + +export const Bond = ({ + onBond, + disabled, +}: { + onBond: () => void; + + disabled: boolean; +}) => ( + + + Bond a mixnode or a gateway + + + + + +); diff --git a/nym-wallet/src/components/Bonding/BondedGateway.tsx b/nym-wallet/src/components/Bonding/BondedGateway.tsx new file mode 100644 index 0000000000..2ae9b26608 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedGateway.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { TBondedGateway, urls } from 'src/context'; +import { NymCard } from 'src/components'; +import { Network } from 'src/types'; +import { IdentityKey } from 'src/components/IdentityKey'; +import { Cell, Header, NodeTable } from './NodeTable'; +import { BondedGatewayActions, TBondedGatwayActions } from './BondedGatewayAction'; + +const headers: Header[] = [ + { + header: 'IP', + id: 'ip', + sx: { pl: 0 }, + }, + { + header: 'Bond', + id: 'bond', + }, + { + id: 'menu-button', + sx: { width: 34, maxWidth: 34 }, + }, +]; + +export const BondedGateway = ({ + gateway, + network, + onActionSelect, +}: { + gateway: TBondedGateway; + network?: Network; + onActionSelect: (action: TBondedGatwayActions) => void; +}) => { + const { name, bond, ip, identityKey } = gateway; + const cells: Cell[] = [ + { + cell: ip, + id: 'stake-saturation-cell', + }, + { + cell: `${bond.amount} ${bond.denom}`, + id: 'stake-cell', + sx: { pl: 0 }, + }, + + { + cell: , + id: 'actions-cell', + align: 'right', + }, + ]; + + return ( + + + Gateway + + + {name && ( + + {name} + + )} + + + } + > + + {network && ( + + Check more stats of your node on the{' '} + + explorer + + + )} + + ); +}; diff --git a/nym-wallet/src/components/Bonding/BondedGatewayAction.tsx b/nym-wallet/src/components/Bonding/BondedGatewayAction.tsx new file mode 100644 index 0000000000..c511d07bf4 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedGatewayAction.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu'; +import { Unbond as UnbondIcon } from '../../svg-icons'; + +export type TBondedGatwayActions = 'unbond'; + +export const BondedGatewayActions = ({ + onActionSelect, +}: { + onActionSelect: (action: TBondedGatwayActions) => void; +}) => { + const [isOpen, setIsOpen] = useState(false); + + const handleOpen = () => setIsOpen(true); + const handleClose = () => setIsOpen(false); + + const handleActionClick = (action: TBondedGatwayActions) => { + onActionSelect(action); + handleClose(); + }; + + return ( + + } + onClick={() => handleActionClick('unbond')} + /> + + ); +}; diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx new file mode 100644 index 0000000000..d602eeacb8 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { Box, Button, Stack, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { TBondedMixnode, urls } from 'src/context'; +import { NymCard } from 'src/components'; +import { Network } from 'src/types'; +import { IdentityKey } from 'src/components/IdentityKey'; +import { NodeStatus } from 'src/components/NodeStatus'; +import { Node as NodeIcon } from '../../svg-icons/node'; +import { Cell, Header, NodeTable } from './NodeTable'; +import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions'; + +const headers: Header[] = [ + { + header: 'Stake', + id: 'stake', + sx: { pl: 0 }, + }, + { + header: 'Bond', + id: 'bond', + }, + { + header: 'Stake saturation', + id: 'stake-saturation', + }, + { + header: 'PM', + id: 'profit-margin', + tooltipText: + 'The percentage of the node rewards that you as the node operator will take before the rest of the reward is shared between you and the delegators.', + }, + { + header: 'Operator rewards', + id: 'operator-rewards', + tooltipText: + 'This is your (operator) new rewards including the PM and cost. You can compound your rewards manually every epoch or unbond your node to redeem them.', + }, + { + header: 'No. delegators', + id: 'delegators', + }, + { + id: 'menu-button', + sx: { width: 34, maxWidth: 34 }, + }, +]; + +export const BondedMixnode = ({ + mixnode, + network, + onActionSelect, +}: { + mixnode: TBondedMixnode; + network?: Network; + onActionSelect: (action: TBondedMixnodeActions) => void; +}) => { + const { name, stake, bond, stakeSaturation, profitMargin, operatorRewards, delegators, status, identityKey } = + mixnode; + const cells: Cell[] = [ + { + cell: `${stake.amount} ${stake.denom}`, + id: 'stake-cell', + }, + { + cell: `${bond.amount} ${bond.denom}`, + id: 'bond-cell', + }, + { + cell: `${stakeSaturation}%`, + id: 'stake-saturation-cell', + }, + { + cell: `${profitMargin}%`, + id: 'pm-cell', + }, + { + cell: `${operatorRewards.amount} ${operatorRewards.denom}`, + id: 'operator-rewards-cell', + }, + { + cell: delegators, + id: 'delegators-cell', + }, + { + cell: ( + + ), + id: 'actions-cell', + align: 'right', + }, + ]; + + return ( + + + + Mix node + + + + {name && ( + + {name} + + )} + + + } + Action={ + + } + > + + {network && ( + + Check more stats of your node on the{' '} + + explorer + + + )} + + ); +}; diff --git a/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx b/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx new file mode 100644 index 0000000000..83cc2a0198 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react'; +import { Typography } from '@mui/material'; +import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu'; +import { Unbond as UnbondIcon } from '../../svg-icons'; + +export type TBondedMixnodeActions = 'nodeSettings' | 'bondMore' | 'unbond' | 'redeem' | 'compound'; + +export const BondedMixnodeActions = ({ + onActionSelect, + disabledRedeemAndCompound, +}: { + onActionSelect: (action: TBondedMixnodeActions) => void; + disabledRedeemAndCompound: boolean; +}) => { + const [isOpen, setIsOpen] = useState(false); + + const handleOpen = () => setIsOpen(true); + const handleClose = () => setIsOpen(false); + + const handleActionClick = (action: TBondedMixnodeActions) => { + onActionSelect(action); + handleClose(); + }; + + return ( + + } + onClick={() => handleActionClick('unbond')} + /> + C} + description={disabledRedeemAndCompound ? 'No rewards to compound' : 'Add your rewards to you balance'} + onClick={() => handleActionClick('compound')} + disabled={disabledRedeemAndCompound} + /> + R} + description={disabledRedeemAndCompound ? 'No rewards to redeem' : 'Add your rewards to you balance'} + onClick={() => handleActionClick('redeem')} + disabled={disabledRedeemAndCompound} + /> + + ); +}; diff --git a/nym-wallet/src/components/Bonding/NodeTable.tsx b/nym-wallet/src/components/Bonding/NodeTable.tsx new file mode 100644 index 0000000000..d1e620a1ce --- /dev/null +++ b/nym-wallet/src/components/Bonding/NodeTable.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { + Stack, + SxProps, + Table, + TableBody, + TableCell, + TableCellProps, + TableContainer, + TableHead, + TableRow, + Typography, +} from '@mui/material'; +import { InfoTooltip } from '../InfoToolTip'; + +export type Header = { header?: string; id: string; tooltipText?: string; sx?: SxProps }; +export type Cell = { cell: string | React.ReactNode; id: string; align?: TableCellProps['align']; sx?: SxProps }; + +export interface TableProps { + headers: Header[]; + cells: Cell[]; +} + +export const NodeTable = ({ headers, cells }: TableProps) => ( + +
+ + + {headers.map(({ header, id, tooltipText }) => ( + + + {tooltipText && } + {header} + + + ))} + + + + + {cells.map(({ cell, id, align }) => ( + + {cell} + + ))} + + +
+
+); diff --git a/nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx b/nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx new file mode 100644 index 0000000000..c4f827dcd5 --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx @@ -0,0 +1,214 @@ +import React, { useEffect, useState } from 'react'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { Box, Checkbox, FormControlLabel, Stack, TextField } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { NodeTypeSelector, TokenPoolSelector } from 'src/components'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils'; +import { CurrencyDenom, TNodeType } from '@nymproject/types'; +import { GatewayAmount, GatewayData } from 'src/pages/bonding/types'; +import { gatewayValidationSchema, amountSchema } from './gatewayValidationSchema'; + +const NodeFormData = ({ gatewayData, onNext }: { gatewayData: GatewayData; onNext: (data: GatewayData) => void }) => { + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); + + const { + register, + formState: { errors }, + handleSubmit, + setValue, + } = useForm({ resolver: yupResolver(gatewayValidationSchema), defaultValues: gatewayData }); + + const handleRequestValidation = (event: { detail: { step: number } }) => { + if (event.detail.step === 1) { + handleSubmit(onNext)(); + } + }; + + useEffect(() => { + window.addEventListener('validate_bond_gateway_step' as any, handleRequestValidation); + return () => window.removeEventListener('validate_bond_gateway_step' as any, handleRequestValidation); + }, []); + + return ( + + setValue('identityKey', value)} + /> + + + + + + + + setShowAdvancedOptions((show) => !show)} checked={showAdvancedOptions} />} + label="Show advanced options" + /> + {showAdvancedOptions && ( + + + + + )} + + ); +}; + +const AmountFormData = ({ + denom, + amountData, + hasVestingTokens, + onNext, +}: { + denom: CurrencyDenom; + amountData: GatewayAmount; + hasVestingTokens: boolean; + onNext: (data: any) => void; +}) => { + const { + formState: { errors }, + handleSubmit, + setValue, + getValues, + setError, + } = useForm({ resolver: yupResolver(amountSchema), defaultValues: amountData }); + + const handleRequestValidation = async (event: { detail: { step: number } }) => { + let hasSufficientTokens = true; + const values = getValues(); + + if (values.tokenPool === 'balance') { + hasSufficientTokens = await checkHasEnoughFunds(values.amount.amount); + } + + if (values.tokenPool === 'locked') { + hasSufficientTokens = await checkHasEnoughLockedTokens(values.amount.amount); + } + + if (event.detail.step === 2 && hasSufficientTokens) { + handleSubmit(onNext)(); + } else { + setError('amount.amount', { message: 'Not enough tokens' }); + } + }; + + useEffect(() => { + window.addEventListener('validate_bond_gateway_step' as any, handleRequestValidation); + return () => window.removeEventListener('validate_bond_gateway_step' as any, handleRequestValidation); + }, []); + + return ( + + + {hasVestingTokens && setValue('tokenPool', pool)} />} + setValue('amount', newValue, { shouldValidate: true })} + validationError={errors.amount?.amount?.message} + denom={denom} + initialValue={amountData.amount.amount} + /> + + + ); +}; + +export const BondGatewayForm = ({ + step, + denom, + gatewayData, + amountData, + hasVestingTokens, + onValidateGatewayData, + onValidateAmountData, + onSelectNodeType, +}: { + step: 1 | 2 | 3; + gatewayData: GatewayData; + amountData: GatewayAmount; + denom: CurrencyDenom; + hasVestingTokens: boolean; + onValidateGatewayData: (data: GatewayData) => void; + onValidateAmountData: (data: GatewayAmount) => Promise; + onSelectNodeType: (nodeType: TNodeType) => void; +}) => ( + <> + {step === 1 && ( + <> + + + + + + )} + {step === 2 && ( + + )} + +); diff --git a/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx b/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx new file mode 100644 index 0000000000..99be47d96b --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx @@ -0,0 +1,223 @@ +import React, { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Box, Checkbox, FormControlLabel, Stack, TextField } from '@mui/material'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { CurrencyDenom, TNodeType } from '@nymproject/types'; +import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils'; +import { NodeTypeSelector, TokenPoolSelector } from 'src/components'; +import { MixnodeAmount, MixnodeData } from 'src/pages/bonding/types'; +import { amountSchema, mixnodeValidationSchema } from './mixnodeValidationSchema'; + +const NodeFormData = ({ mixnodeData, onNext }: { mixnodeData: MixnodeData; onNext: (data: any) => void }) => { + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); + + const { + register, + formState: { errors }, + handleSubmit, + setValue, + } = useForm({ resolver: yupResolver(mixnodeValidationSchema), defaultValues: mixnodeData }); + + const handleRequestValidation = (event: { detail: { step: number } }) => { + if (event.detail.step === 1) { + handleSubmit(onNext)(); + } + }; + + useEffect(() => { + window.addEventListener('validate_bond_mixnode_step' as any, handleRequestValidation); + return () => window.removeEventListener('validate_bond_mixnode_step' as any, handleRequestValidation); + }, []); + + return ( + + setValue('identityKey', value)} + /> + + + + + + + setShowAdvancedOptions((show) => !show)} checked={showAdvancedOptions} />} + label="Show advanced options" + /> + {showAdvancedOptions && ( + + + + + + )} + + ); +}; + +const AmountFormData = ({ + amountData, + hasVestingTokens, + denom, + onNext, +}: { + amountData: MixnodeAmount; + hasVestingTokens: boolean; + denom: CurrencyDenom; + onNext: (data: MixnodeAmount) => void; +}) => { + const { + register, + formState: { errors }, + handleSubmit, + setValue, + getValues, + setError, + } = useForm({ resolver: yupResolver(amountSchema), defaultValues: amountData }); + + const handleRequestValidation = async (event: { detail: { step: number } }) => { + let hasSufficientTokens = true; + const values = getValues(); + + if (values.tokenPool === 'balance') { + hasSufficientTokens = await checkHasEnoughFunds(values.amount.amount); + } + + if (values.tokenPool === 'locked') { + hasSufficientTokens = await checkHasEnoughLockedTokens(values.amount.amount); + } + + if (event.detail.step === 2 && hasSufficientTokens) { + handleSubmit(onNext)(); + } else { + setError('amount.amount', { message: 'Not enough tokens' }); + } + }; + + useEffect(() => { + window.addEventListener('validate_bond_mixnode_step' as any, handleRequestValidation); + return () => window.removeEventListener('validate_bond_mixnode_step' as any, handleRequestValidation); + }, []); + + return ( + + + {hasVestingTokens && setValue('tokenPool', pool)} />} + { + setValue('amount', newValue, { shouldValidate: true }); + }} + validationError={errors.amount?.amount?.message} + denom={denom} + initialValue={amountData.amount.amount} + /> + + + + ); +}; + +export const BondMixnodeForm = ({ + step, + denom, + mixnodeData, + amountData, + hasVestingTokens, + onValidateMixnodeData, + onValidateAmountData, + onSelectNodeType, +}: { + step: 1 | 2 | 3; + mixnodeData: MixnodeData; + amountData: MixnodeAmount; + denom: CurrencyDenom; + hasVestingTokens: boolean; + onValidateMixnodeData: (data: MixnodeData) => void; + onValidateAmountData: (data: MixnodeAmount) => Promise; + onSelectNodeType: (nodeType: TNodeType) => void; +}) => ( + <> + {step === 1 && ( + <> + + + + + + )} + {step === 2 && ( + + )} + +); diff --git a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts new file mode 100644 index 0000000000..fb1625adc1 --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts @@ -0,0 +1,59 @@ +import * as Yup from 'yup'; +import { + isValidHostname, + validateAmount, + validateKey, + validateLocation, + validateRawPort, + validateVersion, +} from 'src/utils'; + +export const gatewayValidationSchema = Yup.object().shape({ + identityKey: Yup.string() + .required('An indentity key is required') + .test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)), + + sphinxKey: Yup.string() + .required('A sphinx key is required') + .test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)), + + ownerSignature: Yup.string() + .required('Signature is required') + .test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)), + + host: Yup.string() + .required('A host is required') + .test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)), + + version: Yup.string() + .required('A version is required') + .test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)), + + location: Yup.string() + .required('A location is required') + .test('valid-location', 'A valid version is required', (locationValueTest) => + locationValueTest ? validateLocation(locationValueTest) : false, + ), + + mixPort: Yup.number() + .required('A mixport is required') + .test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)), + + clientsPort: Yup.number() + .required('A clients port is required') + .test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)), +}); + +export const amountSchema = Yup.object().shape({ + amount: Yup.object().shape({ + amount: Yup.string() + .required('An amount is required') + .test('valid-amount', 'Pledge error', async function isValidAmount(this, value) { + const isValid = await validateAmount(value || '', '100'); + if (!isValid) { + return this.createError({ message: 'A valid amount is required (min 100)' }); + } + return true; + }), + }), +}); diff --git a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts new file mode 100644 index 0000000000..aa9e66db1c --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts @@ -0,0 +1,51 @@ +import * as Yup from 'yup'; +import { isValidHostname, validateAmount, validateKey, validateRawPort, validateVersion } from 'src/utils'; + +export const mixnodeValidationSchema = Yup.object().shape({ + identityKey: Yup.string() + .required('An identity key is required') + .test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)), + + sphinxKey: Yup.string() + .required('A sphinx key is required') + .test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)), + + ownerSignature: Yup.string() + .required('Signature is required') + .test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)), + + host: Yup.string() + .required('A host is required') + .test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)), + + version: Yup.string() + .required('A version is required') + .test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)), + + mixPort: Yup.number() + .required('A mixport is required') + .test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)), + + verlocPort: Yup.number() + .required('A verloc port is required') + .test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)), + + httpApiPort: Yup.number() + .required('A http-api port is required') + .test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)), +}); + +export const amountSchema = Yup.object().shape({ + amount: Yup.object().shape({ + amount: Yup.string() + .required('An amount is required') + .test('valid-amount', 'Pledge error', async function isValidAmount(this, value) { + const isValid = await validateAmount(value || '', '100'); + if (!isValid) { + return this.createError({ message: 'A valid amount is required (min 100)' }); + } + return true; + }), + }), + profitMargin: Yup.number().required('Profit Percentage is required').min(0).max(100), +}); diff --git a/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx new file mode 100644 index 0000000000..d54f75ce24 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from 'react'; +import { Box } from '@mui/material'; +import { CurrencyDenom, TNodeType } from '@nymproject/types'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { TPoolOption } from 'src/components/TokenPoolSelector'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { GatewayAmount, GatewayData } from 'src/pages/bonding/types'; +import { simulateBondGateway, simulateVestingBondGateway } from 'src/requests'; +import { TBondGatewayArgs } from 'src/types'; +import { BondGatewayForm } from '../forms/BondGatewayForm'; + +const defaultMixnodeValues: GatewayData = { + identityKey: '', + sphinxKey: '', + ownerSignature: '', + location: '', + host: '', + version: '', + mixPort: 1789, + clientsPort: 1790, +}; + +const defaultAmountValues = (denom: CurrencyDenom) => ({ + amount: { amount: '100', denom }, + tokenPool: 'balance', +}); + +export const BondGatewayModal = ({ + denom, + hasVestingTokens, + onBondGateway, + onSelectNodeType, + onClose, + onError, +}: { + denom: CurrencyDenom; + hasVestingTokens: boolean; + onBondGateway: (data: TBondGatewayArgs, tokenPool: TPoolOption) => void; + onSelectNodeType: (type: TNodeType) => void; + onClose: () => void; + onError: (e: string) => void; +}) => { + const [step, setStep] = useState<1 | 2 | 3>(1); + const [gatewayData, setGatewayData] = useState(defaultMixnodeValues); + const [amountData, setAmountData] = useState(defaultAmountValues(denom)); + + const { fee, getFee, resetFeeState, feeError } = useGetFee(); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + const validateStep = async (s: number) => { + const event = new CustomEvent('validate_bond_gateway_step', { detail: { step: s } }); + window.dispatchEvent(event); + }; + + const handleBack = () => { + setStep(1); + }; + + const handleUpdateGatwayData = (data: GatewayData) => { + setGatewayData(data); + setStep(2); + }; + + const handleUpdateAmountData = async (data: GatewayAmount) => { + setAmountData(data); + const payload = { + pledge: data.amount, + ownerSignature: gatewayData.ownerSignature, + gateway: { + ...gatewayData, + host: gatewayData.host, + version: gatewayData.version, + mix_port: gatewayData.mixPort, + clients_port: gatewayData.clientsPort, + sphinx_key: gatewayData.sphinxKey, + identity_key: gatewayData.identityKey, + location: gatewayData.location, + }, + }; + + if (data.tokenPool === 'balance') { + await getFee(simulateBondGateway, payload); + } else { + await getFee(simulateVestingBondGateway, payload); + } + }; + + const handleConfirm = async () => { + await onBondGateway( + { + pledge: amountData.amount, + ownerSignature: gatewayData.ownerSignature, + gateway: { + ...gatewayData, + host: gatewayData.host, + version: gatewayData.version, + mix_port: gatewayData.mixPort, + clients_port: gatewayData.clientsPort, + sphinx_key: gatewayData.sphinxKey, + identity_key: gatewayData.identityKey, + location: gatewayData.location, + }, + }, + amountData.tokenPool as TPoolOption, + ); + }; + + if (fee) { + return ( + + + + + ); + } + + return ( + { + await validateStep(step); + }} + onBack={step === 2 ? handleBack : undefined} + onClose={onClose} + header="Bond gateway" + subHeader={`Step ${step}/2`} + okLabel="Next" + > + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx new file mode 100644 index 0000000000..fd0de36051 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx @@ -0,0 +1,160 @@ +import React, { useEffect, useState } from 'react'; +import { Box } from '@mui/material'; +import { CurrencyDenom, TNodeType } from '@nymproject/types'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { TPoolOption } from 'src/components/TokenPoolSelector'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { MixnodeAmount, MixnodeData } from 'src/pages/bonding/types'; +import { simulateBondMixnode, simulateVestingBondMixnode } from 'src/requests'; +import { TBondMixNodeArgs } from 'src/types'; +import { BondMixnodeForm } from '../forms/BondMixnodeForm'; + +const defaultMixnodeValues: MixnodeData = { + identityKey: '', + sphinxKey: '', + ownerSignature: '', + host: '', + version: '', + mixPort: 1789, + verlocPort: 1790, + httpApiPort: 8000, +}; + +const defaultAmountValues = (denom: CurrencyDenom) => ({ + amount: { amount: '100', denom }, + profitMargin: 10, + tokenPool: 'balance', +}); + +export const BondMixnodeModal = ({ + denom, + hasVestingTokens, + onBondMixnode, + onSelectNodeType, + onClose, + onError, +}: { + denom: CurrencyDenom; + hasVestingTokens: boolean; + onBondMixnode: (data: TBondMixNodeArgs, tokenPool: TPoolOption) => void; + onSelectNodeType: (type: TNodeType) => void; + onClose: () => void; + onError: (e: string) => void; +}) => { + const [step, setStep] = useState<1 | 2 | 3>(1); + const [mixnodeData, setMixnodeData] = useState(defaultMixnodeValues); + const [amountData, setAmountData] = useState(defaultAmountValues(denom)); + + const { fee, getFee, resetFeeState, feeError } = useGetFee(); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + const validateStep = async (s: number) => { + const event = new CustomEvent('validate_bond_mixnode_step', { detail: { step: s } }); + window.dispatchEvent(event); + }; + + const handleBack = () => { + setStep(1); + }; + + const handleUpdateMixnodeData = (data: MixnodeData) => { + setMixnodeData(data); + setStep(2); + }; + + const handleUpdateAmountData = async (data: MixnodeAmount) => { + setAmountData(data); + const payload = { + pledge: data.amount, + ownerSignature: mixnodeData.ownerSignature, + mixnode: { + ...mixnodeData, + mix_port: mixnodeData.mixPort, + http_api_port: mixnodeData.httpApiPort, + verloc_port: mixnodeData.verlocPort, + sphinx_key: mixnodeData.sphinxKey, + identity_key: mixnodeData.identityKey, + profit_margin_percent: data.profitMargin, + }, + }; + + if (data.tokenPool === 'balance') { + await getFee(simulateBondMixnode, payload); + } else { + await getFee(simulateVestingBondMixnode, payload); + } + }; + + const handleConfirm = async () => { + await onBondMixnode( + { + pledge: amountData.amount, + ownerSignature: mixnodeData.ownerSignature, + mixnode: { + ...mixnodeData, + mix_port: mixnodeData.mixPort, + http_api_port: mixnodeData.httpApiPort, + verloc_port: mixnodeData.verlocPort, + sphinx_key: mixnodeData.sphinxKey, + identity_key: mixnodeData.identityKey, + profit_margin_percent: amountData.profitMargin, + }, + }, + amountData.tokenPool as TPoolOption, + ); + }; + + if (fee) { + return ( + + + + + ); + } + + return ( + { + await validateStep(step); + }} + onBack={step === 2 ? handleBack : undefined} + onClose={onClose} + header="Bond mixnode" + subHeader={`Step ${step}/2`} + okLabel="Next" + > + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx new file mode 100644 index 0000000000..235795e77a --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/BondMoreModal.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react'; +import { Box, FormHelperText, Stack, TextField } from '@mui/material'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { DecCoin } from '@nymproject/types'; +import { TokenPoolSelector, TPoolOption } from 'src/components/TokenPoolSelector'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { validateAmount, validateKey } from 'src/utils'; + +export const BondMoreModal = ({ + currentBond, + userBalance, + hasVestingTokens, + onConfirm, + onClose, +}: { + currentBond: DecCoin; + userBalance?: string; + hasVestingTokens: boolean; + onConfirm: (args: { additionalBond: DecCoin; signature: string; tokenPool: TPoolOption }) => Promise; + onClose: () => void; +}) => { + const { fee, resetFeeState } = useGetFee(); + const [additionalBond, setAdditionalBond] = useState({ amount: '0', denom: currentBond.denom }); + const [signature, setSignature] = useState(''); + const [tokenPool, setTokenPool] = useState('balance'); + const [errorAmount, setErrorAmount] = useState(false); + const [errorSignature, setErrorSignature] = useState(false); + + const handleOnOk = async () => { + const errors = { + amount: false, + signature: false, + }; + + if (!validateKey(signature || '', 64)) { + errors.signature = true; + } + + if (!additionalBond?.amount) { + errors.amount = true; + } + + if (additionalBond && !(await validateAmount(additionalBond.amount, '1'))) { + errors.amount = true; + } + + if (!errors.amount && !errors.signature) { + onConfirm({ additionalBond, signature, tokenPool }); + } else { + setErrorAmount(errors.amount); + setErrorSignature(errors.signature); + } + }; + + useEffect(() => { + setErrorAmount(false); + }, [additionalBond]); + + if (fee) + return ( + onConfirm({ additionalBond, signature, tokenPool })} + onPrev={resetFeeState} + > + + + + ); + + return ( + + + + {hasVestingTokens && setTokenPool(pool)} />} + { + setAdditionalBond(value); + setErrorSignature(false); + }} + fullWidth + validationError={errorAmount ? 'Please enter a valid amount' : undefined} + /> + + + + setSignature(e.target.value)} /> + {errorSignature && Invalid signature} + + + + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/CompoundRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/CompoundRewardsModal.tsx new file mode 100644 index 0000000000..999f1c3302 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/CompoundRewardsModal.tsx @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import { FeeDetails } from '@nymproject/types'; +import { ModalFee } from 'src/components/Modals/ModalFee'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { TBondedMixnode } from 'src/context'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { simulateCompoundOperatorReward, simulateVestingCompoundOperatorReward } from 'src/requests'; + +export const CompoundRewardsModal = ({ + node, + onConfirm, + onClose, + onError, +}: { + node: TBondedMixnode; + onClose: () => void; + onConfirm: (fee?: FeeDetails) => void; + onError: (err: string) => void; +}) => { + const { fee, getFee, feeError, isFeeLoading } = useGetFee(); + + useEffect(() => { + if (feeError) onError(feeError); + }, [feeError]); + + useEffect(() => { + if (node.proxy) getFee(simulateVestingCompoundOperatorReward, {}); + else getFee(simulateCompoundOperatorReward, {}); + }, []); + + const handleOnOK = async () => onConfirm(fee); + + return ( + + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx new file mode 100644 index 0000000000..9a3c683b55 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Stack, Typography, SxProps } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { ConfirmationModal } from 'src/components/Modals/ConfirmationModal'; +import { ErrorModal } from 'src/components/Modals/ErrorModal'; + +export type ConfirmationDetailProps = { + status: 'success' | 'error'; + title: string; + subtitle?: string; + txUrl?: string; +}; + +export const ConfirmationDetailsModal = ({ + title, + subtitle, + txUrl, + status, + onClose, + sx, + backdropProps, +}: ConfirmationDetailProps & { + onClose: () => void; + sx?: SxProps; + backdropProps?: object; +}) => { + if (status === 'error') { + ; + } + + return ( + + + + {title} + + {subtitle} + {txUrl && } + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx new file mode 100644 index 0000000000..1a1903d205 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx @@ -0,0 +1,128 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Button, FormHelperText, TextField, Typography } from '@mui/material'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { Node as NodeIcon } from 'src/svg-icons/node'; +import { TBondedMixnode } from 'src/context'; +import { Tabs } from 'src/components/Tabs'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { isDecimal } from 'src/utils'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { simulateUpdateMixnode, simulateVestingUpdateMixnode } from 'src/requests'; +import { LoadingModal } from 'src/components/Modals/LoadingModal'; +import { FeeDetails } from '@nymproject/types'; + +export const NodeSettings = ({ + currentPm, + isVesting, + onConfirm, + onClose, + onError, +}: { + currentPm: TBondedMixnode['profitMargin']; + isVesting: boolean; + onConfirm: (profitMargin: number, fee?: FeeDetails) => Promise; + onClose: () => void; + onError: (err: string) => void; +}) => { + const [pm, setPm] = useState(currentPm.toString()); + const [error, setError] = useState(false); + + const { fee, getFee, resetFeeState, isFeeLoading, feeError } = useGetFee(); + + const handleValidate = async () => { + let isValid = true; + const pmAsNumber = Number(pm); + + if (!pmAsNumber) { + isValid = false; + } + if (isDecimal(pmAsNumber)) { + isValid = false; + } + if (pmAsNumber > 100) { + isValid = false; + } + if (pmAsNumber < 0) { + isValid = false; + } + + if (!isValid) { + setError(true); + return; + } + + if (isVesting) { + await getFee(simulateVestingUpdateMixnode, { profitMarginPercent: pmAsNumber }); + } else { + await getFee(simulateUpdateMixnode, { profitMarginPercent: pmAsNumber }); + } + }; + + useEffect(() => { + setError(false); + }, [pm]); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + if (isFeeLoading) return ; + + if (fee) + return ( + onConfirm(Number(pm), fee)} + > + + + + ); + + return ( + + + + Node Settings + +
+ } + okLabel="Next" + onClose={onClose} + > + + + + Set profit margin + + + setPm(e.target.value)} fullWidth /> + {error && ( + + Profit margin should be a whole number between 0 and 100 + + )} + Your new profit margin will be applied in the next epoch + + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx new file mode 100644 index 0000000000..1a312cdc07 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import { FeeDetails } from '@nymproject/types'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { ModalFee } from 'src/components/Modals/ModalFee'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { simulateClaimOperatorReward, simulateVestingClaimOperatorReward } from 'src/requests'; +import { TBondedMixnode } from 'src/context'; + +export const RedeemRewardsModal = ({ + node, + onConfirm, + onError, + onClose, +}: { + node: TBondedMixnode; + onConfirm: (fee?: FeeDetails) => Promise; + onError: (err: string) => void; + onClose: () => void; +}) => { + const { fee, getFee, isFeeLoading, feeError } = useGetFee(); + + useEffect(() => { + if (feeError) onError(feeError); + }, [feeError]); + + useEffect(() => { + if (node.proxy) getFee(simulateVestingClaimOperatorReward, {}); + else getFee(simulateClaimOperatorReward, {}); + }, []); + + const handleOnOK = async () => onConfirm(fee); + + return ( + + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx new file mode 100644 index 0000000000..1c105139bc --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { Typography } from '@mui/material'; +import { useEffect } from 'react'; +import { TBondedGateway, TBondedMixnode } from 'src/context'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { isGateway, isMixnode } from 'src/types'; +import { ModalFee } from '../../Modals/ModalFee'; +import { ModalListItem } from '../../Modals/ModalListItem'; +import { SimpleModal } from '../../Modals/SimpleModal'; +import { + simulateUnbondGateway, + simulateUnbondMixnode, + simulateVestingUnbondGateway, + simulateVestingUnbondMixnode, +} from '../../../requests'; + +interface Props { + node: TBondedMixnode | TBondedGateway; + onConfirm: () => Promise; + onClose: () => void; + onError: (e: string) => void; +} + +export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => { + const { fee, isFeeLoading, getFee, feeError } = useGetFee(); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + useEffect(() => { + if (isMixnode(node) && !node.proxy) { + getFee(simulateUnbondMixnode, {}); + } + + if (isMixnode(node) && node.proxy) { + getFee(simulateVestingUnbondMixnode, {}); + } + + if (isGateway(node) && !node.proxy) { + getFee(simulateUnbondGateway, {}); + } + + if (isGateway(node) && node.proxy) { + getFee(simulateVestingUnbondGateway, {}); + } + }, [node]); + + return ( + + + {isMixnode(node) && ( + + )} + + Tokens will be transferred to the account you are logged in with now + + ); +}; diff --git a/nym-wallet/src/components/CopyToClipboard.tsx b/nym-wallet/src/components/CopyToClipboard.tsx index 453a2ce382..8df15668a8 100644 --- a/nym-wallet/src/components/CopyToClipboard.tsx +++ b/nym-wallet/src/components/CopyToClipboard.tsx @@ -36,7 +36,7 @@ export const CopyToClipboard = ({ text = '', iconButton }: { text?: string; icon color: 'text.primary', }} > - {!copied ? : } + {!copied ? : } ); diff --git a/nym-wallet/src/components/Delegation/DelegateModal.tsx b/nym-wallet/src/components/Delegation/DelegateModal.tsx index be32256409..fb1e69f1ff 100644 --- a/nym-wallet/src/components/Delegation/DelegateModal.tsx +++ b/nym-wallet/src/components/Delegation/DelegateModal.tsx @@ -231,7 +231,7 @@ export const DelegateModal: React.FC<{ {errorAmount}
- +
- - {!item.accumulated_rewards - ? '-' - : `${item.accumulated_rewards.amount} ${item.accumulated_rewards.denom}`} - + {getRewardValue(item)} - {!item.pending_events.length ? ( + {!isPendingDelegation(item) && !item.pending_events.length && ( (onItemActionClick ? onItemActionClick(item, action) : undefined)} disableRedeemingRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'} disableCompoundRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'} /> - ) : ( + )} + {!isPendingDelegation(item) && item.pending_events.length > 0 && ( - + + + )} + {isPendingDelegation(item) && ( + + )} diff --git a/nym-wallet/src/components/Delegation/PendingEvents.tsx b/nym-wallet/src/components/Delegation/PendingEvents.tsx deleted file mode 100644 index ac3afa1be5..0000000000 --- a/nym-wallet/src/components/Delegation/PendingEvents.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React, { FC } from 'react'; -import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import { - Box, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - TableSortLabel, - Tooltip, - Typography, -} from '@mui/material'; -import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'; -import { DelegationEvent } from '@nymproject/types'; -import { ArrowDropDown } from '@mui/icons-material'; -import { visuallyHidden } from '@mui/utils'; -import { Link } from '@nymproject/react/link/Link'; - -type Order = 'asc' | 'desc'; - -interface HeadCell { - id: keyof DelegationEvent; - label: string; - sortable: boolean; - disablePadding?: boolean; -} - -interface EnhancedTableProps { - onRequestSort: (event: React.MouseEvent, property: keyof DelegationEvent) => void; - order: Order; - orderBy: string; -} - -const headCells: HeadCell[] = [ - { id: 'node_identity', label: 'Node ID', sortable: true }, - { id: 'amount', label: 'Delegation', sortable: true }, - { id: 'kind', label: 'Type', sortable: true }, -]; - -function descendingComparator(a: T, b: T, orderBy: keyof T) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -} - -function getComparator( - order: Order, - orderBy: Key, -): (a: DelegationEvent, b: DelegationEvent) => number { - return order === 'desc' - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); -} - -const EnhancedTableHead: React.FC = ({ order, orderBy, onRequestSort }) => { - const createSortHandler = (property: keyof DelegationEvent) => (event: React.MouseEvent) => { - onRequestSort(event, property); - }; - - return ( - - - {headCells.map((headCell) => ( - - - {headCell.label} - {orderBy === headCell.id ? ( - - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} - - ) : null} - - - ))} - - - ); -}; - -export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl: string }> = ({ - pendingEvents, - explorerUrl, -}) => { - const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('node_identity'); - - const handleRequestSort = (event: React.MouseEvent, property: keyof DelegationEvent) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; - - if (pendingEvents.length === 0) return No pending events; - - return ( - - - - - {pendingEvents.sort(getComparator(order, orderBy)).map((item) => ( - - - - Copy identity key {item.node_identity} to clipboard - - } - /> - - Click to view {item.node_identity} in the Network Explorer - - } - placement="right" - arrow - > - - - - {!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom.toUpperCase()}`} - - {item.kind === 'Delegate' ? 'Delegation' : 'Undelegation'} - {item.proxy && ( - - - - )} - - - ))} - -
-
- ); -}; diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 476624beba..7f54dba56b 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -14,7 +14,7 @@ import { TPoolOption } from 'src/components'; export type TDelegationContext = { isLoading: boolean; error?: string; - delegations?: DelegationWithEverything[]; + delegations?: TDelegations; pendingDelegations?: DelegationEvent[]; totalDelegations?: string; totalRewards?: string; @@ -35,6 +35,12 @@ export type TDelegationTransaction = { transactionUrl: string; }; +export type DelegationWithEvent = DelegationWithEverything | DelegationEvent; +export type TDelegations = DelegationWithEvent[]; + +export const isPendingDelegation = (delegation: DelegationWithEvent): delegation is DelegationEvent => + 'kind' in delegation; + export const DelegationContext = createContext({ isLoading: true, refresh: async () => undefined, @@ -51,7 +57,7 @@ export const DelegationContextProvider: FC<{ }> = ({ network, children }) => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); - const [delegations, setDelegations] = useState(); + const [delegations, setDelegations] = useState(); const [totalDelegations, setTotalDelegations] = useState(); const [totalRewards, setTotalRewards] = useState(); const [pendingDelegations, setPendingDelegations] = useState(); @@ -87,8 +93,13 @@ export const DelegationContextProvider: FC<{ const data = await getDelegationSummary(); const pending = await getAllPendingDelegations(); + const pendingOnNewNodes = pending.filter((event) => { + const some = data.delegations.some(({ node_identity }) => node_identity === event.node_identity); + return !some; + }); + setPendingDelegations(pending); - setDelegations(data.delegations); + setDelegations([...data.delegations, ...pendingOnNewNodes]); setTotalDelegations(`${data.total_delegations.amount} ${data.total_delegations.denom}`); setTotalRewards(`${data.total_rewards.amount} ${data.total_rewards.denom}`); } catch (e) { diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index a71ee958ea..d8a7d9f531 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -1,5 +1,5 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { TransactionExecuteResult } from '@nymproject/types'; +import { DelegationWithEverything, TransactionExecuteResult } from '@nymproject/types'; import { RewardsContext, TRewardsTransaction } from '../rewards'; import { useDelegationContext } from '../delegations'; import { mockSleep } from './utils'; @@ -9,7 +9,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => { const [error, setError] = useState(); const [totalRewards, setTotalRewards] = useState(); const { delegations } = useDelegationContext(); - const delegationsHash = delegations?.map((d) => d.accumulated_rewards).join(','); + const delegationsHash = delegations?.map((d) => (d as DelegationWithEverything).accumulated_rewards).join(','); const resetState = () => { setIsLoading(true); @@ -19,7 +19,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => { const recalculate = () => { const sum: number | undefined = delegations - ?.map((d) => (d.accumulated_rewards ? Number(10) : Number(0))) + ?.map((d) => ((d as DelegationWithEverything).accumulated_rewards ? Number(10) : Number(0))) .reduce((acc, cur) => acc + cur, Number(0)); setTotalRewards(sum ? `${sum} NYM` : undefined); diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index 6c78b9d773..15cbdbe75f 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -5,7 +5,6 @@ import { DelegationWithEverything, FeeDetails, DecCoin } from '@nymproject/types import { Link } from '@nymproject/react/link/Link'; import { AppContext, urls } from 'src/context/main'; import { DelegationList } from 'src/components/Delegation/DelegationList'; -import { PendingEvents } from 'src/components/Delegation/PendingEvents'; import { TPoolOption } from 'src/components'; import { Console } from 'src/utils/console'; import { CompoundModal } from 'src/components/Rewards/CompoundModal'; @@ -49,7 +48,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { const { delegations, - pendingDelegations, totalDelegations, totalRewards, isLoading, @@ -322,15 +320,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { - {pendingDelegations && ( - - - Pending Delegation Events - - - - )} - {showNewDelegationModal && ( Date: Thu, 18 Aug 2022 10:45:42 +0100 Subject: [PATCH 91/91] Moving helpful comment into docs comment --- contracts/vesting/src/vesting/account/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 5418619cfe..b5369aa410 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -101,10 +101,9 @@ impl Account { } } + /// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. + /// In case vesting is over it will always return NUM_VESTING_PERIODS. pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period { - // Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet. - // In case vesting is over it will always return NUM_VESTING_PERIODS. - if block_time.seconds() < self.periods.first().unwrap().start_time { Period::Before } else if self.periods.last().unwrap().end_time() < block_time {