Compare commits

..

3 Commits

Author SHA1 Message Date
Jędrzej Stuczyński e246fc6ccf Attempting to de-rot the branch 2022-07-12 16:19:47 +01:00
Jędrzej Stuczyński 213f454446 post-rebase fixes 2022-06-10 11:03:10 +01:00
Jędrzej Stuczyński 895edea531 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
2022-06-10 10:52:49 +01:00
360 changed files with 5366 additions and 9459 deletions
-12
View File
@@ -1,7 +1,6 @@
name: CI for Network Explorer
on:
workflow_dispatch:
push:
paths:
- 'explorer/**'
@@ -76,14 +75,3 @@ jobs:
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
- name: Deploy
if: github.event_name == 'workflow_dispatch'
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
EXCLUDE: "/dist/, /node_modules/"
@@ -1,50 +0,0 @@
[
{
"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"
}
]
@@ -1,174 +0,0 @@
name: Nightly builds on dispatch
on: workflow_dispatch
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v2
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+5 -30
View File
@@ -8,6 +8,10 @@ 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
@@ -22,9 +26,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
- 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
@@ -35,11 +36,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- native & socks5 clients: rerun init will now reuse previous gateway configuration instead of failing ([#1353])
- native & socks5 clients: deduplicate big chunks of init logic
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
- network-requester: fix filter for suffix-only domains ([#1487])
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
- all: updated `rocket` to `0.5.0-rc.2`.
@@ -47,11 +47,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- gateway: allow to voluntarily send statistical data about the number of active inboxes served by a gateway ([#1376])
- gateway & mixnode: move detailed build info back to `--version` from `--help`.
- socks5 client/websocket client: upgrade to latest clap and switched to declarative commandline parsing.
- validator-api: fee payment for multisig operations comes from the gateway account instead of the validator APIs' accounts ([#1419])
- multisig-contract: Limit the proposal creating functionality to one address (coconut-bandwidth-contract address) ([#1457])
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
@@ -71,26 +66,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1376]: https://github.com/nymtech/nym/pull/1376
[#1393]: https://github.com/nymtech/nym/pull/1393
[#1404]: https://github.com/nymtech/nym/pull/1404
[#1419]: https://github.com/nymtech/nym/pull/1419
[#1457]: https://github.com/nymtech/nym/pull/1457
[#1463]: https://github.com/nymtech/nym/pull/1463
[#1478]: https://github.com/nymtech/nym/pull/1478
[#1482]: https://github.com/nymtech/nym/pull/1482
[#1487]: https://github.com/nymtech/nym/pull/1487
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
### Added
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
[#1427]: https://github.com/nymtech/nym/pull/1427
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
Generated
+12 -42
View File
@@ -644,7 +644,6 @@ name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"multisig-contract-common",
"schemars",
"serde",
]
@@ -783,8 +782,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cosmos-sdk-proto"
version = "0.12.3"
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f109fe191e73898d74b8020c50f86018364ad19bc30318aa074616c382b52856"
dependencies = [
"prost 0.10.3",
"prost-types 0.10.1",
@@ -793,8 +793,9 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.7.1"
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8413275b23cb5a0734d9d1e3e33f0b5b94547c1e94776dbc3149dbf46588a533"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -928,6 +929,7 @@ dependencies = [
"coconut-interface",
"cosmrs",
"crypto",
"network-defaults",
"rand 0.7.3",
"thiserror",
"url",
@@ -1590,7 +1592,6 @@ name = "explorer-api"
version = "1.0.1"
dependencies = [
"chrono",
"clap 3.2.8",
"humantime-serde",
"isocountry",
"itertools",
@@ -1606,7 +1607,6 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"task",
"thiserror",
"tokio",
"validator-client",
@@ -2872,6 +2872,7 @@ dependencies = [
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
@@ -2968,7 +2969,6 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if 1.0.0",
"dotenv",
"hex-literal",
"once_cell",
"serde",
@@ -3066,6 +3066,7 @@ dependencies = [
"credentials",
"crypto",
"dirs",
"dotenv",
"futures",
"gateway-client",
"gateway-requests",
@@ -3232,6 +3233,7 @@ dependencies = [
"credentials",
"crypto",
"dirs",
"dotenv",
"futures",
"gateway-client",
"gateway-requests",
@@ -3298,8 +3300,6 @@ dependencies = [
"credential-storage",
"credentials",
"crypto",
"cw-utils",
"cw3",
"dirs",
"dotenv",
"futures",
@@ -5389,6 +5389,7 @@ version = "1.0.1"
dependencies = [
"async-trait",
"log",
"network-defaults",
"reqwest",
"serde",
"serde_json",
@@ -5397,15 +5398,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "streaming-stats"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d670ce4e348a2081843569e0f79b21c99c91bb9028b3b3ecb0f050306de547"
dependencies = [
"num-traits",
]
[[package]]
name = "stringprep"
version = "0.1.2"
@@ -6293,29 +6285,6 @@ dependencies = [
"vesting-contract-common",
]
[[package]]
name = "validator-client-scripts"
version = "0.1.0"
dependencies = [
"base64",
"bip39",
"bs58",
"clap 3.2.8",
"csv",
"dotenv",
"log",
"mixnet-contract-common",
"network-defaults",
"pretty_env_logger",
"serde",
"serde_json",
"streaming-stats",
"tokio",
"url",
"validator-client",
"vesting-contract-common",
]
[[package]]
name = "valuable"
version = "0.1.0"
@@ -6368,6 +6337,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+1 -2
View File
@@ -68,8 +68,7 @@ members = [
"service-providers/network-statistics",
"validator-api",
"validator-api/validator-api-requests",
"tools/ts-rs-cli",
"tools/validator-client-scripts"
"tools/ts-rs-cli"
]
default-members = [
+1 -1
View File
@@ -1,5 +1,5 @@
test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
test: build clippy-all cargo-test wasm fmt
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
+2 -1
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::*;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -365,7 +366,7 @@ impl<T: NymConfig> Default for Client<T> {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
disabled_credentials_mode: true,
validator_api_urls: vec![],
validator_api_urls: default_api_endpoints(),
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_encryption_key_file: Default::default(),
+4 -9
View File
@@ -4,7 +4,7 @@
use crate::error::Result;
use crate::{MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use network_defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO};
use std::str::FromStr;
use url::Url;
use validator_client::nymd;
@@ -13,23 +13,18 @@ use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
pub(crate) struct Client {
nymd_client: NymdClient<SigningNymdClient>,
mix_denom_base: String,
}
impl Client {
pub fn new() -> Self {
let nymd_url = Url::from_str(NYMD_URL).unwrap();
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
let network_details = NymNetworkDetails::new_from_env();
let config = nymd::Config::try_from_nym_network_details(&network_details)
let config = nymd::Config::try_from_nym_network_details(&DEFAULT_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,
mix_denom_base: network_details.chain_details.mix_denom.base,
}
Client { nymd_client }
}
pub async fn deposit(
@@ -39,7 +34,7 @@ impl Client {
encryption_key: String,
fee: Option<Fee>,
) -> Result<String> {
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
let amount = Coin::new(amount as u128, MIX_DENOM.base.to_string());
Ok(self
.nymd_client
.deposit(
+1
View File
@@ -22,6 +22,7 @@ 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
+4 -10
View File
@@ -3,6 +3,7 @@
use crate::client::config::{Config, SocketType};
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
#[cfg(not(feature = "coconut"))]
@@ -27,6 +28,7 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -44,6 +46,8 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -54,10 +58,6 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -113,12 +113,6 @@ 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 {
+10 -1
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::NymConfig;
use config::{defaults::default_api_endpoints, NymConfig};
use version_checker::Version;
use clap::Args;
@@ -105,6 +105,15 @@ 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());
+1 -2
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
pub mod commands;
@@ -10,11 +9,11 @@ pub mod websocket;
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
}
+1
View File
@@ -13,6 +13,7 @@ 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"
+5 -9
View File
@@ -3,6 +3,7 @@
use crate::client::config::Config;
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
pub mod init;
@@ -27,6 +28,7 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -44,6 +46,8 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -54,10 +58,6 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) {
}
}
pub fn parse_validators(raw: &str) -> Vec<Url> {
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
@@ -112,10 +112,6 @@ 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 {
+10 -1
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::NymConfig;
use config::{defaults::default_api_endpoints, NymConfig};
use version_checker::Version;
use clap::Args;
@@ -104,6 +104,15 @@ 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());
+1 -2
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
mod commands;
@@ -10,11 +9,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;
}
+3 -7
View File
@@ -5,7 +5,7 @@ use futures::StreamExt;
use log::*;
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use socks5_requests::Message;
use socks5_requests::Response;
pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender,
@@ -44,16 +44,12 @@ impl MixnetResponseListener {
warn!("this message had a surb - we didn't do anything with it");
}
let response = match Message::try_from_bytes(&raw_message) {
let response = match Response::try_from_bytes(&raw_message) {
Err(err) => {
warn!("failed to parse received response - {:?}", err);
return;
}
Ok(Message::Request(_)) => {
warn!("unexpected request");
return;
}
Ok(Message::Response(data)) => data,
Ok(data) => data,
};
self.controller_sender
+1 -1
View File
@@ -1 +1 @@
16
15.0.1
+1 -4
View File
@@ -31,12 +31,9 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"expect": "^28.1.3",
"mocha": "^10.0.0",
"prettier": "^2.5.1",
"typedoc": "^0.22.13",
"ts-mocha": "^10.0.0",
"typescript": "^4.6.2"
"typescript": "^4.1.3"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.28.0",
+12 -11
View File
@@ -64,20 +64,23 @@ export default class ValidatorClient implements INymClient {
readonly vestingContract: string;
readonly mainnetDenom = 'unym';
readonly mainnetDenom = "unym";
readonly mainnetPrefix = 'n';
readonly mainnetPrefix = "n";
private constructor(
client: SigningClient | QueryClient,
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
vestingContract: string
) {
this.client = client;
this.prefix = prefix;
this.denom = `u${denom}`;
if (prefix == this.mainnetPrefix) {
this.denom = this.mainnetDenom;
} else {
this.denom = `u${prefix}`;
}
this.mixnetContract = mixnetContract;
this.vestingContract = vestingContract;
@@ -90,12 +93,11 @@ export default class ValidatorClient implements INymClient {
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom);
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom);
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix);
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract);
}
static async connectForQuery(
@@ -104,10 +106,9 @@ export default class ValidatorClient implements INymClient {
prefix: string,
mixnetContract: string,
vestingContract: string,
denom: string,
): Promise<ValidatorClient> {
const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl);
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom);
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract);
}
public get address(): string {
@@ -456,4 +457,4 @@ export default class ValidatorClient implements INymClient {
this.assertSigning();
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
}
}
+1 -3
View File
@@ -221,12 +221,10 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
nymdUrl: string,
validatorApiUrl: string,
prefix: string,
denom: string,
): Promise<SigningClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
prefix,
gasPrice: nymGasPrice(denom),
gasPrice: nymGasPrice(prefix),
};
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
+4 -3
View File
@@ -7,12 +7,13 @@ const mainnetDenom = 'nym';
export function nymGasPrice(prefix: string): GasPrice {
if (typeof prefix === 'string') {
if (prefix === mainnetPrefix) {
return GasPrice.fromString(`0.025u${mainnetDenom}`);
prefix = mainnetDenom;
}
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
throw new Error(`${prefix} is not of type string`);
else {
throw new Error(`${prefix} is not of type string`);
}
}
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
@@ -1,11 +0,0 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
-14
View File
@@ -1,14 +0,0 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
// TODO: implement for QA with .env for mnemonics
// describe('Sign: send', () => {
// it('can send tokens', async () => {
// const client = await ValidatorClient.connect(
// '<ADD MNEMONIC HERE>',
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
// await client.send('<ADD ADDRESS HERE>')
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
// }).timeout(5000);
// })
+1 -2
View File
@@ -5,8 +5,7 @@
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true
"outDir": "./dist"
},
"typedocOptions": {
"entryPoints": [
File diff suppressed because it is too large Load Diff
@@ -35,7 +35,7 @@ validator-api-requests = { path = "../../../validator-api/validator-api-requests
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cosmrs = { version = "0.7.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
@@ -5,7 +5,8 @@ use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody,
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
@@ -28,7 +29,7 @@ use mixnet_contract_common::{
RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
use network_defaults::{all::Network, NymNetworkDetails};
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
@@ -113,6 +114,9 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
// compatibility : (
pub network: Network,
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// non-trivial amount of work and it's out of scope of the current branch
mnemonic: Option<bip39::Mnemonic>,
@@ -131,6 +135,9 @@ pub struct Client<C> {
impl Client<SigningNymdClient> {
pub fn new_signing(
config: Config,
// we need to provide network argument due to compatibility with other components (wallet...)
// that rely on its existence...
network: Network,
mnemonic: bip39::Mnemonic,
) -> Result<Client<SigningNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
@@ -142,6 +149,7 @@ impl Client<SigningNymdClient> {
)?;
Ok(Client {
network,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -169,12 +177,18 @@ impl Client<SigningNymdClient> {
#[cfg(feature = "nymd-client")]
impl Client<QueryNymdClient> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
pub fn new_query(
config: Config,
// we need to provide network argument due to compatibility with other components (wallet...)
// that rely on its existence...
network: Network,
) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
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,
@@ -720,10 +734,6 @@ impl ApiClient {
Ok(self.validator_api.get_coconut_verification_key().await?)
}
pub async fn get_cosmos_address(&self) -> Result<CosmosAddressResponse, ValidatorClientError> {
Ok(self.validator_api.get_cosmos_address().await?)
}
pub async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
@@ -733,4 +743,24 @@ impl ApiClient {
.verify_bandwidth_credential(request_body)
.await?)
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorClientError> {
Ok(self
.validator_api
.propose_release_funds(request_body)
.await?)
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorClientError> {
Ok(self
.validator_api
.execute_release_funds(request_body)
.await?)
}
}
@@ -12,9 +12,6 @@ use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::feegrant::{
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
};
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
@@ -26,7 +23,7 @@ use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
use std::time::{Duration, SystemTime};
use std::time::Duration;
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
@@ -423,63 +420,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()
}
#[allow(clippy::too_many_arguments)]
async fn grant_allowance(
&self,
granter: &AccountId,
grantee: &AccountId,
spend_limit: Vec<Coin>,
expiration: Option<SystemTime>,
allowed_messages: Vec<String>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let basic_allowance = BasicAllowance {
spend_limit: spend_limit.into_iter().map(Into::into).collect(),
expiration,
}
.to_any()
.map_err(|_| NymdError::SerializationError("BasicAllowance".to_owned()))?;
let allowed_msg_allowance = AllowedMsgAllowance {
allowance: Some(basic_allowance),
allowed_messages,
}
.to_any()
.map_err(|_| NymdError::SerializationError("AllowedMsgAllowance".to_owned()))?;
let grant_allowance_msg = MsgGrantAllowance {
granter: granter.to_owned(),
grantee: grantee.to_owned(),
allowance: Some(allowed_msg_allowance),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgGrantAllowance".to_owned()))?;
self.sign_and_broadcast(granter, vec![grant_allowance_msg], fee, memo)
.await?
.check_response()
}
async fn revoke_allowance(
&self,
granter: &AccountId,
grantee: &AccountId,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, NymdError> {
let revoke_allowance_msg = MsgRevokeAllowance {
granter: granter.to_owned(),
grantee: grantee.to_owned(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgRevokeAllowance".to_owned()))?;
self.sign_and_broadcast(granter, vec![revoke_allowance_msg], fee, memo)
.await?
.check_response()
}
async fn delegate_tokens(
&self,
delegator_address: &AccountId,
@@ -576,8 +516,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
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.payer = Some(auto_feegrant.payer);
fee.granter = Some(auto_feegrant.granter);
fee
}
};
@@ -16,16 +16,16 @@ pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.3;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoFeeGrant {
pub gas_adjustment: Option<GasAdjustment>,
pub payer: Option<AccountId>,
pub granter: Option<AccountId>,
pub payer: AccountId,
pub granter: AccountId,
}
impl Display for AutoFeeGrant {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(gas_adjustment) = self.gas_adjustment {
write!(f, "Feegrant in auto mode with {gas_adjustment} simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
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)
write!(f, "Feegrant in auto mode with no custom simulated multiplier with {} payer and {} granter", self.payer, self.granter)
}
}
}
@@ -64,17 +64,6 @@ impl Display for Fee {
}
impl Fee {
pub fn new_payer_granter_auto(
gas_adjustment: Option<GasAdjustment>,
payer: Option<AccountId>,
granter: Option<AccountId>,
) -> Self {
Fee::PayerGranterAuto(AutoFeeGrant {
gas_adjustment,
payer,
granter,
})
}
pub fn try_get_manual_amount(&self) -> Option<Vec<Coin>> {
match self {
Fee::Manual(tx_fee) => Some(tx_fee.amount.iter().cloned().map(Into::into).collect()),
@@ -26,7 +26,6 @@ use mixnet_contract_common::{
};
use serde::Serialize;
use std::convert::TryInto;
use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract_common::QueryMsg as VestingQueryMsg;
@@ -846,49 +845,6 @@ impl<C> NymdClient<C> {
.await
}
/// Grant a fee allowance from one address to another
pub async fn grant_allowance(
&self,
grantee: &AccountId,
spend_limit: Vec<Coin>,
expiration: Option<SystemTime>,
allowed_messages: Vec<String>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.grant_allowance(
self.address(),
grantee,
spend_limit,
expiration,
allowed_messages,
fee,
memo,
)
.await
}
/// Revoke a fee allowance from one address to another
pub async fn revoke_allowance(
&self,
grantee: &AccountId,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.revoke_allowance(self.address(), grantee, fee, memo)
.await
}
pub async fn execute<M>(
&self,
contract_address: &AccountId,
@@ -1006,29 +962,6 @@ impl<C> NymdClient<C> {
.await
}
#[execute("mixnet")]
fn _compound_reward(
&self,
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
fee: Option<Fee>,
) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
},
fee,
)
}
#[execute("mixnet")]
fn _compound_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
where
@@ -1,33 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient};
use coconut_bandwidth_contract_common::msg::QueryMsg;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use async_trait::async_trait;
#[async_trait]
pub trait CoconutBandwidthQueryClient {
async fn get_spent_credential(
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> CoconutBandwidthQueryClient for NymdClient<C> {
async fn get_spent_credential(
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse, NymdError> {
let request = QueryMsg::GetSpentCredential {
blinded_serial_number,
};
self.client
.query_contract_smart(self.coconut_bandwidth_contract_address(), &request)
.await
}
}
@@ -5,7 +5,6 @@ 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;
@@ -20,13 +19,6 @@ pub trait CoconutBandwidthSigningClient {
encryption_key: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn spend_credential(
&self,
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
@@ -54,30 +46,4 @@ impl<C: SigningCosmWasmClient + Sync + Send> CoconutBandwidthSigningClient for N
)
.await
}
async fn spend_credential(
&self,
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
funds.into(),
blinded_serial_number,
gateway_cosmos_address,
),
};
self.client
.execute(
self.address(),
self.coconut_bandwidth_contract_address(),
&req,
fee,
"CoconutBandwidth::SpendCredential",
vec![],
)
.await
}
}
@@ -1,16 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_query_client::QueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -9,12 +9,12 @@ use multisig_contract_common::msg::{ProposalResponse, QueryMsg};
use async_trait::async_trait;
#[async_trait]
pub trait MultisigQueryClient {
pub trait QueryClient {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> MultisigQueryClient for NymdClient<C> {
impl<C: CosmWasmClient + Sync + Send> QueryClient for NymdClient<C> {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError> {
let request = QueryMsg::Proposal { proposal_id };
self.client
@@ -12,6 +12,7 @@ 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 {
@@ -48,10 +49,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> MultisigSigningClient for NymdClien
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
funds: Coin::new(
voucher_value,
self.config.chain_details.mix_denom.base.clone(),
),
funds: Coin::new(voucher_value, DEFAULT_NETWORK.mix_denom().base),
};
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: self.coconut_bandwidth_contract_address().to_string(),
@@ -207,20 +207,35 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = MAINNET.bech32_prefix();
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
let prefixes = vec![
MAINNET.bech32_prefix(),
SANDBOX.bech32_prefix(),
QA.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()
)
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()
)
}
}
}
}
@@ -8,8 +8,9 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
ExecuteReleaseFundsRequestBody, ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse,
VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
@@ -404,6 +405,40 @@ impl Client {
)
.await
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_PROPOSE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_EXECUTE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -19,6 +19,8 @@ pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-creden
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds";
pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
@@ -9,4 +9,3 @@ edition = "2021"
cosmwasm-std = "1.0.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
multisig-contract-common = { path = "../multisig-contract" }
@@ -1,4 +1,3 @@
pub mod deposit;
pub mod events;
pub mod msg;
pub mod spend_credential;
@@ -5,34 +5,24 @@ use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{deposit::DepositData, spend_credential::SpendCredentialData};
use crate::deposit::DepositData;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct InstantiateMsg {
pub multisig_addr: String,
pub pool_addr: String,
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
SpendCredential { data: SpendCredentialData },
ReleaseFunds { funds: Coin },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetSpentCredential {
blinded_serial_number: String,
},
GetAllSpentCredentials {
limit: Option<u32>,
start_after: Option<String>,
},
}
pub enum QueryMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -1,148 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg};
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::msg::ExecuteMsg;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SpendCredentialData {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: String,
}
impl SpendCredentialData {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: String) -> Self {
SpendCredentialData {
funds,
blinded_serial_number,
gateway_cosmos_address,
}
}
pub fn funds(&self) -> &Coin {
&self.funds
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn gateway_cosmos_address(&self) -> &str {
&self.gateway_cosmos_address
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub enum SpendCredentialStatus {
InProgress,
Spent,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct SpendCredential {
funds: Coin,
blinded_serial_number: String,
gateway_cosmos_address: Addr,
status: SpendCredentialStatus,
}
impl SpendCredential {
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: Addr) -> Self {
SpendCredential {
funds,
blinded_serial_number,
gateway_cosmos_address,
status: SpendCredentialStatus::InProgress,
}
}
pub fn blinded_serial_number(&self) -> &str {
&self.blinded_serial_number
}
pub fn status(&self) -> SpendCredentialStatus {
self.status
}
pub fn mark_as_spent(&mut self) {
self.status = SpendCredentialStatus::Spent;
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedSpendCredentialResponse {
pub spend_credentials: Vec<SpendCredential>,
pub per_page: usize,
pub start_next_after: Option<String>,
}
impl PagedSpendCredentialResponse {
pub fn new(
spend_credentials: Vec<SpendCredential>,
per_page: usize,
start_next_after: Option<String>,
) -> Self {
PagedSpendCredentialResponse {
spend_credentials,
per_page,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct SpendCredentialResponse {
pub spend_credential: Option<SpendCredential>,
}
impl SpendCredentialResponse {
pub fn new(spend_credential: Option<SpendCredential>) -> Self {
SpendCredentialResponse { spend_credential }
}
}
pub fn to_cosmos_msg(
funds: Coin,
blinded_serial_number: String,
coconut_bandwidth_addr: String,
multisig_addr: String,
) -> StdResult<CosmosMsg> {
let release_funds_req = ExecuteMsg::ReleaseFunds { funds };
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_bandwidth_addr,
msg: to_binary(&release_funds_req)?,
funds: vec![],
});
let req = MultisigExecuteMsg::Propose {
title: String::from("Release funds, as ordered by Coconut Bandwidth Contract"),
description: blinded_serial_number,
msgs: vec![release_funds_msg],
latest: None,
};
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: multisig_addr,
msg: to_binary(&req)?,
funds: vec![],
});
Ok(msg)
}
pub fn funds_from_cosmos_msgs(msgs: Vec<CosmosMsg>) -> Option<Coin> {
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: _,
msg,
funds: _,
})) = msgs.get(0)
{
if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::<ExecuteMsg>(msg) {
return Some(funds);
}
}
None
}
@@ -3,7 +3,6 @@ name = "mixnet-contract-common"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.62"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -14,6 +13,7 @@ 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"
@@ -10,7 +10,6 @@ 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)]
@@ -33,12 +32,6 @@ pub enum ExecuteMsg {
CompoundDelegatorReward {
mix_identity: IdentityKey,
},
CompoundReward {
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
},
BondMixnode {
mix_node: MixNode,
owner_signature: String,
@@ -122,7 +115,6 @@ pub enum ExecuteMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetBlacklistedNodes {},
GetCurrentOperatorCost {},
GetRewardingValidatorAddress {},
GetAllDelegationKeys {},
@@ -213,31 +205,6 @@ pub enum QueryMsg {
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub mixnet_denom: String,
nodes_to_remove: Option<Vec<NodeToRemove>>,
}
impl MigrateMsg {
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
self.nodes_to_remove.clone().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct NodeToRemove {
owner: String,
proxy: Option<String>,
}
impl NodeToRemove {
pub fn owner(&self) -> &str {
&self.owner
}
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
}
pub struct MigrateMsg {}
@@ -14,7 +14,6 @@ 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,
}
@@ -78,8 +77,3 @@ pub enum QueryMsg {
limit: Option<u32>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
}
@@ -1,5 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::MIX_DENOM;
use cosmwasm_std::{Coin, Timestamp};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -9,8 +10,8 @@ pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
pub mod events;
pub mod messages;
pub fn one_ucoin(denom: String) -> Coin {
Coin::new(1, denom)
pub fn one_ucoin() -> Coin {
Coin::new(1, MIX_DENOM.base)
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -7,14 +7,11 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "snake_case")]
pub struct InitMsg {
pub mixnet_contract_address: String,
pub mix_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub mix_denom: String,
}
pub struct MigrateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
pub struct VestingSpecification {
+2 -1
View File
@@ -7,13 +7,14 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", optional = true }
cosmrs = { version = "0.7.0", optional = true }
thiserror = "1.0"
url = "2.2"
# I guess temporarily until we get serde support in coconut up and running
coconut-interface = { path = "../coconut-interface" }
crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
network-defaults = { path = "../network-defaults" }
validator-api-requests = { path = "../../validator-api/validator-api-requests" }
validator-client = { path = "../client-libs/validator-client" }
+7 -11
View File
@@ -1,3 +1,4 @@
use config::defaults::DEFAULT_NETWORK;
use subtle_encoding::bech32;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -14,15 +15,16 @@ pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
}
}
pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(), Bech32Error> {
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
let prefix = try_bech32_decode(address)?;
if prefix == bech32_prefix {
if prefix == DEFAULT_NETWORK.bech32_prefix() {
Ok(())
} else {
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not {}",
bech32_prefix, prefix
DEFAULT_NETWORK.bech32_prefix(),
prefix
)))
}
}
@@ -31,8 +33,6 @@ pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(),
mod tests {
use super::*;
const TEST_BECH32_PREFIX: &str = "n";
mod decoding_bech32_addresses {
use super::*;
@@ -71,12 +71,9 @@ mod tests {
assert_eq!(
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not punk",
TEST_BECH32_PREFIX
DEFAULT_NETWORK.bech32_prefix()
))),
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0"
)
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
)
}
@@ -85,7 +82,6 @@ mod tests {
assert_eq!(
Ok(()),
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"
)
)
-1
View File
@@ -8,7 +8,6 @@ edition = "2021"
[dependencies]
cfg-if = "1.0.0"
dotenv = "0.15.0"
hex-literal = "0.3.3"
once_cell = "1.7.2"
serde = {version = "1.0", features = ["derive"]}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
fn main() {
match option_env!("NETWORK") {
None | Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
_ => panic!("No such network"),
}
}
+61 -63
View File
@@ -3,7 +3,6 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, path::PathBuf};
use url::Url;
pub mod all;
@@ -11,10 +10,36 @@ pub mod eth_contract;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
// 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;
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ChainDetails {
@@ -57,56 +82,23 @@ impl NymNetworkDetails {
NymNetworkDetails::default()
}
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_qa() -> Self {
(&*QA_DEFAULTS).into()
}
pub fn new_sandbox() -> Self {
(&*SANDBOX_DEFAULTS).into()
}
pub fn new_mainnet() -> Self {
(&*MAINNET_DEFAULTS).into()
}
pub fn current_default() -> Self {
// backwards compatibility reasons
DEFAULT_NETWORK.details()
}
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
@@ -341,21 +333,27 @@ impl ValidatorDetails {
}
}
pub fn setup_env(config_env_file: Option<PathBuf>) {
match std::env::var(var_names::CONFIGURED) {
// if the configuration is not already set in the env vars
Err(std::env::VarError::NotPresent) => {
if let Some(config_env_file) = config_env_file {
dotenv::from_path(config_env_file)
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
pub fn default_statistics_service_url() -> Url {
DEFAULT_NETWORK
.statistics_service_url()
.parse()
.expect("the provided statistics service url is invalid!")
}
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.map(ValidatorDetails::nymd_url)
.collect()
}
pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.filter_map(ValidatorDetails::api_url)
.collect()
}
// Name of the event triggered by the eth contract. If the event name is changed,
+4 -44
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::var_names;
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
@@ -25,48 +24,9 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
pub const NYMD_VALIDATOR: &str = "https://rpc.nyx.nodes.guru/";
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(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);
vec![ValidatorDetails::new(
"https://rpc.nyx.nodes.guru/",
Some("https://validator.nymtech.net/api/"),
)]
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// Environment variable that, if set, shows the environment is currently configured
pub const CONFIGURED: &str = "CONFIGURED";
pub const BECH32_PREFIX: &str = "BECH32_PREFIX";
pub const MIX_DENOM: &str = "MIX_DENOM";
pub const MIX_DENOM_DISPLAY: &str = "MIX_DENOM_DISPLAY";
pub const STAKE_DENOM: &str = "STAKE_DENOM";
pub const STAKE_DENOM_DISPLAY: &str = "STAKE_DENOM_DISPLAY";
pub const DENOMS_EXPONENT: &str = "DENOMS_EXPONENT";
pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS";
pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR";
pub const API_VALIDATOR: &str = "API_VALIDATOR";
+2 -1
View File
@@ -34,7 +34,8 @@ mod error;
mod impls;
mod proofs;
mod scheme;
pub mod tests;
#[cfg(test)]
mod tests;
mod traits;
mod utils;
+63 -3
View File
@@ -1,13 +1,23 @@
use crate::{
aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen,
verify_credential, CoconutError, VerificationKey,
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
SignatureShare, VerificationKey,
};
use itertools::izip;
#[test]
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes)?;
// generate_keys
let coconut_keypairs = ttp_keygen(&params, 2, 3)?;
@@ -20,8 +30,58 @@ fn main() -> Result<(), CoconutError> {
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
&params,
vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = theta_from_keys_and_attributes(&params, &coconut_keypairs, &public_attributes)?;
let theta = prove_bandwidth_credential(
&params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
// Verify credentials
assert!(verify_credential(
-87
View File
@@ -1,87 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::*;
use itertools::izip;
pub fn theta_from_keys_and_attributes(
params: &Parameters,
coconut_keypairs: &Vec<KeyPair>,
public_attributes: &Vec<PublicAttribute>,
) -> Result<Theta, CoconutError> {
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(params, &private_attributes, public_attributes)?;
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// aggregate verification keys
let indices: Vec<u64> = coconut_keypairs
.iter()
.enumerate()
.map(|(idx, _)| (idx + 1) as u64)
.collect();
let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
params,
&keypair.secret_key(),
&blind_sign_request,
public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
params,
vk,
&private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
Ok(theta)
}
-2
View File
@@ -1,3 +1 @@
#[cfg(test)]
mod e2e;
pub mod helpers;
+2
View File
@@ -17,3 +17,5 @@ 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" }
+2 -6
View File
@@ -34,16 +34,12 @@ pub enum StatsData {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsGatewayData {
pub gateway_id: String,
pub inbox_count: u32,
}
impl StatsGatewayData {
pub fn new(gateway_id: String, inbox_count: u32) -> Self {
StatsGatewayData {
gateway_id,
inbox_count,
}
pub fn new(inbox_count: u32) -> Self {
StatsGatewayData { inbox_count }
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ url = "2.2"
ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmrs = "0.7.0"
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
+9 -10
View File
@@ -1,5 +1,4 @@
use crate::currency::{CurrencyDenom, DecCoin};
use config::defaults::DenomDetails;
use crate::currency::DecCoin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -10,20 +9,20 @@ use serde::{Deserialize, Serialize};
)]
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct Account {
// TODO: discuss with @MS whether it makes sense:
// 1. why are we restricting to single denom here? What if user holds both stake and mix currencies?
// 2. what's the `contract_address`? is it mixnet? vesting? coconut? why does it relate to an account anyway?
pub contract_address: String,
pub client_address: String,
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,
pub denom: String,
}
impl Account {
pub fn new(client_address: String, mix_denom: DenomDetails) -> Self {
pub fn new(contract_address: String, client_address: String, denom: String) -> Self {
Account {
contract_address,
client_address,
base_mix_denom: mix_denom.base.to_owned(),
display_mix_denom: mix_denom.display.parse().unwrap_or_default(),
denom,
}
}
}
+2 -5
View File
@@ -23,7 +23,6 @@ use ts_rs::{Dependency, TS};
#[cfg_attr(feature = "generate-ts", ts(rename_all = "lowercase"))]
#[derive(
Display,
Default,
Serialize,
Deserialize,
Clone,
@@ -37,9 +36,6 @@ use ts_rs::{Dependency, TS};
#[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)]
@@ -320,9 +316,10 @@ pub fn try_convert_decimal_to_u128(dec: Decimal) -> Result<u128, TypesError> {
Ok(whole.u128())
}
// TODO: adjust the implementation to as required by @MS or @FT
impl Display for DecCoin {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.amount, self.denom)
write!(f, "{}{}", self.amount, self.denom)
}
}
+9 -34
View File
@@ -50,6 +50,7 @@ name = "bandwidth-claim"
version = "1.0.0"
dependencies = [
"bandwidth-claim-contract",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"schemars",
@@ -220,11 +221,12 @@ version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth-contract-common",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
@@ -235,32 +237,10 @@ name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"multisig-contract-common",
"schemars",
"serde",
]
[[package]]
name = "coconut-test"
version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth",
"coconut-bandwidth-contract-common",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw3-flex-multisig",
"cw4-group",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -612,12 +592,6 @@ 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"
@@ -1082,6 +1056,7 @@ dependencies = [
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
@@ -1107,7 +1082,6 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if",
"dotenv",
"hex-literal",
"once_cell",
"serde",
@@ -1392,18 +1366,18 @@ dependencies = [
[[package]]
name = "regex"
version = "1.6.0"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rfc6979"
@@ -1854,6 +1828,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
[profile.release]
opt-level = 3
+2
View File
@@ -9,6 +9,8 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dev-dependencies]
config = { path = "../../common/config"}
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
+3 -4
View File
@@ -62,11 +62,10 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
pub mod tests {
use super::*;
use bandwidth_claim_contract::payment::PagedPaymentResponse;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
@@ -92,11 +91,11 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, TEST_MIX_DENOM),
coins(0, MIX_DENOM.base),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, TEST_MIX_DENOM)
.query_balance(env.contract.address, MIX_DENOM.base)
.unwrap()]
);
}
+4 -1
View File
@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
config = { path = "../../common/config"}
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
@@ -21,3 +21,6 @@ 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" }
+77 -23
View File
@@ -1,14 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use cosmwasm_std::{entry_point, 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;
@@ -25,14 +22,12 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
let pool_addr = deps.api.addr_validate(&msg.pool_addr)?;
let mix_denom = msg.mix_denom;
ADMIN.set(deps.branch(), Some(multisig_addr.clone()))?;
let cfg = Config {
multisig_addr,
pool_addr,
mix_denom,
};
CONFIG.save(deps.storage, &cfg)?;
@@ -49,23 +44,13 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
ExecuteMsg::SpendCredential { data } => {
transactions::spend_credential(deps, env, info, data)
}
ExecuteMsg::ReleaseFunds { funds } => transactions::release_funds(deps, env, info, funds),
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
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)?),
}
pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult<Binary> {
unimplemented!();
}
#[entry_point]
@@ -76,10 +61,12 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use crate::support::tests::helpers::*;
use cosmwasm_std::coins;
use coconut_bandwidth_contract_common::deposit::DepositData;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, Addr};
use cw_multi_test::Executor;
#[test]
fn initialize_contract() {
@@ -88,7 +75,6 @@ mod tests {
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
@@ -97,12 +83,80 @@ mod tests {
// Contract balance should be 0
assert_eq!(
coins(0, TEST_MIX_DENOM),
coins(0, MIX_DENOM.base),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, TEST_MIX_DENOM)
.query_balance(env.contract.address, MIX_DENOM.base)
.unwrap()]
);
}
#[test]
fn deposit_and_release() {
let init_funds = coins(10, MIX_DENOM.base);
let deposit_funds = coins(1, MIX_DENOM.base);
let release_funds = coins(2, MIX_DENOM.base);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, MIX_DENOM.base).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
}
+4 -5
View File
@@ -5,6 +5,8 @@ use cosmwasm_std::StdError;
use cw_controllers::AdminError;
use thiserror::Error;
use config::defaults::MIX_DENOM;
/// Custom errors for contract failure conditions.
///
/// Add any other custom errors you like here.
@@ -20,15 +22,12 @@ pub enum ContractError {
#[error("No coin was sent for voucher")]
NoCoin,
#[error("Wrong coin denomination, you must send {mix_denom}")]
WrongDenom { mix_denom: String },
#[error("Wrong coin denomination, you must send {}", MIX_DENOM.base)]
WrongDenom,
#[error("There aren't enough funds in the contract")]
NotEnoughFunds,
#[error("Credential already spent or in process of spending")]
DuplicateBlindedSerialNumber,
#[error("{0}")]
Admin(#[from] AdminError),
}
+1 -3
View File
@@ -2,9 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
pub mod error;
mod queries;
mod error;
mod state;
mod storage;
mod support;
mod transactions;
-178
View File
@@ -1,178 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
pub(crate) fn query_all_spent_credentials_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedSpendCredentialResponse> {
let limit = limit
.unwrap_or(SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT)
.min(SPEND_CREDENTIAL_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let nodes = storage::spent_credentials()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<SpendCredential>>>()?;
let start_next_after = nodes
.last()
.map(|spend_credential| spend_credential.blinded_serial_number().to_string());
Ok(PagedSpendCredentialResponse::new(
nodes,
limit,
start_next_after,
))
}
pub(crate) fn query_spent_credential(
deps: Deps<'_>,
blinded_serial_number: String,
) -> StdResult<SpendCredentialResponse> {
let spend_credential =
storage::spent_credentials().may_load(deps.storage, &blinded_serial_number)?;
Ok(SpendCredentialResponse::new(spend_credential))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::init_contract;
use crate::transactions::spend_credential;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn spent_credentials_empty_on_init() {
let deps = init_contract();
let response =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.spend_credentials.len());
}
#[test]
fn spent_credentials_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let limit = 2;
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query without explicitly setting a limit
let page1 = query_all_spent_credentials_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(
SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT,
page1.spend_credentials.len() as u32
);
}
#[test]
fn spent_credentials_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_pagination_works() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number1");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
let per_page = 2;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.spend_credentials.len());
// save another
let data = spend_credential_data_fixture("blinded_serial_number2");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 should have 2 results on it
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number3");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 still has 2 results
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_all_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number4");
spend_credential(deps.as_mut(), env, info, data).unwrap();
let page2 = query_all_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.spend_credentials.len());
}
}
-1
View File
@@ -13,7 +13,6 @@ pub const ADMIN: Admin = Admin::new("admin");
pub struct Config {
pub multisig_addr: Addr,
pub pool_addr: Addr,
pub mix_denom: String,
}
pub const CONFIG: Item<Config> = Item::new("config");
-106
View File
@@ -1,106 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::SpendCredential;
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
// storage prefixes
const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc";
const SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE: &str = "scn";
// paged retrieval limits for all queries and transactions
pub(crate) const SPEND_CREDENTIAL_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) struct SpendCredentialIndex<'a> {
pub(crate) blinded_serial_number: UniqueIndex<'a, String, SpendCredential>,
}
// IndexList is just boilerplate code for fetching a struct's indexes
// note that from my understanding this will be converted into a macro at some point in the future
impl<'a> IndexList<SpendCredential> for SpendCredentialIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<SpendCredential>> + '_> {
let v: Vec<&dyn Index<SpendCredential>> = vec![&self.blinded_serial_number];
Box::new(v.into_iter())
}
}
// gateways() is the storage access function.
pub(crate) fn spent_credentials<'a>(
) -> IndexedMap<'a, &'a str, SpendCredential, SpendCredentialIndex<'a>> {
let indexes = SpendCredentialIndex {
blinded_serial_number: UniqueIndex::new(
|d| d.blinded_serial_number().to_string(),
SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE,
),
};
IndexedMap::new(SPEND_CREDENTIAL_PK_NAMESPACE, indexes)
}
// currently not used outside tests
#[cfg(test)]
mod tests {
use super::super::storage;
use crate::storage::SpendCredential;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
#[test]
fn spend_credential_single_read_retrieval() {
let mut storage = MockStorage::new();
let blind_serial_number1 = "number1";
let blind_serial_number2 = "number2";
let spend1 = fixtures::spend_credential_fixture(blind_serial_number1);
let spend2 = fixtures::spend_credential_fixture(blind_serial_number2);
storage::spent_credentials()
.save(&mut storage, blind_serial_number1, &spend1)
.unwrap();
storage::spent_credentials()
.save(&mut storage, blind_serial_number2, &spend2)
.unwrap();
let res1 = storage::spent_credentials()
.load(&storage, blind_serial_number1)
.unwrap();
let res2 = storage::spent_credentials()
.load(&storage, blind_serial_number2)
.unwrap();
assert_eq!(spend1, res1);
assert_eq!(spend2, res2);
}
#[test]
fn mark_as_spent_credential() {
let mut mock_storage = MockStorage::new();
let funds = Coin::new(100, TEST_MIX_DENOM);
let blind_serial_number = "blind_serial_number";
let gateway_cosmos_address: Addr = Addr::unchecked("gateway_cosmos_address");
let res = storage::spent_credentials()
.may_load(&mock_storage, blind_serial_number)
.unwrap();
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds.clone(),
blind_serial_number.to_string(),
gateway_cosmos_address.clone(),
);
spend_credential.mark_as_spent();
storage::spent_credentials()
.save(&mut mock_storage, blind_serial_number, &spend_credential)
.unwrap();
let ret = storage::spent_credentials()
.load(&mock_storage, blind_serial_number)
.unwrap();
assert_eq!(ret, spend_credential);
assert_eq!(ret.status(), SpendCredentialStatus::Spent);
}
}
@@ -1,5 +1,3 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod tests;
@@ -0,0 +1,45 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Addr, Coin, Empty, MemoryStorage, OwnedDeps};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
);
Box::new(contract)
}
}
@@ -1,23 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData};
use cosmwasm_std::{Addr, Coin};
pub const TEST_MIX_DENOM: &str = "unym";
pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential {
SpendCredential::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
Addr::unchecked("gateway_owner_addr"),
)
}
pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData {
SpendCredentialData::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
"gateway_owner_addr".to_string(),
)
}
@@ -1,25 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
use super::fixtures::TEST_MIX_DENOM;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
@@ -1,5 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
+16 -161
View File
@@ -1,23 +1,20 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
to_cosmos_msg, SpendCredential, SpendCredentialData,
};
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response};
use crate::error::ContractError;
use crate::state::{ADMIN, CONFIG};
use crate::storage;
use coconut_bandwidth_contract_common::deposit::DepositData;
use coconut_bandwidth_contract_common::events::{
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
DEPOSIT_VALUE,
};
use config::defaults::MIX_DENOM;
pub(crate) fn deposit_funds(
deps: DepsMut<'_>,
_deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
data: DepositData,
@@ -28,9 +25,8 @@ pub(crate) fn deposit_funds(
if info.funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if info.funds[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
if info.funds[0].denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
}
let voucher_value = info.funds.last().unwrap();
@@ -43,55 +39,18 @@ pub(crate) fn deposit_funds(
Ok(Response::new().add_event(event))
}
pub(crate) fn spend_credential(
deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
data: SpendCredentialData,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if data.funds().denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
if storage::spent_credentials().has(deps.storage, data.blinded_serial_number()) {
return Err(ContractError::DuplicateBlindedSerialNumber);
}
let cfg = CONFIG.load(deps.storage)?;
let gateway_cosmos_address = deps.api.addr_validate(data.gateway_cosmos_address())?;
storage::spent_credentials().save(
deps.storage,
data.blinded_serial_number(),
&SpendCredential::new(
data.funds().to_owned(),
data.blinded_serial_number().to_owned(),
gateway_cosmos_address,
),
)?;
let msg = to_cosmos_msg(
data.funds().clone(),
data.blinded_serial_number().to_string(),
env.contract.address.into_string(),
cfg.multisig_addr.into_string(),
)?;
Ok(Response::new().add_message(msg))
}
pub(crate) fn release_funds(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
funds: Coin,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if funds.denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
if funds.denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
}
let current_balance = deps
.querier
.query_balance(env.contract.address, mix_denom)?;
.query_balance(env.contract.address, MIX_DENOM.base)?;
if funds.amount > current_balance.amount {
return Err(ContractError::NotEnoughFunds);
}
@@ -111,13 +70,11 @@ pub(crate) fn release_funds(
#[cfg(test)]
mod tests {
use super::*;
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 crate::support::tests::helpers;
use crate::support::tests::helpers::{MULTISIG_CONTRACT, POOL_CONTRACT};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg};
use cosmwasm_std::{Coin, CosmosMsg};
use cw_controllers::AdminError;
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
#[test]
fn invalid_deposit() {
@@ -135,7 +92,7 @@ mod tests {
Err(ContractError::NoCoin)
);
let coin = Coin::new(1000000, crate::support::tests::fixtures::TEST_MIX_DENOM);
let coin = Coin::new(1000000, MIX_DENOM.base);
let second_coin = Coin::new(1000000, "some_denom");
let info = mock_info("requester", &[coin, second_coin.clone()]);
@@ -147,9 +104,7 @@ mod tests {
let info = mock_info("requester", &[second_coin]);
assert_eq!(
deposit_funds(deps.as_mut(), env, info, data),
Err(ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
})
Err(ContractError::WrongDenom)
);
}
@@ -167,10 +122,7 @@ mod tests {
verification_key.clone(),
encryption_key.clone(),
);
let coin = Coin::new(
deposit_value,
crate::support::tests::fixtures::TEST_MIX_DENOM,
);
let coin = Coin::new(deposit_value, MIX_DENOM.base);
let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
@@ -219,7 +171,7 @@ mod tests {
let mut deps = helpers::init_contract();
let env = mock_env();
let invalid_admin = "invalid admin";
let funds = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
let funds = Coin::new(1, MIX_DENOM.base);
let err = release_funds(
deps.as_mut(),
@@ -228,12 +180,7 @@ mod tests {
Coin::new(1, "invalid denom"),
)
.unwrap_err();
assert_eq!(
err,
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
assert_eq!(err, ContractError::WrongDenom);
let err = release_funds(
deps.as_mut(),
@@ -260,7 +207,7 @@ mod tests {
fn valid_release() {
let mut deps = helpers::init_contract();
let env = mock_env();
let coin = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
let coin = Coin::new(1, MIX_DENOM.base);
deps.querier
.update_balance(env.contract.address.clone(), vec![coin.clone()]);
@@ -279,96 +226,4 @@ mod tests {
})
);
}
#[test]
fn valid_spend() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number");
let res = spend_credential(deps.as_mut(), env.clone(), info, data.clone()).unwrap();
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &res.messages[0].msg
{
assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
if let MultisigExecuteMsg::Propose {
title: _,
description,
msgs,
latest,
} = multisig_msg
{
assert_eq!(description, data.blinded_serial_number().to_string());
assert!(latest.is_none());
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msgs[0]
{
assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds());
} else {
panic!("Could not extract release funds message from proposal");
}
}
} else {
panic!("Could not extract proposal from binary blob");
}
} else {
panic!("Wasm execute message not found");
}
}
#[test]
fn invalid_spend_attempts() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let invalid_data = SpendCredentialData::new(
Coin::new(1, "invalid_denom".to_string()),
String::new(),
String::new(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let invalid_data = SpendCredentialData::new(
Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM),
String::new(),
"Blinded Serial Number".to_string(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err().to_string(),
"Generic error: Invalid input: address not normalized".to_string()
);
let invalid_data = spend_credential_data_fixture("blined_serial_number");
spend_credential(
deps.as_mut(),
env.clone(),
info.clone(),
invalid_data.clone(),
)
.unwrap();
let ret = spend_credential(deps.as_mut(), env, info, invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::DuplicateBlindedSerialNumber
);
}
}
-30
View File
@@ -1,30 +0,0 @@
[package]
name = "coconut-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.4"
cw-controllers = "0.13.4"
cw-utils = "0.13.4"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
coconut-bandwidth = { path = "../coconut-bandwidth" }
cw-multi-test = { version = "0.13.2" }
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
cw4-group = { path = "../multisig/cw4-group" }
[[test]]
name = "coconut-test"
path = "src/tests.rs"
@@ -1,101 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
deposit::DepositData,
msg::{ExecuteMsg, InstantiateMsg},
};
use cosmwasm_std::{coins, Addr};
use cw_controllers::AdminError;
use cw_multi_test::Executor;
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn deposit_and_release() {
let init_funds = coins(10, TEST_MIX_DENOM);
let deposit_funds = coins(1, TEST_MIX_DENOM);
let release_funds = coins(2, TEST_MIX_DENOM);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let random_addr = String::from(RANDOM_ADDRESS);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
// try to call release from non-admin
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(random_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(
ContractError::Admin(AdminError::NotAdmin {}),
err.downcast().unwrap()
);
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
-64
View File
@@ -1,64 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
use cw3_flex_multisig::{state::CONFIG, ContractError};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
pub const RANDOM_ADDRESS: &str = "random address";
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
let mut cfg = CONFIG.load(deps.storage)?;
cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?;
CONFIG.save(deps.storage, &cfg)?;
Ok(Default::default())
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
coconut_bandwidth::contract::execute,
coconut_bandwidth::contract::instantiate,
coconut_bandwidth::contract::query,
);
Box::new(contract)
}
pub fn contract_multisig() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw3_flex_multisig::contract::execute,
cw3_flex_multisig::contract::instantiate,
cw3_flex_multisig::contract::query,
)
.with_migrate(migrate);
Box::new(contract)
}
pub fn contract_group() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw4_group::contract::execute,
cw4_group::contract::instantiate,
cw4_group::contract::query,
);
Box::new(contract)
}
@@ -1,160 +0,0 @@
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
msg::{
ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg,
},
spend_credential::SpendCredentialData,
};
use cosmwasm_std::{coins, Addr, Coin, Decimal};
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
pub const TEST_COIN_DENOM: &str = "unym";
pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
#[test]
fn spend_credential_creates_proposal() {
let init_funds = coins(10, TEST_COIN_DENOM);
let mut app = mock_app(&init_funds);
let pool_addr = String::from(POOL_CONTRACT);
let group_code_id = app.store_code(contract_group());
let msg = GroupInstantiateMsg {
admin: Some(OWNER.to_string()),
members: vec![],
};
let group_contract_addr = app
.instantiate_contract(
group_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"group",
None,
)
.unwrap();
let multisig_code_id = app.store_code(contract_multisig());
let msg = MultisigInstantiateMsg {
group_addr: group_contract_addr.into_string(),
threshold: Threshold::AbsolutePercentage {
percentage: Decimal::from_ratio(2u128, 3u128),
},
max_voting_period: Duration::Height(1000),
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
};
let multisig_contract_addr = app
.instantiate_contract(
multisig_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"multisig",
Some(OWNER.to_string()),
)
.unwrap();
let coconut_bandwidth_code_id = app.store_code(contract_bandwidth());
let msg = CoconutBandwidthInstantiateMsg {
multisig_addr: multisig_contract_addr.to_string(),
pool_addr,
mix_denom: TEST_COIN_DENOM.to_string(),
};
let coconut_bandwidth_contract_addr = app
.instantiate_contract(
coconut_bandwidth_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"coconut bandwidth",
None,
)
.unwrap();
let msg = MigrateMsg {
coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(),
};
app.migrate_contract(
Addr::unchecked(OWNER),
multisig_contract_addr,
&msg,
multisig_code_id,
)
.unwrap();
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(1, proposal_id);
// Trying with the same blinded serial number will detect the double spend attempt
let err = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap_err();
assert_eq!(
ContractError::DuplicateBlindedSerialNumber,
err.downcast().unwrap()
);
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number2"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(2, proposal_id);
}
-6
View File
@@ -1,6 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod deposit_and_release;
mod helpers;
mod spend_credential_creates_proposal;
+12 -87
View File
@@ -27,18 +27,16 @@ use crate::mixnodes::bonding_queries::{
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
};
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::mixnodes::transactions::_try_remove_mixnode;
use crate::queued_migrations::migrate_config_from_env;
use crate::rewards::queries::{
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
};
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
Storage, Uint128,
Uint128,
};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
use time::OffsetDateTime;
@@ -63,14 +61,9 @@ pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
}
fn default_initial_state(
owner: Addr,
mix_denom: String,
rewarding_validator_address: Addr,
) -> ContractState {
fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> ContractState {
ContractState {
owner,
mix_denom,
rewarding_validator_address,
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
@@ -95,7 +88,7 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, msg.mixnet_denom, rewarding_validator_address);
let state = default_initial_state(info.sender, rewarding_validator_address);
init_epoch(deps.storage, env)?;
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
@@ -114,19 +107,6 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
} => crate::rewards::transactions::try_compound_reward(
deps,
env,
operator,
delegator,
mix_identity,
proxy,
),
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
try_update_rewarding_validator_address(deps, info, address)
}
@@ -142,7 +122,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
}
ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
@@ -236,13 +216,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
&env,
deps.storage,
deps.api,
info,
owner,
)
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
@@ -301,7 +275,7 @@ pub fn execute(
)
}
ExecuteMsg::ReconcileDelegations {} => {
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
}
ExecuteMsg::CheckpointMixnodes {} => {
crate::mixnodes::transactions::try_checkpoint_mixnodes(
@@ -342,9 +316,6 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetBlacklistedNodes {} => to_binary(
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
),
QueryMsg::GetRewardingValidatorAddress {} => {
to_binary(&query_rewarding_validator_address(deps)?)
}
@@ -472,61 +443,16 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
Ok(query_res?)
}
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => {
return Err(ContractError::NoAssociatedMixNodeBond {
owner: owner.to_owned(),
})
}
};
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
Ok(())
}
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
fn remove_malicious_node(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
node: &NodeToRemove,
) -> Result<Response, ContractError> {
let proxy = node.proxy().map(|p| {
api.addr_validate(p)
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
});
let owner_addr = api.addr_validate(node.owner())?;
blacklist_malicious_node(storage, &owner_addr)?;
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
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)
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests;
use crate::support::tests::fixtures::{TEST_COIN_DENOM, TEST_REWARDING_VALIDATOR_ADDRESS};
use config::defaults::{DEFAULT_NETWORK, MIX_DENOM};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
use mixnet_contract_common::PagedMixnodeResponse;
@@ -536,8 +462,7 @@ pub mod tests {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
mixnet_denom: TEST_COIN_DENOM.to_string(),
rewarding_validator_address: DEFAULT_NETWORK.rewarding_validator_address().to_string(),
};
let info = mock_info("creator", &[]);
@@ -559,7 +484,7 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, TEST_COIN_DENOM),
coins(0, MIX_DENOM.base),
tests::queries::query_contract_balance(env.contract.address, deps)
);
}
+5 -4
View File
@@ -199,7 +199,8 @@ pub(crate) fn query_mixnode_delegations_paged(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::{fixtures::TEST_COIN_DENOM, test_helpers};
use crate::support::tests::test_helpers;
use config::defaults::MIX_DENOM;
use cosmwasm_std::{coin, Addr, Storage};
use rand::Rng;
@@ -384,7 +385,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
coin(1234, TEST_COIN_DENOM),
coin(1234, MIX_DENOM.base),
1234,
None,
);
@@ -432,7 +433,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner2,
node_identity1.clone(),
coin(1234, TEST_COIN_DENOM),
coin(1234, MIX_DENOM.base),
1234,
None,
);
@@ -459,7 +460,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner1.clone(),
node_identity2,
coin(1234, TEST_COIN_DENOM),
coin(1234, MIX_DENOM.base),
1234,
None,
);
+3 -3
View File
@@ -76,8 +76,8 @@ mod tests {
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
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;
use cosmwasm_std::{coin, Order};
use mixnet_contract_common::Delegation;
@@ -87,7 +87,7 @@ mod tests {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = coin(12345, TEST_COIN_DENOM);
let delegation = coin(12345, MIX_DENOM.base);
let dummy_data = Delegation::new(
delegation_owner.clone(),
@@ -125,7 +125,7 @@ mod tests {
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
let delegation = coin(12345, TEST_COIN_DENOM);
let delegation = coin(12345, MIX_DENOM.base);
assert!(test_helpers::read_delegation(
deps.as_ref().storage,
+231 -101
View File
@@ -4,8 +4,9 @@ use super::storage::{self, PENDING_DELEGATION_EVENTS};
// use crate::contract::debug_with_visibility;
// use crate::contract::debug_with_visibility;
use crate::error::ContractError;
use crate::mixnet_contract_settings::storage::{self as mixnet_params_storage};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use config::defaults::MIX_DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Order,
Response, Storage, Uint128, WasmMsg,
@@ -19,7 +20,16 @@ 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<'_>) -> Result<Response, ContractError> {
pub fn try_reconcile_all_delegation_events(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, ContractError> {
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);
}
_try_reconcile_all_delegation_events(deps.storage, deps.api)
}
@@ -64,10 +74,7 @@ pub(crate) fn _try_reconcile_all_delegation_events(
Ok(response)
}
fn validate_delegation_stake(
mut delegation: Vec<Coin>,
mix_denom: String,
) -> Result<Coin, ContractError> {
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
// check if anything was put as delegation
if delegation.is_empty() {
return Err(ContractError::EmptyDelegation);
@@ -78,8 +85,8 @@ fn validate_delegation_stake(
}
// check that the denomination is correct
if delegation[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
if delegation[0].denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom {});
}
// check that we have provided a non-zero amount in the delegation
@@ -97,8 +104,7 @@ pub(crate) fn try_delegate_to_mixnode(
mix_identity: IdentityKey,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
let amount =
validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?;
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(
deps,
@@ -118,8 +124,7 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
delegate: String,
) -> Result<Response, ContractError> {
// check if the delegation contains any funds of the appropriate denomination
let amount =
validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?;
let amount = validate_delegation_stake(info.funds)?;
_try_delegate_to_mixnode(
deps,
@@ -255,7 +260,6 @@ pub(crate) fn try_reconcile_undelegation(
pending_undelegate: &PendingUndelegate,
) -> Result<ReconcileUndelegateResponse, ContractError> {
let delegation_map = storage::delegations();
let mix_denom = mixnet_params_storage::mix_denom(storage)?;
// debug_with_visibility(api, "Reconciling undelegations");
@@ -385,7 +389,7 @@ pub(crate) fn try_reconcile_undelegation(
.as_ref()
.unwrap_or(&pending_undelegate.delegate())
.to_string(),
amount: coins(total_funds.u128(), mix_denom.clone()),
amount: coins(total_funds.u128(), MIX_DENOM.base),
})
} else {
None
@@ -397,10 +401,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.clone()),
amount: Coin::new(total_funds.u128(), MIX_DENOM.base),
});
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?);
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?);
}
let event = new_undelegation_event(
@@ -464,9 +468,9 @@ 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::storage;
use super::*;
#[cfg(test)]
@@ -479,7 +483,7 @@ mod tests {
fn stake_cant_be_empty() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(vec![], TEST_COIN_DENOM.to_string())
validate_delegation_stake(vec![])
)
}
@@ -487,24 +491,19 @@ mod tests {
fn stake_must_have_single_coin_type() {
assert_eq!(
Err(ContractError::MultipleDenoms),
validate_delegation_stake(
vec![
coin(123, TEST_COIN_DENOM),
coin(123, "BTC"),
coin(123, "DOGE")
],
TEST_COIN_DENOM.to_string()
)
validate_delegation_stake(vec![
coin(123, MIX_DENOM.base),
coin(123, "BTC"),
coin(123, "DOGE")
])
)
}
#[test]
fn stake_coin_must_be_of_correct_type() {
assert_eq!(
Err(ContractError::WrongDenom {
mix_denom: TEST_COIN_DENOM.to_string()
}),
validate_delegation_stake(coins(123, "DOGE"), TEST_COIN_DENOM.to_string())
Err(ContractError::WrongDenom {}),
validate_delegation_stake(coins(123, "DOGE"))
)
}
@@ -512,28 +511,16 @@ mod tests {
fn stake_coin_must_have_value_greater_than_zero() {
assert_eq!(
Err(ContractError::EmptyDelegation),
validate_delegation_stake(coins(0, TEST_COIN_DENOM), TEST_COIN_DENOM.to_string())
validate_delegation_stake(coins(0, MIX_DENOM.base))
)
}
#[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, 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());
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());
}
}
@@ -556,7 +543,7 @@ mod tests {
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info("sender", &coins(123, TEST_COIN_DENOM)),
mock_info("sender", &coins(123, MIX_DENOM.base)),
"non-existent-mix-identity".into(),
)
);
@@ -572,7 +559,7 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(123, TEST_COIN_DENOM);
let delegation = coin(123, MIX_DENOM.base);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
@@ -621,14 +608,7 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
let api = deps.api.clone();
try_remove_mixnode(
&mock_env(),
deps.as_mut().storage,
&api,
mock_info(mixnode_owner, &[]),
)
.unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
@@ -636,7 +616,7 @@ mod tests {
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)),
mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)),
identity,
)
);
@@ -651,20 +631,13 @@ mod tests {
tests::fixtures::good_mixnode_pledge(),
deps.as_mut(),
);
let api = deps.api.clone();
try_remove_mixnode(
&mock_env(),
deps.as_mut().storage,
&api,
mock_info(mixnode_owner, &[]),
)
.unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let identity = test_helpers::add_mixnode(
mixnode_owner,
tests::fixtures::good_mixnode_pledge(),
deps.as_mut(),
);
let delegation = coin(123, TEST_COIN_DENOM);
let delegation = coin(123, MIX_DENOM.base);
let delegation_owner = Addr::unchecked("sender");
assert!(try_delegate_to_mixnode(
deps.as_mut(),
@@ -714,8 +687,8 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
let delegation1 = coin(100, TEST_COIN_DENOM);
let delegation2 = coin(50, TEST_COIN_DENOM);
let delegation1 = coin(100, MIX_DENOM.base);
let delegation2 = coin(50, MIX_DENOM.base);
let mut env = mock_env();
@@ -742,7 +715,7 @@ mod tests {
// let expected = Delegation::new(
// delegation_owner.clone(),
// identity.clone(),
// coin(delegation1.amount.u128() + delegation2.amount.u128(), TEST_COIN_DENOM),
// coin(delegation1.amount.u128() + delegation2.amount.u128(), MIX_DENOM.base),
// mock_env().block.height,
// None,
// );
@@ -777,7 +750,7 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
let delegation = coin(100, TEST_COIN_DENOM);
let delegation = coin(100, MIX_DENOM.base);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
@@ -842,8 +815,8 @@ mod tests {
);
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(100, TEST_COIN_DENOM);
let delegation2 = coin(120, TEST_COIN_DENOM);
let delegation1 = coin(100, MIX_DENOM.base);
let delegation2 = coin(120, MIX_DENOM.base);
let env1 = mock_env();
let mut env2 = mock_env();
let initial_height = env1.block.height;
@@ -918,18 +891,11 @@ mod tests {
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)),
mock_info(delegation_owner.as_str(), &coins(100, MIX_DENOM.base)),
identity.clone(),
)
.unwrap();
let api = deps.api.clone();
try_remove_mixnode(
&mock_env(),
deps.as_mut().storage,
&api,
mock_info(mixnode_owner, &[]),
)
.unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
assert_eq!(
Err(ContractError::MixNodeBondNotFound {
identity: identity.clone()
@@ -937,7 +903,7 @@ mod tests {
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(50, TEST_COIN_DENOM)),
mock_info(delegation_owner.as_str(), &coins(50, MIX_DENOM.base)),
identity,
)
);
@@ -962,14 +928,14 @@ mod tests {
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)),
mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)),
identity1.clone(),
)
.is_ok());
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
mock_info(delegation_owner.as_str(), &coins(42, TEST_COIN_DENOM)),
mock_info(delegation_owner.as_str(), &coins(42, MIX_DENOM.base)),
identity2.clone(),
)
.is_ok());
@@ -979,7 +945,7 @@ mod tests {
let expected1 = Delegation::new(
delegation_owner.clone(),
identity1.clone(),
coin(123, TEST_COIN_DENOM),
coin(123, MIX_DENOM.base),
mock_env().block.height,
None,
);
@@ -987,7 +953,7 @@ mod tests {
let expected2 = Delegation::new(
delegation_owner.clone(),
identity2.clone(),
coin(42, TEST_COIN_DENOM),
coin(42, MIX_DENOM.base),
mock_env().block.height,
None,
);
@@ -1023,8 +989,8 @@ mod tests {
tests::fixtures::good_mixnode_pledge(),
deps.as_mut(),
);
let delegation1 = coin(123, TEST_COIN_DENOM);
let delegation2 = coin(234, TEST_COIN_DENOM);
let delegation1 = coin(123, MIX_DENOM.base);
let delegation2 = coin(234, MIX_DENOM.base);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
@@ -1060,7 +1026,7 @@ mod tests {
deps.as_mut(),
);
let delegation_owner = Addr::unchecked("sender");
let delegation_amount = coin(100, TEST_COIN_DENOM);
let delegation_amount = coin(100, MIX_DENOM.base);
try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
@@ -1070,14 +1036,8 @@ mod tests {
.unwrap();
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
let api = deps.api.clone();
try_remove_mixnode(
&mock_env(),
deps.as_mut().storage,
&api,
mock_info(mixnode_owner, &[]),
)
.unwrap();
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
let expected = Delegation::new(
delegation_owner.clone(),
@@ -1102,12 +1062,182 @@ mod tests {
#[cfg(test)]
mod removing_mix_stake_delegation {
use super::*;
use crate::support::tests;
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
//#[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, MIX_DENOM.base)),
// 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, MIX_DENOM.base),
// })
// .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, MIX_DENOM.base)),
// 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, MIX_DENOM.base),
// })
// .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() {
@@ -1121,8 +1251,8 @@ mod tests {
);
let delegation_owner1 = Addr::unchecked("sender1");
let delegation_owner2 = Addr::unchecked("sender2");
let delegation1 = coin(123, TEST_COIN_DENOM);
let delegation2 = coin(234, TEST_COIN_DENOM);
let delegation1 = coin(123, MIX_DENOM.base);
let delegation2 = coin(234, MIX_DENOM.base);
assert!(try_delegate_to_mixnode(
deps.as_mut(),
mock_env(),
+5 -11
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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;
@@ -32,14 +33,14 @@ pub enum ContractError {
#[error("MIXNET ({}): Unauthorized", line!())]
Unauthorized,
#[error("MIXNET ({}): Wrong coin denomination, you must send {mix_denom}", line!())]
WrongDenom { mix_denom: String },
#[error("MIXNET ({}): Wrong coin denomination, you must send {}", line!(), MIX_DENOM.base)]
WrongDenom,
#[error("MIXNET ({}): Received multiple coin types during staking", line!())]
MultipleDenoms,
#[error("MIXNET ({}): No coin was sent for the bonding, you must send {mix_denom}", line!())]
NoBondFound { mix_denom: String },
#[error("MIXNET ({}): No coin was sent for the bonding, you must send {}", line!(), MIX_DENOM.base)]
NoBondFound,
#[error("MIXNET ({}): Provided active set size is bigger than the rewarded set", line!())]
InvalidActiveSetSize,
@@ -178,11 +179,4 @@ pub enum ContractError {
last_update_time: u64,
current_block_time: u64,
},
#[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 },
}
+2 -2
View File
@@ -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 crate::support::tests::fixtures::TEST_COIN_DENOM;
use config::defaults::MIX_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, TEST_COIN_DENOM),
pledge_amount: coin(pledge_amount, MIX_DENOM.base),
owner: node_owner,
block_height: 12_345,
gateway: Gateway {
+17 -55
View File
@@ -5,6 +5,7 @@ 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,
};
@@ -25,8 +26,7 @@ pub fn try_add_gateway(
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
_try_add_gateway(
deps,
@@ -52,8 +52,7 @@ pub fn try_add_gateway_on_behalf(
.load(deps.storage)?
.params
.minimum_mixnode_pledge;
let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?;
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
let proxy = info.sender;
_try_add_gateway(
@@ -139,7 +138,6 @@ pub(crate) fn _try_remove_gateway(
proxy: Option<Addr>,
) -> Result<Response, ContractError> {
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
@@ -179,7 +177,7 @@ pub(crate) fn _try_remove_gateway(
amount: gateway_bond.pledge_amount(),
};
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?;
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
response = response.add_message(track_unbond_message);
}
@@ -194,11 +192,10 @@ pub(crate) fn _try_remove_gateway(
fn validate_gateway_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Uint128,
mix_denom: String,
) -> Result<Coin, ContractError> {
// check if anything was put as bond
if pledge.is_empty() {
return Err(ContractError::NoBondFound { mix_denom });
return Err(ContractError::NoBondFound);
}
if pledge.len() > 1 {
@@ -206,8 +203,8 @@ fn validate_gateway_pledge(
}
// check that the denomination is correct
if pledge[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
if pledge[0].denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom {});
}
// check that we have at least 100 coins in our pledge
@@ -228,8 +225,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};
@@ -241,7 +238,7 @@ pub mod tests {
// if we fail validation (by say not sending enough funds
let insufficient_bond = Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1;
let info = mock_info("anyone", &coins(insufficient_bond, TEST_COIN_DENOM));
let info = mock_info("anyone", &coins(insufficient_bond, MIX_DENOM.base));
let (msg, _) = tests::messages::valid_bond_gateway_msg("anyone");
// we are informed that we didn't send enough funds
@@ -547,26 +544,13 @@ pub mod tests {
#[test]
fn validating_gateway_bond() {
// you must send SOME funds
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()
})
);
let result = validate_gateway_pledge(Vec::new(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::NoBondFound));
// you must send at least 100 coins...
let mut bond = 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,
TEST_COIN_DENOM.to_string(),
);
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(
result,
Err(ContractError::InsufficientGatewayBond {
@@ -578,40 +562,18 @@ 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,
TEST_COIN_DENOM.to_string(),
);
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert!(result.is_ok());
// it must be sent in the defined denom!
let mut bond = tests::fixtures::good_gateway_pledge();
bond[0].denom = "baddenom".to_string();
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 result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
let mut bond = tests::fixtures::good_gateway_pledge();
bond[0].denom = "foomp".to_string();
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 result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
assert_eq!(result, Err(ContractError::WrongDenom {}));
}
}
-1
View File
@@ -9,6 +9,5 @@ mod gateways;
mod interval;
mod mixnet_contract_settings;
mod mixnodes;
mod queued_migrations;
mod rewards;
mod support;
@@ -9,7 +9,6 @@ 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,
}
@@ -45,7 +45,6 @@ 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(),
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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;
@@ -10,14 +11,10 @@ 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) -> StdResult<String> {
CONTRACT_STATE
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<String, ContractError> {
Ok(CONTRACT_STATE
.load(storage)
.map(|state| state.rewarding_validator_address.to_string())
}
pub fn mix_denom(storage: &dyn Storage) -> StdResult<String> {
CONTRACT_STATE.load(storage).map(|state| state.mix_denom)
.map(|state| state.rewarding_validator_address.to_string())?)
}
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
@@ -8,13 +8,6 @@ use mixnet_contract_common::{
IdentityKey, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse,
};
pub fn get_blacklisted_nodes(deps: Deps<'_>) -> Vec<IdentityKey> {
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,
@@ -259,13 +252,10 @@ 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().storage,
&api,
env,
deps.as_mut(),
mock_info("fred", &[]),
)
.unwrap();
+5 -11
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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::{
@@ -10,8 +11,6 @@ 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";
@@ -19,7 +18,6 @@ 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";
@@ -33,9 +31,6 @@ 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>,
@@ -176,7 +171,7 @@ pub(crate) fn read_full_mixnode_bond(
Ok(Some(MixNodeBond {
pledge_amount: stored_bond.pledge_amount,
total_delegation: Coin {
denom: mix_denom(storage)?,
denom: MIX_DENOM.base.to_owned(),
amount: total_delegation.unwrap_or_default(),
},
owner: stored_bond.owner,
@@ -195,8 +190,7 @@ mod tests {
use super::super::storage;
use super::*;
use crate::support::tests;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::init_contract;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::{coin, Addr, Uint128};
use mixnet_contract_common::{IdentityKey, MixNode};
@@ -217,7 +211,7 @@ mod tests {
#[test]
fn reading_mixnode_bond() {
let mut mock_storage = init_contract().storage;
let mut mock_storage = MockStorage::new();
let node_owner: Addr = Addr::unchecked("node-owner");
let node_identity: IdentityKey = "nodeidentity".into();
@@ -229,7 +223,7 @@ mod tests {
let pledge_value = 1000000000;
let mixnode_bond = StoredMixnodeBond {
pledge_amount: coin(pledge_value, TEST_COIN_DENOM),
pledge_amount: coin(pledge_value, MIX_DENOM.base),
owner: node_owner,
layer: Layer::One,
block_height: 12_345,

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