Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2e982a677 | |||
| 7b15f350cd | |||
| 2b4917b8b1 | |||
| 921e558660 | |||
| 0dabff72bd | |||
| fa354016e0 | |||
| 935ee765e9 | |||
| 4c8e59e6fc | |||
| 067f3e6f1a | |||
| 6f09d46dce | |||
| bdef48331b | |||
| 51a6936e51 | |||
| fd456d2952 | |||
| eee1abe593 | |||
| fffad43937 | |||
| 3a79f43a8d | |||
| 2e495f87ab | |||
| 57a9f18f5a | |||
| 0c6a0a9cae | |||
| c80d8d354a | |||
| 3f544dbc69 | |||
| d1e1f15db0 | |||
| 651c314182 | |||
| b957b939cf | |||
| a57545521d | |||
| da60606921 | |||
| 14f9bf7234 | |||
| c1fa92869a | |||
| c8533e3ec8 | |||
| 06c4dd601d | |||
| 4ff80bbab2 | |||
| d7220b1fec | |||
| d92df9ada3 | |||
| 9c19ae322d | |||
| 07893828d8 | |||
| 1167f50543 | |||
| ba1818a903 | |||
| e631219a73 | |||
| 207c6cf2c7 | |||
| c5ece97872 | |||
| 8a2c95d044 | |||
| ba5e3d4efa | |||
| c81623a61a | |||
| 8bb42c2b1b | |||
| 33e161bd59 | |||
| 0233499036 | |||
| a059a29173 | |||
| 83c3398570 | |||
| 93f931459a | |||
| 5a7b19aeb6 | |||
| b901655591 | |||
| a9fdbccb82 | |||
| 9ca3f69aa8 | |||
| c485934b06 | |||
| d62e13c932 |
@@ -1,6 +1,7 @@
|
||||
name: CI for Network Explorer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'explorer/**'
|
||||
@@ -75,3 +76,14 @@ jobs:
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- name: Deploy
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
|
||||
@@ -45,3 +45,4 @@ jobs:
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
|
||||
+29
-5
@@ -8,10 +8,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
### Added
|
||||
|
||||
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
|
||||
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427])
|
||||
- nym-connect: add ability to export gateway keys as JSON
|
||||
- nym-connect: add auto updater
|
||||
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
|
||||
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
|
||||
- explorer-api: add apy values to `mix_nodes` endpoint
|
||||
@@ -27,6 +23,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
|
||||
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
|
||||
- validator-client: add `denom` argument and add simple test for querying an account balance
|
||||
- gateway, validator-api: Checks for coconut credential double spending attempts, taking the coconut bandwidth contract as source of truth ([#1457])
|
||||
- coconut-bandwidth-contract: Record the state of a coconut credential; create specific proposal for releasing funds ([#1457])
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -37,10 +35,12 @@ 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])
|
||||
- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1496]).
|
||||
|
||||
### 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`.
|
||||
@@ -49,6 +49,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- gateway & mixnode: move detailed build info back to `--version` from `--help`.
|
||||
- socks5 client/websocket client: upgrade to latest clap and switched to declarative commandline parsing.
|
||||
- validator-api: fee payment for multisig operations comes from the gateway account instead of the validator APIs' accounts ([#1419])
|
||||
- multisig-contract: Limit the proposal creating functionality to one address (coconut-bandwidth-contract address) ([#1457])
|
||||
- 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
|
||||
@@ -69,6 +73,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1393]: https://github.com/nymtech/nym/pull/1393
|
||||
[#1404]: https://github.com/nymtech/nym/pull/1404
|
||||
[#1419]: https://github.com/nymtech/nym/pull/1419
|
||||
[#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
|
||||
[#1496]: https://github.com/nymtech/nym/pull/1496
|
||||
|
||||
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
|
||||
|
||||
### 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
+7
-6
@@ -644,6 +644,7 @@ name = "coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"multisig-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
@@ -927,7 +928,6 @@ dependencies = [
|
||||
"coconut-interface",
|
||||
"cosmrs",
|
||||
"crypto",
|
||||
"network-defaults",
|
||||
"rand 0.7.3",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -1590,6 +1590,7 @@ name = "explorer-api"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 3.2.8",
|
||||
"humantime-serde",
|
||||
"isocountry",
|
||||
"itertools",
|
||||
@@ -1605,6 +1606,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"validator-client",
|
||||
@@ -2870,7 +2872,6 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
@@ -2967,6 +2968,7 @@ name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"dotenv",
|
||||
"hex-literal",
|
||||
"once_cell",
|
||||
"serde",
|
||||
@@ -3064,7 +3066,6 @@ dependencies = [
|
||||
"credentials",
|
||||
"crypto",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
@@ -3231,7 +3232,6 @@ dependencies = [
|
||||
"credentials",
|
||||
"crypto",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
@@ -3298,6 +3298,8 @@ dependencies = [
|
||||
"credential-storage",
|
||||
"credentials",
|
||||
"crypto",
|
||||
"cw-utils",
|
||||
"cw3",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures",
|
||||
@@ -3323,6 +3325,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"task",
|
||||
"thiserror",
|
||||
"time 0.3.9",
|
||||
"tokio",
|
||||
@@ -5387,7 +5390,6 @@ version = "1.0.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -6335,7 +6337,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
name = "vesting-contract"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test: clippy-all cargo-test wasm fmt
|
||||
test-all: test cargo-test-expensive
|
||||
test: build clippy-all cargo-test wasm fmt
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
happy: fmt clippy-happy test
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::defaults::*;
|
||||
use config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
@@ -366,7 +365,7 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
disabled_credentials_mode: true,
|
||||
validator_api_urls: default_api_endpoints(),
|
||||
validator_api_urls: vec![],
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_encryption_key_file: Default::default(),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::error::Result;
|
||||
use crate::{MNEMONIC, NYMD_URL};
|
||||
use bip39::Mnemonic;
|
||||
use network_defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO};
|
||||
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
use validator_client::nymd;
|
||||
@@ -13,18 +13,23 @@ use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
|
||||
|
||||
pub(crate) struct Client {
|
||||
nymd_client: NymdClient<SigningNymdClient>,
|
||||
mix_denom_base: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new() -> Self {
|
||||
let nymd_url = Url::from_str(NYMD_URL).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
|
||||
let config = nymd::Config::try_from_nym_network_details(&DEFAULT_NETWORK.details())
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config = nymd::Config::try_from_nym_network_details(&network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
let nymd_client =
|
||||
NymdClient::connect_with_mnemonic(config, nymd_url.as_ref(), mnemonic, None).unwrap();
|
||||
|
||||
Client { nymd_client }
|
||||
Client {
|
||||
nymd_client,
|
||||
mix_denom_base: network_details.chain_details.mix_denom.base,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn deposit(
|
||||
@@ -34,7 +39,7 @@ impl Client {
|
||||
encryption_key: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<String> {
|
||||
let amount = Coin::new(amount as u128, MIX_DENOM.base.to_string());
|
||||
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
|
||||
Ok(self
|
||||
.nymd_client
|
||||
.deposit(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -22,7 +22,6 @@ url = "2.2"
|
||||
|
||||
clap = { version = "3.2.8", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
|
||||
log = "0.4" # self explanatory
|
||||
pretty_env_logger = "0.4" # for formatting log messages
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use clap::{Parser, Subcommand};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -28,7 +27,6 @@ fn long_version() -> String {
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
@@ -46,8 +44,6 @@ fn long_version() -> String {
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
@@ -113,6 +113,12 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
|
||||
let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR)
|
||||
.expect("api validator not set");
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if args.disable_socket {
|
||||
|
||||
@@ -82,7 +82,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
warn!("The native-client binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::config::{Config, MISSING_VALUE};
|
||||
|
||||
use config::{defaults::default_api_endpoints, NymConfig};
|
||||
use config::NymConfig;
|
||||
use version_checker::Version;
|
||||
|
||||
use clap::Args;
|
||||
@@ -105,15 +105,6 @@ fn minor_0_12_upgrade(
|
||||
|
||||
print_start_upgrade(&config_version, &to_version);
|
||||
|
||||
println!(
|
||||
"Setting validator API endpoints to {:?}",
|
||||
default_api_endpoints()
|
||||
);
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(default_api_endpoints());
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
@@ -9,11 +10,11 @@ pub mod websocket;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -13,7 +13,6 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
clap = { version = "3.2.8", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0"
|
||||
futures = "0.3"
|
||||
log = "0.4"
|
||||
pin-project = "1.0"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::client::config::Config;
|
||||
use clap::{Parser, Subcommand};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
|
||||
pub mod init;
|
||||
@@ -28,7 +27,6 @@ fn long_version() -> String {
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
@@ -46,8 +44,6 @@ fn long_version() -> String {
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
@@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
pub fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
raw_validator
|
||||
@@ -112,6 +112,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::API_VALIDATOR) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if let Some(port) = args.port {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::config::{Config, MISSING_VALUE};
|
||||
|
||||
use config::{defaults::default_api_endpoints, NymConfig};
|
||||
use config::NymConfig;
|
||||
use version_checker::Version;
|
||||
|
||||
use clap::Args;
|
||||
@@ -104,15 +104,6 @@ fn minor_0_12_upgrade(
|
||||
|
||||
print_start_upgrade(&config_version, &to_version);
|
||||
|
||||
println!(
|
||||
"Setting validator API endpoints to {:?}",
|
||||
default_api_endpoints()
|
||||
);
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(default_api_endpoints());
|
||||
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
@@ -9,11 +10,11 @@ pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
commands::execute(&args).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
||||
use socks5_requests::Response;
|
||||
use socks5_requests::Message;
|
||||
|
||||
pub(crate) struct MixnetResponseListener {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
@@ -44,12 +44,16 @@ impl MixnetResponseListener {
|
||||
warn!("this message had a surb - we didn't do anything with it");
|
||||
}
|
||||
|
||||
let response = match Response::try_from_bytes(&raw_message) {
|
||||
let response = match Message::try_from_bytes(&raw_message) {
|
||||
Err(err) => {
|
||||
warn!("failed to parse received response - {:?}", err);
|
||||
return;
|
||||
}
|
||||
Ok(data) => data,
|
||||
Ok(Message::Request(_)) => {
|
||||
warn!("unexpected request");
|
||||
return;
|
||||
}
|
||||
Ok(Message::Response(data)) => data,
|
||||
};
|
||||
|
||||
self.controller_sender
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::{validator_api, ValidatorClientError};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
|
||||
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use validator_api_requests::models::{
|
||||
@@ -29,7 +28,7 @@ use mixnet_contract_common::{
|
||||
RewardedSetUpdateDetails,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use network_defaults::{all::Network, NymNetworkDetails};
|
||||
use network_defaults::NymNetworkDetails;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -114,9 +113,6 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
// compatibility : (
|
||||
pub network: Network,
|
||||
|
||||
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
|
||||
// non-trivial amount of work and it's out of scope of the current branch
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
@@ -135,9 +131,6 @@ pub struct Client<C> {
|
||||
impl Client<SigningNymdClient> {
|
||||
pub fn new_signing(
|
||||
config: Config,
|
||||
// we need to provide network argument due to compatibility with other components (wallet...)
|
||||
// that rely on its existence...
|
||||
network: Network,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<Client<SigningNymdClient>, ValidatorClientError> {
|
||||
let validator_api_client = validator_api::Client::new(config.api_url.clone());
|
||||
@@ -149,7 +142,6 @@ impl Client<SigningNymdClient> {
|
||||
)?;
|
||||
|
||||
Ok(Client {
|
||||
network,
|
||||
mnemonic: Some(mnemonic),
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -177,18 +169,12 @@ impl Client<SigningNymdClient> {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl Client<QueryNymdClient> {
|
||||
pub fn new_query(
|
||||
config: Config,
|
||||
// we need to provide network argument due to compatibility with other components (wallet...)
|
||||
// that rely on its existence...
|
||||
network: Network,
|
||||
) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
|
||||
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
|
||||
let validator_api_client = validator_api::Client::new(config.api_url.clone());
|
||||
let nymd_client =
|
||||
NymdClient::connect(config.nymd_config.clone(), config.nymd_url.as_str())?;
|
||||
|
||||
Ok(Client {
|
||||
network,
|
||||
mnemonic: None,
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
@@ -747,14 +733,4 @@ impl ApiClient {
|
||||
.verify_bandwidth_credential(request_body)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn propose_release_funds(
|
||||
&self,
|
||||
request_body: &ProposeReleaseFundsRequestBody,
|
||||
) -> Result<ProposeReleaseFundsResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.validator_api
|
||||
.propose_release_funds(request_body)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,6 +1006,29 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[execute("mixnet")]
|
||||
fn _compound_reward(
|
||||
&self,
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
fee: Option<Fee>,
|
||||
) -> (ExecuteMsg, Option<Fee>)
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
(
|
||||
ExecuteMsg::CompoundReward {
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
},
|
||||
fee,
|
||||
)
|
||||
}
|
||||
|
||||
#[execute("mixnet")]
|
||||
fn _compound_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
|
||||
where
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::{CosmWasmClient, NymdClient};
|
||||
|
||||
use coconut_bandwidth_contract_common::msg::QueryMsg;
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait CoconutBandwidthQueryClient {
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
) -> Result<SpendCredentialResponse, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: CosmWasmClient + Sync + Send> CoconutBandwidthQueryClient for NymdClient<C> {
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
) -> Result<SpendCredentialResponse, NymdError> {
|
||||
let request = QueryMsg::GetSpentCredential {
|
||||
blinded_serial_number,
|
||||
};
|
||||
self.client
|
||||
.query_contract_smart(self.coconut_bandwidth_contract_address(), &request)
|
||||
.await
|
||||
}
|
||||
}
|
||||
+34
@@ -5,6 +5,7 @@ pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::{Coin, Fee, NymdClient};
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialData;
|
||||
use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -19,6 +20,13 @@ pub trait CoconutBandwidthSigningClient {
|
||||
encryption_key: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
async fn spend_credential(
|
||||
&self,
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
gateway_cosmos_address: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -46,4 +54,30 @@ impl<C: SigningCosmWasmClient + Sync + Send> CoconutBandwidthSigningClient for N
|
||||
)
|
||||
.await
|
||||
}
|
||||
async fn spend_credential(
|
||||
&self,
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
gateway_cosmos_address: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
let req = ExecuteMsg::SpendCredential {
|
||||
data: SpendCredentialData::new(
|
||||
funds.into(),
|
||||
blinded_serial_number,
|
||||
gateway_cosmos_address,
|
||||
),
|
||||
};
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.coconut_bandwidth_contract_address(),
|
||||
&req,
|
||||
fee,
|
||||
"CoconutBandwidth::SpendCredential",
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod coconut_bandwidth_query_client;
|
||||
mod coconut_bandwidth_signing_client;
|
||||
mod multisig_query_client;
|
||||
mod multisig_signing_client;
|
||||
mod vesting_query_client;
|
||||
mod vesting_signing_client;
|
||||
|
||||
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
|
||||
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
|
||||
pub use multisig_query_client::QueryClient;
|
||||
pub use multisig_query_client::MultisigQueryClient;
|
||||
pub use multisig_signing_client::MultisigSigningClient;
|
||||
pub use vesting_query_client::VestingQueryClient;
|
||||
pub use vesting_signing_client::VestingSigningClient;
|
||||
|
||||
@@ -9,12 +9,12 @@ use multisig_contract_common::msg::{ProposalResponse, QueryMsg};
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait QueryClient {
|
||||
pub trait MultisigQueryClient {
|
||||
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C: CosmWasmClient + Sync + Send> QueryClient for NymdClient<C> {
|
||||
impl<C: CosmWasmClient + Sync + Send> MultisigQueryClient for NymdClient<C> {
|
||||
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError> {
|
||||
let request = QueryMsg::Proposal { proposal_id };
|
||||
self.client
|
||||
|
||||
@@ -12,7 +12,6 @@ use multisig_contract_common::msg::ExecuteMsg;
|
||||
use async_trait::async_trait;
|
||||
use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg};
|
||||
use cw3::Vote;
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
|
||||
#[async_trait]
|
||||
pub trait MultisigSigningClient {
|
||||
@@ -49,7 +48,10 @@ impl<C: SigningCosmWasmClient + Sync + Send> MultisigSigningClient for NymdClien
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
|
||||
funds: Coin::new(voucher_value, DEFAULT_NETWORK.mix_denom().base),
|
||||
funds: Coin::new(
|
||||
voucher_value,
|
||||
self.config.chain_details.mix_denom.base.clone(),
|
||||
),
|
||||
};
|
||||
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: self.coconut_bandwidth_contract_address().to_string(),
|
||||
|
||||
@@ -207,35 +207,20 @@ mod tests {
|
||||
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
|
||||
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
|
||||
];
|
||||
let prefixes = vec![
|
||||
MAINNET.bech32_prefix(),
|
||||
SANDBOX.bech32_prefix(),
|
||||
QA.bech32_prefix(),
|
||||
];
|
||||
let prefix = MAINNET.bech32_prefix();
|
||||
|
||||
for prefix in prefixes {
|
||||
let addrs = match prefix.as_ref() {
|
||||
"nymt" => vec![
|
||||
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
|
||||
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
|
||||
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
|
||||
],
|
||||
"n" => vec![
|
||||
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
|
||||
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
],
|
||||
_ => panic!("Test needs to be updated with new bech32 prefix"),
|
||||
};
|
||||
for (idx, mnemonic) in mnemonics.iter().enumerate() {
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
addrs[idx].parse().unwrap()
|
||||
)
|
||||
}
|
||||
let addrs = vec![
|
||||
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
|
||||
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
];
|
||||
for (idx, mnemonic) in mnemonics.iter().enumerate() {
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
addrs[idx].parse().unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
|
||||
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use validator_api_requests::models::{
|
||||
@@ -405,23 +404,6 @@ impl Client {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn propose_release_funds(
|
||||
&self,
|
||||
request_body: &ProposeReleaseFundsRequestBody,
|
||||
) -> Result<ProposeReleaseFundsResponse, ValidatorAPIError> {
|
||||
self.post_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_PROPOSE_RELEASE_FUNDS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in validator API forever.
|
||||
|
||||
@@ -19,7 +19,6 @@ pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-creden
|
||||
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
|
||||
pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address";
|
||||
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
|
||||
pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds";
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
|
||||
@@ -9,3 +9,4 @@ edition = "2021"
|
||||
cosmwasm-std = "1.0.0"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
multisig-contract-common = { path = "../multisig-contract" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod deposit;
|
||||
pub mod events;
|
||||
pub mod msg;
|
||||
pub mod spend_credential;
|
||||
|
||||
@@ -5,24 +5,34 @@ use cosmwasm_std::Coin;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::deposit::DepositData;
|
||||
use crate::{deposit::DepositData, spend_credential::SpendCredentialData};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub multisig_addr: String,
|
||||
pub pool_addr: String,
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DepositFunds { data: DepositData },
|
||||
SpendCredential { data: SpendCredentialData },
|
||||
ReleaseFunds { funds: Coin },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {}
|
||||
pub enum QueryMsg {
|
||||
GetSpentCredential {
|
||||
blinded_serial_number: String,
|
||||
},
|
||||
GetAllSpentCredentials {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{from_binary, to_binary, Addr, Coin, CosmosMsg, StdResult, WasmMsg};
|
||||
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::msg::ExecuteMsg;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct SpendCredentialData {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
gateway_cosmos_address: String,
|
||||
}
|
||||
|
||||
impl SpendCredentialData {
|
||||
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: String) -> Self {
|
||||
SpendCredentialData {
|
||||
funds,
|
||||
blinded_serial_number,
|
||||
gateway_cosmos_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn funds(&self) -> &Coin {
|
||||
&self.funds
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> &str {
|
||||
&self.blinded_serial_number
|
||||
}
|
||||
|
||||
pub fn gateway_cosmos_address(&self) -> &str {
|
||||
&self.gateway_cosmos_address
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub enum SpendCredentialStatus {
|
||||
InProgress,
|
||||
Spent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredential {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
gateway_cosmos_address: Addr,
|
||||
status: SpendCredentialStatus,
|
||||
}
|
||||
|
||||
impl SpendCredential {
|
||||
pub fn new(funds: Coin, blinded_serial_number: String, gateway_cosmos_address: Addr) -> Self {
|
||||
SpendCredential {
|
||||
funds,
|
||||
blinded_serial_number,
|
||||
gateway_cosmos_address,
|
||||
status: SpendCredentialStatus::InProgress,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blinded_serial_number(&self) -> &str {
|
||||
&self.blinded_serial_number
|
||||
}
|
||||
|
||||
pub fn status(&self) -> SpendCredentialStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn mark_as_spent(&mut self) {
|
||||
self.status = SpendCredentialStatus::Spent;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct PagedSpendCredentialResponse {
|
||||
pub spend_credentials: Vec<SpendCredential>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<String>,
|
||||
}
|
||||
|
||||
impl PagedSpendCredentialResponse {
|
||||
pub fn new(
|
||||
spend_credentials: Vec<SpendCredential>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<String>,
|
||||
) -> Self {
|
||||
PagedSpendCredentialResponse {
|
||||
spend_credentials,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredentialResponse {
|
||||
pub spend_credential: Option<SpendCredential>,
|
||||
}
|
||||
|
||||
impl SpendCredentialResponse {
|
||||
pub fn new(spend_credential: Option<SpendCredential>) -> Self {
|
||||
SpendCredentialResponse { spend_credential }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_cosmos_msg(
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
coconut_bandwidth_addr: String,
|
||||
multisig_addr: String,
|
||||
) -> StdResult<CosmosMsg> {
|
||||
let release_funds_req = ExecuteMsg::ReleaseFunds { funds };
|
||||
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: coconut_bandwidth_addr,
|
||||
msg: to_binary(&release_funds_req)?,
|
||||
funds: vec![],
|
||||
});
|
||||
let req = MultisigExecuteMsg::Propose {
|
||||
title: String::from("Release funds, as ordered by Coconut Bandwidth Contract"),
|
||||
description: blinded_serial_number,
|
||||
msgs: vec![release_funds_msg],
|
||||
latest: None,
|
||||
};
|
||||
let msg = CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: multisig_addr,
|
||||
msg: to_binary(&req)?,
|
||||
funds: vec![],
|
||||
});
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
pub fn funds_from_cosmos_msgs(msgs: Vec<CosmosMsg>) -> Option<Coin> {
|
||||
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr: _,
|
||||
msg,
|
||||
funds: _,
|
||||
})) = msgs.get(0)
|
||||
{
|
||||
if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::<ExecuteMsg>(msg) {
|
||||
return Some(funds);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -14,7 +14,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
thiserror = "1.0"
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
fixed = { version = "1.1", features = ["serde"] }
|
||||
az = "1.1"
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub rewarding_validator_address: String,
|
||||
pub mixnet_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
@@ -32,6 +33,12 @@ pub enum ExecuteMsg {
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CompoundReward {
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -115,6 +122,7 @@ pub enum ExecuteMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetBlacklistedNodes {},
|
||||
GetCurrentOperatorCost {},
|
||||
GetRewardingValidatorAddress {},
|
||||
GetAllDelegationKeys {},
|
||||
@@ -205,6 +213,31 @@ pub enum QueryMsg {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
pub mixnet_denom: String,
|
||||
nodes_to_remove: Option<Vec<NodeToRemove>>,
|
||||
}
|
||||
|
||||
impl MigrateMsg {
|
||||
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
|
||||
self.nodes_to_remove.clone().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct NodeToRemove {
|
||||
owner: String,
|
||||
proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl NodeToRemove {
|
||||
pub fn owner(&self) -> &str {
|
||||
&self.owner
|
||||
}
|
||||
|
||||
pub fn proxy(&self) -> Option<&String> {
|
||||
self.proxy.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use cw_utils::{Duration, Expiration, Threshold};
|
||||
pub struct InstantiateMsg {
|
||||
// this is the group contract that contains the member list
|
||||
pub group_addr: String,
|
||||
pub coconut_bandwidth_contract_address: String,
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
}
|
||||
@@ -77,3 +78,8 @@ pub enum QueryMsg {
|
||||
limit: Option<u32>,
|
||||
},
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {
|
||||
pub coconut_bandwidth_address: String,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -10,8 +9,8 @@ pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
|
||||
pub mod events;
|
||||
pub mod messages;
|
||||
|
||||
pub fn one_ucoin() -> Coin {
|
||||
Coin::new(1, MIX_DENOM.base)
|
||||
pub fn one_ucoin(denom: String) -> Coin {
|
||||
Coin::new(1, denom)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
|
||||
@@ -7,11 +7,14 @@ use serde::{Deserialize, Serialize};
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct InitMsg {
|
||||
pub mixnet_contract_address: String,
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
|
||||
pub struct VestingSpecification {
|
||||
|
||||
@@ -14,7 +14,6 @@ url = "2.2"
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
coconut-interface = { path = "../coconut-interface" }
|
||||
crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] }
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
validator-api-requests = { path = "../../validator-api/validator-api-requests" }
|
||||
validator-client = { path = "../client-libs/validator-client" }
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use config::defaults::DEFAULT_NETWORK;
|
||||
use subtle_encoding::bech32;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -15,16 +14,15 @@ pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
|
||||
pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(), Bech32Error> {
|
||||
let prefix = try_bech32_decode(address)?;
|
||||
|
||||
if prefix == DEFAULT_NETWORK.bech32_prefix() {
|
||||
if prefix == bech32_prefix {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Bech32Error::WrongPrefix(format!(
|
||||
"your bech32 address prefix should be {}, not {}",
|
||||
DEFAULT_NETWORK.bech32_prefix(),
|
||||
prefix
|
||||
bech32_prefix, prefix
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -33,6 +31,8 @@ pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const TEST_BECH32_PREFIX: &str = "n";
|
||||
|
||||
mod decoding_bech32_addresses {
|
||||
use super::*;
|
||||
|
||||
@@ -71,9 +71,12 @@ mod tests {
|
||||
assert_eq!(
|
||||
Err(Bech32Error::WrongPrefix(format!(
|
||||
"your bech32 address prefix should be {}, not punk",
|
||||
DEFAULT_NETWORK.bech32_prefix()
|
||||
TEST_BECH32_PREFIX
|
||||
))),
|
||||
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
|
||||
validate_bech32_prefix(
|
||||
TEST_BECH32_PREFIX,
|
||||
"punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -82,6 +85,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
validate_bech32_prefix(
|
||||
TEST_BECH32_PREFIX,
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
dotenv = "0.15.0"
|
||||
hex-literal = "0.3.3"
|
||||
once_cell = "1.7.2"
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
fn main() {
|
||||
match option_env!("NETWORK") {
|
||||
None | Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
|
||||
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
|
||||
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
|
||||
_ => panic!("No such network"),
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{env::var, path::PathBuf};
|
||||
use url::Url;
|
||||
|
||||
pub mod all;
|
||||
@@ -10,36 +11,10 @@ pub mod eth_contract;
|
||||
pub mod mainnet;
|
||||
pub mod qa;
|
||||
pub mod sandbox;
|
||||
pub mod var_names;
|
||||
|
||||
// The set of defaults that are decided at compile time. Ideally we want to reduce these to a
|
||||
// minimum.
|
||||
// Keep DENOM around mostly for use in contracts. (TODO: consider moving it there, or renaming?)
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(network = "mainnet")] {
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::MAINNET;
|
||||
pub const MIX_DENOM: DenomDetails = mainnet::MIX_DENOM;
|
||||
pub const STAKE_DENOM: DenomDetails = mainnet::STAKE_DENOM;
|
||||
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
|
||||
} else if #[cfg(network = "qa")] {
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::QA;
|
||||
pub const MIX_DENOM: DenomDetails = qa::MIX_DENOM;
|
||||
pub const STAKE_DENOM: DenomDetails = qa::STAKE_DENOM;
|
||||
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
|
||||
} else if #[cfg(network = "sandbox")] {
|
||||
pub const DEFAULT_NETWORK: all::Network = all::Network::SANDBOX;
|
||||
pub const MIX_DENOM: DenomDetails = sandbox::MIX_DENOM;
|
||||
pub const STAKE_DENOM: DenomDetails = sandbox::STAKE_DENOM;
|
||||
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
}
|
||||
}
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
|
||||
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct ChainDetails {
|
||||
@@ -82,23 +57,56 @@ impl NymNetworkDetails {
|
||||
NymNetworkDetails::default()
|
||||
}
|
||||
|
||||
pub fn new_qa() -> Self {
|
||||
(&*QA_DEFAULTS).into()
|
||||
}
|
||||
|
||||
pub fn new_sandbox() -> Self {
|
||||
(&*SANDBOX_DEFAULTS).into()
|
||||
pub fn new_from_env() -> Self {
|
||||
NymNetworkDetails::new()
|
||||
.with_bech32_account_prefix(
|
||||
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
|
||||
)
|
||||
.with_mix_denom(DenomDetailsOwned {
|
||||
base: var(var_names::MIX_DENOM).expect("mix denomination base not set"),
|
||||
display: var(var_names::MIX_DENOM_DISPLAY)
|
||||
.expect("mix denomination display not set"),
|
||||
display_exponent: var(var_names::DENOMS_EXPONENT)
|
||||
.expect("denomination exponent not set")
|
||||
.parse()
|
||||
.expect("denomination exponent is not u32"),
|
||||
})
|
||||
.with_stake_denom(DenomDetailsOwned {
|
||||
base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"),
|
||||
display: var(var_names::STAKE_DENOM_DISPLAY)
|
||||
.expect("stake denomination display not set"),
|
||||
display_exponent: var(var_names::DENOMS_EXPONENT)
|
||||
.expect("denomination exponent not set")
|
||||
.parse()
|
||||
.expect("denomination exponent is not u32"),
|
||||
})
|
||||
.with_validator_endpoint(ValidatorDetails::new(
|
||||
var(var_names::NYMD_VALIDATOR).expect("nymd validator not set"),
|
||||
Some(var(var_names::API_VALIDATOR).expect("api validator not set")),
|
||||
))
|
||||
.with_mixnet_contract(Some(
|
||||
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
|
||||
))
|
||||
.with_vesting_contract(Some(
|
||||
var(var_names::VESTING_CONTRACT_ADDRESS).expect("vesting contract not set"),
|
||||
))
|
||||
.with_bandwidth_claim_contract(Some(
|
||||
var(var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS)
|
||||
.expect("bandwidth claim contract not set"),
|
||||
))
|
||||
.with_coconut_bandwidth_contract(Some(
|
||||
var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("coconut bandwidth contract not set"),
|
||||
))
|
||||
.with_multisig_contract(Some(
|
||||
var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
(&*MAINNET_DEFAULTS).into()
|
||||
}
|
||||
|
||||
pub fn current_default() -> Self {
|
||||
// backwards compatibility reasons
|
||||
DEFAULT_NETWORK.details()
|
||||
}
|
||||
|
||||
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
|
||||
self.chain_details.bech32_account_prefix = prefix.into();
|
||||
self
|
||||
@@ -333,27 +341,21 @@ impl ValidatorDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_statistics_service_url() -> Url {
|
||||
DEFAULT_NETWORK
|
||||
.statistics_service_url()
|
||||
.parse()
|
||||
.expect("the provided statistics service url is invalid!")
|
||||
}
|
||||
|
||||
pub fn default_nymd_endpoints() -> Vec<Url> {
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.iter()
|
||||
.map(ValidatorDetails::nymd_url)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn default_api_endpoints() -> Vec<Url> {
|
||||
DEFAULT_NETWORK
|
||||
.validators()
|
||||
.iter()
|
||||
.filter_map(ValidatorDetails::api_url)
|
||||
.collect()
|
||||
pub fn setup_env(config_env_file: Option<PathBuf>) {
|
||||
match std::env::var(var_names::CONFIGURED) {
|
||||
// if the configuration is not already set in the env vars
|
||||
Err(std::env::VarError::NotPresent) => {
|
||||
if let Some(config_env_file) = config_env_file {
|
||||
dotenv::from_path(config_env_file)
|
||||
.expect("Invalid path to environment configuration file");
|
||||
} else {
|
||||
// if nothing is set, the use mainnet defaults
|
||||
crate::mainnet::export_to_env();
|
||||
}
|
||||
}
|
||||
Err(_) => crate::mainnet::export_to_env(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Name of the event triggered by the eth contract. If the event name is changed,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::var_names;
|
||||
use crate::{DenomDetails, ValidatorDetails};
|
||||
|
||||
pub(crate) const BECH32_PREFIX: &str = "n";
|
||||
@@ -24,9 +25,48 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
|
||||
pub const NYMD_VALIDATOR: &str = "https://rpc.nyx.nodes.guru/";
|
||||
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://rpc.nyx.nodes.guru/",
|
||||
Some("https://validator.nymtech.net/api/"),
|
||||
)]
|
||||
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
|
||||
}
|
||||
|
||||
pub fn export_to_env() {
|
||||
std::env::set_var(var_names::CONFIGURED, "true");
|
||||
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
|
||||
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
|
||||
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
|
||||
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
|
||||
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
|
||||
std::env::set_var(
|
||||
var_names::DENOMS_EXPONENT,
|
||||
STAKE_DENOM.display_exponent.to_string(),
|
||||
);
|
||||
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
|
||||
std::env::set_var(
|
||||
var_names::VESTING_CONTRACT_ADDRESS,
|
||||
VESTING_CONTRACT_ADDRESS,
|
||||
);
|
||||
std::env::set_var(
|
||||
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
|
||||
);
|
||||
std::env::set_var(
|
||||
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
|
||||
);
|
||||
std::env::set_var(
|
||||
var_names::MULTISIG_CONTRACT_ADDRESS,
|
||||
MULTISIG_CONTRACT_ADDRESS,
|
||||
);
|
||||
std::env::set_var(
|
||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||
REWARDING_VALIDATOR_ADDRESS,
|
||||
);
|
||||
std::env::set_var(
|
||||
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
);
|
||||
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
|
||||
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Environment variable that, if set, shows the environment is currently configured
|
||||
pub const CONFIGURED: &str = "CONFIGURED";
|
||||
|
||||
pub const BECH32_PREFIX: &str = "BECH32_PREFIX";
|
||||
pub const MIX_DENOM: &str = "MIX_DENOM";
|
||||
pub const MIX_DENOM_DISPLAY: &str = "MIX_DENOM_DISPLAY";
|
||||
pub const STAKE_DENOM: &str = "STAKE_DENOM";
|
||||
pub const STAKE_DENOM_DISPLAY: &str = "STAKE_DENOM_DISPLAY";
|
||||
pub const DENOMS_EXPONENT: &str = "DENOMS_EXPONENT";
|
||||
pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS";
|
||||
pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
|
||||
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
|
||||
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
|
||||
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
|
||||
pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR";
|
||||
pub const API_VALIDATOR: &str = "API_VALIDATOR";
|
||||
@@ -34,8 +34,7 @@ mod error;
|
||||
mod impls;
|
||||
mod proofs;
|
||||
mod scheme;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod tests;
|
||||
mod traits;
|
||||
mod utils;
|
||||
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
use crate::{
|
||||
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
|
||||
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
|
||||
SignatureShare, VerificationKey,
|
||||
aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen,
|
||||
verify_credential, CoconutError, VerificationKey,
|
||||
};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[test]
|
||||
fn main() -> Result<(), CoconutError> {
|
||||
let params = setup(5)?;
|
||||
|
||||
let public_attributes = params.n_random_scalars(2);
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
// generate commitment
|
||||
let (commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(¶ms, &private_attributes, &public_attributes)?;
|
||||
|
||||
// generate_keys
|
||||
let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?;
|
||||
@@ -30,58 +20,8 @@ fn main() -> Result<(), CoconutError> {
|
||||
// aggregate verification keys
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
|
||||
|
||||
// generate blinded signatures
|
||||
let mut blinded_signatures = Vec::new();
|
||||
|
||||
for keypair in coconut_keypairs {
|
||||
let blinded_signature = blind_sign(
|
||||
¶ms,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
&public_attributes,
|
||||
)?;
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
// Unblind
|
||||
let unblinded_signatures: Vec<Signature> =
|
||||
izip!(blinded_signatures.iter(), verification_keys.iter())
|
||||
.map(|(s, vk)| {
|
||||
s.unblind(
|
||||
¶ms,
|
||||
vk,
|
||||
&private_attributes,
|
||||
&public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Aggregate signatures
|
||||
let signature_shares: Vec<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(¶ms, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
¶ms,
|
||||
&verification_key,
|
||||
&signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)?;
|
||||
let theta = theta_from_keys_and_attributes(¶ms, &coconut_keypairs, &public_attributes)?;
|
||||
|
||||
// Verify credentials
|
||||
assert!(verify_credential(
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::*;
|
||||
use itertools::izip;
|
||||
|
||||
pub fn theta_from_keys_and_attributes(
|
||||
params: &Parameters,
|
||||
coconut_keypairs: &Vec<KeyPair>,
|
||||
public_attributes: &Vec<PublicAttribute>,
|
||||
) -> Result<Theta, CoconutError> {
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![serial_number, binding_number];
|
||||
|
||||
// generate commitment
|
||||
let (commitments_openings, blind_sign_request) =
|
||||
prepare_blind_sign(params, &private_attributes, public_attributes)?;
|
||||
|
||||
let verification_keys: Vec<VerificationKey> = coconut_keypairs
|
||||
.iter()
|
||||
.map(|keypair| keypair.verification_key())
|
||||
.collect();
|
||||
|
||||
// aggregate verification keys
|
||||
let indices: Vec<u64> = coconut_keypairs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, _)| (idx + 1) as u64)
|
||||
.collect();
|
||||
let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?;
|
||||
|
||||
// generate blinded signatures
|
||||
let mut blinded_signatures = Vec::new();
|
||||
|
||||
for keypair in coconut_keypairs {
|
||||
let blinded_signature = blind_sign(
|
||||
params,
|
||||
&keypair.secret_key(),
|
||||
&blind_sign_request,
|
||||
public_attributes,
|
||||
)?;
|
||||
blinded_signatures.push(blinded_signature)
|
||||
}
|
||||
|
||||
// Unblind
|
||||
let unblinded_signatures: Vec<Signature> =
|
||||
izip!(blinded_signatures.iter(), verification_keys.iter())
|
||||
.map(|(s, vk)| {
|
||||
s.unblind(
|
||||
params,
|
||||
vk,
|
||||
&private_attributes,
|
||||
public_attributes,
|
||||
&blind_sign_request.get_commitment_hash(),
|
||||
&commitments_openings,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Aggregate signatures
|
||||
let signature_shares: Vec<SignatureShare> = unblinded_signatures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
|
||||
.collect();
|
||||
|
||||
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
|
||||
attributes.extend_from_slice(&private_attributes);
|
||||
attributes.extend_from_slice(public_attributes);
|
||||
|
||||
// Randomize credentials and generate any cryptographic material to verify them
|
||||
let signature =
|
||||
aggregate_signature_shares(params, &verification_key, &attributes, &signature_shares)?;
|
||||
|
||||
// Generate cryptographic material to verify them
|
||||
let theta = prove_bandwidth_credential(
|
||||
params,
|
||||
&verification_key,
|
||||
&signature,
|
||||
serial_number,
|
||||
binding_number,
|
||||
)?;
|
||||
|
||||
Ok(theta)
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
#[cfg(test)]
|
||||
mod e2e;
|
||||
pub mod helpers;
|
||||
|
||||
@@ -17,5 +17,3 @@ serde_json = "1"
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
|
||||
thiserror = "1"
|
||||
tokio = { version = "1.19.1", features = [ "time" ] }
|
||||
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -34,12 +34,16 @@ pub enum StatsData {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct StatsGatewayData {
|
||||
pub gateway_id: String,
|
||||
pub inbox_count: u32,
|
||||
}
|
||||
|
||||
impl StatsGatewayData {
|
||||
pub fn new(inbox_count: u32) -> Self {
|
||||
StatsGatewayData { inbox_count }
|
||||
pub fn new(gateway_id: String, inbox_count: u32) -> Self {
|
||||
StatsGatewayData {
|
||||
gateway_id,
|
||||
inbox_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -507,7 +507,7 @@ mod test {
|
||||
|
||||
for (expected, raw_display) in values {
|
||||
let coin = DecCoin {
|
||||
denom: Network::MAINNET.mix_denom().display.into(),
|
||||
denom: Network::MAINNET.mix_denom().display,
|
||||
amount: raw_display.parse().unwrap(),
|
||||
};
|
||||
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
|
||||
|
||||
Generated
+34
-9
@@ -50,7 +50,6 @@ name = "bandwidth-claim"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"schemars",
|
||||
@@ -221,12 +220,11 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"coconut-bandwidth-contract-common",
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"multisig-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
@@ -237,10 +235,32 @@ name = "coconut-bandwidth-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"multisig-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coconut-test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"coconut-bandwidth",
|
||||
"coconut-bandwidth-contract-common",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw3-flex-multisig",
|
||||
"cw4-group",
|
||||
"multisig-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
@@ -592,6 +612,12 @@ dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
@@ -1056,7 +1082,6 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
@@ -1082,6 +1107,7 @@ name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dotenv",
|
||||
"hex-literal",
|
||||
"once_cell",
|
||||
"serde",
|
||||
@@ -1366,18 +1392,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
@@ -1828,7 +1854,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
name = "vesting-contract"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
|
||||
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
||||
@@ -9,8 +9,6 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dev-dependencies]
|
||||
config = { path = "../../common/config"}
|
||||
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
|
||||
|
||||
@@ -62,10 +62,11 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use bandwidth_claim_contract::payment::PagedPaymentResponse;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, from_binary};
|
||||
|
||||
const TEST_MIX_DENOM: &str = "unym";
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
let mut deps = mock_dependencies();
|
||||
@@ -91,11 +92,11 @@ pub mod tests {
|
||||
|
||||
// Contract balance should match what we initialized it as
|
||||
assert_eq!(
|
||||
coins(0, MIX_DENOM.base),
|
||||
coins(0, TEST_MIX_DENOM),
|
||||
vec![deps
|
||||
.as_ref()
|
||||
.querier
|
||||
.query_balance(env.contract.address, MIX_DENOM.base)
|
||||
.query_balance(env.contract.address, TEST_MIX_DENOM)
|
||||
.unwrap()]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
@@ -21,6 +21,3 @@ cw-controllers = "0.13.4"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { version = "0.13.2" }
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
|
||||
};
|
||||
|
||||
use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::queries::{query_all_spent_credentials_paged, query_spent_credential};
|
||||
use crate::state::{Config, ADMIN, CONFIG};
|
||||
use crate::transactions;
|
||||
|
||||
@@ -22,12 +25,14 @@ pub fn instantiate(
|
||||
) -> Result<Response, ContractError> {
|
||||
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
|
||||
let pool_addr = deps.api.addr_validate(&msg.pool_addr)?;
|
||||
let mix_denom = msg.mix_denom;
|
||||
|
||||
ADMIN.set(deps.branch(), Some(multisig_addr.clone()))?;
|
||||
|
||||
let cfg = Config {
|
||||
multisig_addr,
|
||||
pool_addr,
|
||||
mix_denom,
|
||||
};
|
||||
CONFIG.save(deps.storage, &cfg)?;
|
||||
|
||||
@@ -44,13 +49,23 @@ pub fn execute(
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
|
||||
ExecuteMsg::SpendCredential { data } => {
|
||||
transactions::spend_credential(deps, env, info, data)
|
||||
}
|
||||
ExecuteMsg::ReleaseFunds { funds } => transactions::release_funds(deps, env, info, funds),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult<Binary> {
|
||||
unimplemented!();
|
||||
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||
match msg {
|
||||
QueryMsg::GetAllSpentCredentials { limit, start_after } => to_binary(
|
||||
&query_all_spent_credentials_paged(deps, start_after, limit)?,
|
||||
),
|
||||
QueryMsg::GetSpentCredential {
|
||||
blinded_serial_number,
|
||||
} => to_binary(&query_spent_credential(deps, blinded_serial_number)?),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
@@ -61,12 +76,10 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::TEST_MIX_DENOM;
|
||||
use crate::support::tests::helpers::*;
|
||||
use coconut_bandwidth_contract_common::deposit::DepositData;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::coins;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, Addr};
|
||||
use cw_multi_test::Executor;
|
||||
|
||||
#[test]
|
||||
fn initialize_contract() {
|
||||
@@ -75,6 +88,7 @@ mod tests {
|
||||
let msg = InstantiateMsg {
|
||||
multisig_addr: String::from(MULTISIG_CONTRACT),
|
||||
pool_addr: String::from(POOL_CONTRACT),
|
||||
mix_denom: TEST_MIX_DENOM.to_string(),
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
@@ -83,80 +97,12 @@ mod tests {
|
||||
|
||||
// Contract balance should be 0
|
||||
assert_eq!(
|
||||
coins(0, MIX_DENOM.base),
|
||||
coins(0, TEST_MIX_DENOM),
|
||||
vec![deps
|
||||
.as_ref()
|
||||
.querier
|
||||
.query_balance(env.contract.address, MIX_DENOM.base)
|
||||
.query_balance(env.contract.address, TEST_MIX_DENOM)
|
||||
.unwrap()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deposit_and_release() {
|
||||
let init_funds = coins(10, MIX_DENOM.base);
|
||||
let deposit_funds = coins(1, MIX_DENOM.base);
|
||||
let release_funds = coins(2, MIX_DENOM.base);
|
||||
let mut app = mock_app(&init_funds);
|
||||
let multisig_addr = String::from(MULTISIG_CONTRACT);
|
||||
let pool_addr = String::from(POOL_CONTRACT);
|
||||
|
||||
let code_id = app.store_code(contract_bandwidth());
|
||||
let msg = InstantiateMsg {
|
||||
multisig_addr: multisig_addr.clone(),
|
||||
pool_addr: pool_addr.clone(),
|
||||
};
|
||||
let contract_addr = app
|
||||
.instantiate_contract(
|
||||
code_id,
|
||||
Addr::unchecked(OWNER),
|
||||
&msg,
|
||||
&[],
|
||||
"bandwidth",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = ExecuteMsg::DepositFunds {
|
||||
data: DepositData::new(
|
||||
String::from("info"),
|
||||
String::from("id"),
|
||||
String::from("enc"),
|
||||
),
|
||||
};
|
||||
app.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&deposit_funds,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// try to release more then it's in the contract
|
||||
let msg = ExecuteMsg::ReleaseFunds {
|
||||
funds: release_funds[0].clone(),
|
||||
};
|
||||
let err = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(multisig_addr.clone()),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
|
||||
|
||||
let msg = ExecuteMsg::ReleaseFunds {
|
||||
funds: deposit_funds[0].clone(),
|
||||
};
|
||||
app.execute_contract(
|
||||
Addr::unchecked(multisig_addr),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let pool_bal = app.wrap().query_balance(pool_addr, MIX_DENOM.base).unwrap();
|
||||
assert_eq!(pool_bal, deposit_funds[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use cosmwasm_std::StdError;
|
||||
use cw_controllers::AdminError;
|
||||
use thiserror::Error;
|
||||
|
||||
use config::defaults::MIX_DENOM;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
///
|
||||
/// Add any other custom errors you like here.
|
||||
@@ -22,12 +20,15 @@ pub enum ContractError {
|
||||
#[error("No coin was sent for voucher")]
|
||||
NoCoin,
|
||||
|
||||
#[error("Wrong coin denomination, you must send {}", MIX_DENOM.base)]
|
||||
WrongDenom,
|
||||
#[error("Wrong coin denomination, you must send {mix_denom}")]
|
||||
WrongDenom { mix_denom: String },
|
||||
|
||||
#[error("There aren't enough funds in the contract")]
|
||||
NotEnoughFunds,
|
||||
|
||||
#[error("Credential already spent or in process of spending")]
|
||||
DuplicateBlindedSerialNumber,
|
||||
|
||||
#[error("{0}")]
|
||||
Admin(#[from] AdminError),
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod contract;
|
||||
mod error;
|
||||
pub mod error;
|
||||
mod queries;
|
||||
mod state;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod transactions;
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_bandwidth_contract_common::spend_credential::{
|
||||
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
|
||||
};
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
|
||||
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
|
||||
|
||||
pub(crate) fn query_all_spent_credentials_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedSpendCredentialResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT)
|
||||
.min(SPEND_CREDENTIAL_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let start = start_after.as_deref().map(Bound::exclusive);
|
||||
|
||||
let nodes = storage::spent_credentials()
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<SpendCredential>>>()?;
|
||||
|
||||
let start_next_after = nodes
|
||||
.last()
|
||||
.map(|spend_credential| spend_credential.blinded_serial_number().to_string());
|
||||
|
||||
Ok(PagedSpendCredentialResponse::new(
|
||||
nodes,
|
||||
limit,
|
||||
start_next_after,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn query_spent_credential(
|
||||
deps: Deps<'_>,
|
||||
blinded_serial_number: String,
|
||||
) -> StdResult<SpendCredentialResponse> {
|
||||
let spend_credential =
|
||||
storage::spent_credentials().may_load(deps.storage, &blinded_serial_number)?;
|
||||
Ok(SpendCredentialResponse::new(spend_credential))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::fixtures::spend_credential_data_fixture;
|
||||
use crate::support::tests::helpers::init_contract;
|
||||
use crate::transactions::spend_credential;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
|
||||
#[test]
|
||||
fn spent_credentials_empty_on_init() {
|
||||
let deps = init_contract();
|
||||
let response =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(2)).unwrap();
|
||||
assert_eq!(0, response.spend_credentials.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spent_credentials_paged_retrieval_obeys_limits() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
let limit = 2;
|
||||
for n in 0..1000 {
|
||||
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
}
|
||||
|
||||
let page1 =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
|
||||
assert_eq!(limit, page1.spend_credentials.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spent_credentials_paged_retrieval_has_default_limit() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
for n in 0..1000 {
|
||||
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
}
|
||||
|
||||
// query without explicitly setting a limit
|
||||
let page1 = query_all_spent_credentials_paged(deps.as_ref(), None, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT,
|
||||
page1.spend_credentials.len() as u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spent_credentials_paged_retrieval_has_max_limit() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
for n in 0..1000 {
|
||||
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
}
|
||||
|
||||
// query with a crazily high limit in an attempt to use too many resources
|
||||
let crazy_limit = 1000 * SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
|
||||
let page1 =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(crazy_limit))
|
||||
.unwrap();
|
||||
|
||||
// we default to a decent sized upper bound instead
|
||||
let expected_limit = SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
|
||||
assert_eq!(expected_limit, page1.spend_credentials.len() as u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spent_credentials_pagination_works() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
|
||||
let data = spend_credential_data_fixture("blinded_serial_number1");
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
|
||||
let per_page = 2;
|
||||
let page1 =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
|
||||
// page should have 1 result on it
|
||||
assert_eq!(1, page1.spend_credentials.len());
|
||||
|
||||
// save another
|
||||
let data = spend_credential_data_fixture("blinded_serial_number2");
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
|
||||
// page1 should have 2 results on it
|
||||
let page1 =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.spend_credentials.len());
|
||||
|
||||
let data = spend_credential_data_fixture("blinded_serial_number3");
|
||||
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
|
||||
|
||||
// page1 still has 2 results
|
||||
let page1 =
|
||||
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
|
||||
assert_eq!(2, page1.spend_credentials.len());
|
||||
|
||||
// retrieving the next page should start after the last key on this page
|
||||
let start_after = page1.start_next_after.unwrap();
|
||||
let page2 = query_all_spent_credentials_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after.clone()),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, page2.spend_credentials.len());
|
||||
|
||||
let data = spend_credential_data_fixture("blinded_serial_number4");
|
||||
spend_credential(deps.as_mut(), env, info, data).unwrap();
|
||||
|
||||
let page2 = query_all_spent_credentials_paged(
|
||||
deps.as_ref(),
|
||||
Option::from(start_after),
|
||||
Option::from(per_page),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// now we have 2 pages, with 2 results on the second page
|
||||
assert_eq!(2, page2.spend_credentials.len());
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ pub const ADMIN: Admin = Admin::new("admin");
|
||||
pub struct Config {
|
||||
pub multisig_addr: Addr,
|
||||
pub pool_addr: Addr,
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
pub const CONFIG: Item<Config> = Item::new("config");
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredential;
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
|
||||
|
||||
// storage prefixes
|
||||
const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc";
|
||||
const SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE: &str = "scn";
|
||||
|
||||
// paged retrieval limits for all queries and transactions
|
||||
pub(crate) const SPEND_CREDENTIAL_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
pub(crate) struct SpendCredentialIndex<'a> {
|
||||
pub(crate) blinded_serial_number: UniqueIndex<'a, String, SpendCredential>,
|
||||
}
|
||||
|
||||
// IndexList is just boilerplate code for fetching a struct's indexes
|
||||
// note that from my understanding this will be converted into a macro at some point in the future
|
||||
impl<'a> IndexList<SpendCredential> for SpendCredentialIndex<'a> {
|
||||
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<SpendCredential>> + '_> {
|
||||
let v: Vec<&dyn Index<SpendCredential>> = vec![&self.blinded_serial_number];
|
||||
Box::new(v.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
// gateways() is the storage access function.
|
||||
pub(crate) fn spent_credentials<'a>(
|
||||
) -> IndexedMap<'a, &'a str, SpendCredential, SpendCredentialIndex<'a>> {
|
||||
let indexes = SpendCredentialIndex {
|
||||
blinded_serial_number: UniqueIndex::new(
|
||||
|d| d.blinded_serial_number().to_string(),
|
||||
SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE,
|
||||
),
|
||||
};
|
||||
IndexedMap::new(SPEND_CREDENTIAL_PK_NAMESPACE, indexes)
|
||||
}
|
||||
|
||||
// currently not used outside tests
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::storage;
|
||||
use crate::storage::SpendCredential;
|
||||
use crate::support::tests::fixtures;
|
||||
use crate::support::tests::fixtures::TEST_MIX_DENOM;
|
||||
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus;
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
|
||||
#[test]
|
||||
fn spend_credential_single_read_retrieval() {
|
||||
let mut storage = MockStorage::new();
|
||||
let blind_serial_number1 = "number1";
|
||||
let blind_serial_number2 = "number2";
|
||||
let spend1 = fixtures::spend_credential_fixture(blind_serial_number1);
|
||||
let spend2 = fixtures::spend_credential_fixture(blind_serial_number2);
|
||||
storage::spent_credentials()
|
||||
.save(&mut storage, blind_serial_number1, &spend1)
|
||||
.unwrap();
|
||||
storage::spent_credentials()
|
||||
.save(&mut storage, blind_serial_number2, &spend2)
|
||||
.unwrap();
|
||||
|
||||
let res1 = storage::spent_credentials()
|
||||
.load(&storage, blind_serial_number1)
|
||||
.unwrap();
|
||||
let res2 = storage::spent_credentials()
|
||||
.load(&storage, blind_serial_number2)
|
||||
.unwrap();
|
||||
assert_eq!(spend1, res1);
|
||||
assert_eq!(spend2, res2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_as_spent_credential() {
|
||||
let mut mock_storage = MockStorage::new();
|
||||
let funds = Coin::new(100, TEST_MIX_DENOM);
|
||||
let blind_serial_number = "blind_serial_number";
|
||||
let gateway_cosmos_address: Addr = Addr::unchecked("gateway_cosmos_address");
|
||||
|
||||
let res = storage::spent_credentials()
|
||||
.may_load(&mock_storage, blind_serial_number)
|
||||
.unwrap();
|
||||
assert!(res.is_none());
|
||||
|
||||
let mut spend_credential = SpendCredential::new(
|
||||
funds.clone(),
|
||||
blind_serial_number.to_string(),
|
||||
gateway_cosmos_address.clone(),
|
||||
);
|
||||
spend_credential.mark_as_spent();
|
||||
|
||||
storage::spent_credentials()
|
||||
.save(&mut mock_storage, blind_serial_number, &spend_credential)
|
||||
.unwrap();
|
||||
|
||||
let ret = storage::spent_credentials()
|
||||
.load(&mock_storage, blind_serial_number)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(ret, spend_credential);
|
||||
assert_eq!(ret.status(), SpendCredentialStatus::Spent);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod helpers {
|
||||
pub const OWNER: &str = "admin0001";
|
||||
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
|
||||
pub const POOL_CONTRACT: &str = "mix pool contract address";
|
||||
|
||||
use crate::contract::instantiate;
|
||||
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{Addr, Coin, Empty, MemoryStorage, OwnedDeps};
|
||||
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
multisig_addr: String::from(MULTISIG_CONTRACT),
|
||||
pool_addr: String::from(POOL_CONTRACT),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
deps
|
||||
}
|
||||
|
||||
pub fn mock_app(init_funds: &[Coin]) -> App {
|
||||
AppBuilder::new().build(|router, _, storage| {
|
||||
router
|
||||
.bank
|
||||
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
|
||||
let contract = ContractWrapper::new(
|
||||
crate::contract::execute,
|
||||
crate::contract::instantiate,
|
||||
crate::contract::query,
|
||||
);
|
||||
Box::new(contract)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData};
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
|
||||
pub const TEST_MIX_DENOM: &str = "unym";
|
||||
|
||||
pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential {
|
||||
SpendCredential::new(
|
||||
Coin::new(100, TEST_MIX_DENOM),
|
||||
blinded_serial_number.to_string(),
|
||||
Addr::unchecked("gateway_owner_addr"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData {
|
||||
SpendCredentialData::new(
|
||||
Coin::new(100, TEST_MIX_DENOM),
|
||||
blinded_serial_number.to_string(),
|
||||
"gateway_owner_addr".to_string(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
|
||||
pub const POOL_CONTRACT: &str = "mix pool contract address";
|
||||
|
||||
use crate::contract::instantiate;
|
||||
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
|
||||
|
||||
use super::fixtures::TEST_MIX_DENOM;
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
multisig_addr: String::from(MULTISIG_CONTRACT),
|
||||
pool_addr: String::from(POOL_CONTRACT),
|
||||
mix_denom: TEST_MIX_DENOM.to_string(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
deps
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod fixtures;
|
||||
pub mod helpers;
|
||||
@@ -1,20 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_bandwidth_contract_common::spend_credential::{
|
||||
to_cosmos_msg, SpendCredential, SpendCredentialData,
|
||||
};
|
||||
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::state::{ADMIN, CONFIG};
|
||||
use crate::storage;
|
||||
|
||||
use coconut_bandwidth_contract_common::deposit::DepositData;
|
||||
use coconut_bandwidth_contract_common::events::{
|
||||
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
|
||||
DEPOSIT_VALUE,
|
||||
};
|
||||
use config::defaults::MIX_DENOM;
|
||||
|
||||
pub(crate) fn deposit_funds(
|
||||
_deps: DepsMut<'_>,
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
info: MessageInfo,
|
||||
data: DepositData,
|
||||
@@ -25,8 +28,9 @@ pub(crate) fn deposit_funds(
|
||||
if info.funds.len() > 1 {
|
||||
return Err(ContractError::MultipleDenoms);
|
||||
}
|
||||
if info.funds[0].denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom);
|
||||
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
|
||||
if info.funds[0].denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
|
||||
let voucher_value = info.funds.last().unwrap();
|
||||
@@ -39,18 +43,55 @@ pub(crate) fn deposit_funds(
|
||||
Ok(Response::new().add_event(event))
|
||||
}
|
||||
|
||||
pub(crate) fn spend_credential(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
_info: MessageInfo,
|
||||
data: SpendCredentialData,
|
||||
) -> Result<Response, ContractError> {
|
||||
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
|
||||
if data.funds().denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
if storage::spent_credentials().has(deps.storage, data.blinded_serial_number()) {
|
||||
return Err(ContractError::DuplicateBlindedSerialNumber);
|
||||
}
|
||||
let cfg = CONFIG.load(deps.storage)?;
|
||||
|
||||
let gateway_cosmos_address = deps.api.addr_validate(data.gateway_cosmos_address())?;
|
||||
storage::spent_credentials().save(
|
||||
deps.storage,
|
||||
data.blinded_serial_number(),
|
||||
&SpendCredential::new(
|
||||
data.funds().to_owned(),
|
||||
data.blinded_serial_number().to_owned(),
|
||||
gateway_cosmos_address,
|
||||
),
|
||||
)?;
|
||||
|
||||
let msg = to_cosmos_msg(
|
||||
data.funds().clone(),
|
||||
data.blinded_serial_number().to_string(),
|
||||
env.contract.address.into_string(),
|
||||
cfg.multisig_addr.into_string(),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_message(msg))
|
||||
}
|
||||
|
||||
pub(crate) fn release_funds(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
funds: Coin,
|
||||
) -> Result<Response, ContractError> {
|
||||
if funds.denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom);
|
||||
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
|
||||
if funds.denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
let current_balance = deps
|
||||
.querier
|
||||
.query_balance(env.contract.address, MIX_DENOM.base)?;
|
||||
.query_balance(env.contract.address, mix_denom)?;
|
||||
if funds.amount > current_balance.amount {
|
||||
return Err(ContractError::NotEnoughFunds);
|
||||
}
|
||||
@@ -70,11 +111,13 @@ pub(crate) fn release_funds(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::{MULTISIG_CONTRACT, POOL_CONTRACT};
|
||||
use crate::support::tests::fixtures::spend_credential_data_fixture;
|
||||
use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT};
|
||||
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{Coin, CosmosMsg};
|
||||
use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg};
|
||||
use cw_controllers::AdminError;
|
||||
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit() {
|
||||
@@ -92,7 +135,7 @@ mod tests {
|
||||
Err(ContractError::NoCoin)
|
||||
);
|
||||
|
||||
let coin = Coin::new(1000000, MIX_DENOM.base);
|
||||
let coin = Coin::new(1000000, crate::support::tests::fixtures::TEST_MIX_DENOM);
|
||||
let second_coin = Coin::new(1000000, "some_denom");
|
||||
|
||||
let info = mock_info("requester", &[coin, second_coin.clone()]);
|
||||
@@ -104,7 +147,9 @@ mod tests {
|
||||
let info = mock_info("requester", &[second_coin]);
|
||||
assert_eq!(
|
||||
deposit_funds(deps.as_mut(), env, info, data),
|
||||
Err(ContractError::WrongDenom)
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,7 +167,10 @@ mod tests {
|
||||
verification_key.clone(),
|
||||
encryption_key.clone(),
|
||||
);
|
||||
let coin = Coin::new(deposit_value, MIX_DENOM.base);
|
||||
let coin = Coin::new(
|
||||
deposit_value,
|
||||
crate::support::tests::fixtures::TEST_MIX_DENOM,
|
||||
);
|
||||
let info = mock_info("requester", &[coin]);
|
||||
|
||||
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
|
||||
@@ -171,7 +219,7 @@ mod tests {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let invalid_admin = "invalid admin";
|
||||
let funds = Coin::new(1, MIX_DENOM.base);
|
||||
let funds = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
|
||||
|
||||
let err = release_funds(
|
||||
deps.as_mut(),
|
||||
@@ -180,7 +228,12 @@ mod tests {
|
||||
Coin::new(1, "invalid denom"),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, ContractError::WrongDenom);
|
||||
assert_eq!(
|
||||
err,
|
||||
ContractError::WrongDenom {
|
||||
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let err = release_funds(
|
||||
deps.as_mut(),
|
||||
@@ -207,7 +260,7 @@ mod tests {
|
||||
fn valid_release() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let coin = Coin::new(1, MIX_DENOM.base);
|
||||
let coin = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
|
||||
|
||||
deps.querier
|
||||
.update_balance(env.contract.address.clone(), vec![coin.clone()]);
|
||||
@@ -226,4 +279,96 @@ mod tests {
|
||||
})
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn valid_spend() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
let data = spend_credential_data_fixture("blinded_serial_number");
|
||||
let res = spend_credential(deps.as_mut(), env.clone(), info, data.clone()).unwrap();
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &res.messages[0].msg
|
||||
{
|
||||
assert_eq!(contract_addr, MULTISIG_CONTRACT);
|
||||
assert!(funds.is_empty());
|
||||
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
|
||||
if let MultisigExecuteMsg::Propose {
|
||||
title: _,
|
||||
description,
|
||||
msgs,
|
||||
latest,
|
||||
} = multisig_msg
|
||||
{
|
||||
assert_eq!(description, data.blinded_serial_number().to_string());
|
||||
assert!(latest.is_none());
|
||||
if let CosmosMsg::Wasm(WasmMsg::Execute {
|
||||
contract_addr,
|
||||
msg,
|
||||
funds,
|
||||
}) = &msgs[0]
|
||||
{
|
||||
assert_eq!(*contract_addr, env.contract.address.into_string());
|
||||
assert!(funds.is_empty());
|
||||
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap();
|
||||
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
|
||||
assert_eq!(funds, *data.funds());
|
||||
} else {
|
||||
panic!("Could not extract release funds message from proposal");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Could not extract proposal from binary blob");
|
||||
}
|
||||
} else {
|
||||
panic!("Wasm execute message not found");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_spend_attempts() {
|
||||
let mut deps = helpers::init_contract();
|
||||
let env = mock_env();
|
||||
let info = mock_info("requester", &[]);
|
||||
|
||||
let invalid_data = SpendCredentialData::new(
|
||||
Coin::new(1, "invalid_denom".to_string()),
|
||||
String::new(),
|
||||
String::new(),
|
||||
);
|
||||
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
|
||||
assert_eq!(
|
||||
ret.unwrap_err(),
|
||||
ContractError::WrongDenom {
|
||||
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let invalid_data = SpendCredentialData::new(
|
||||
Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM),
|
||||
String::new(),
|
||||
"Blinded Serial Number".to_string(),
|
||||
);
|
||||
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
|
||||
assert_eq!(
|
||||
ret.unwrap_err().to_string(),
|
||||
"Generic error: Invalid input: address not normalized".to_string()
|
||||
);
|
||||
|
||||
let invalid_data = spend_credential_data_fixture("blined_serial_number");
|
||||
spend_credential(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
invalid_data.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let ret = spend_credential(deps.as_mut(), env, info, invalid_data);
|
||||
assert_eq!(
|
||||
ret.unwrap_err(),
|
||||
ContractError::DuplicateBlindedSerialNumber
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "coconut-test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cw-utils = "0.13.4"
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
coconut-bandwidth = { path = "../coconut-bandwidth" }
|
||||
cw-multi-test = { version = "0.13.2" }
|
||||
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
|
||||
[[test]]
|
||||
name = "coconut-test"
|
||||
path = "src/tests.rs"
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::helpers::*;
|
||||
use coconut_bandwidth::error::ContractError;
|
||||
use coconut_bandwidth_contract_common::{
|
||||
deposit::DepositData,
|
||||
msg::{ExecuteMsg, InstantiateMsg},
|
||||
};
|
||||
use cosmwasm_std::{coins, Addr};
|
||||
use cw_controllers::AdminError;
|
||||
use cw_multi_test::Executor;
|
||||
|
||||
const TEST_MIX_DENOM: &str = "unym";
|
||||
|
||||
#[test]
|
||||
fn deposit_and_release() {
|
||||
let init_funds = coins(10, TEST_MIX_DENOM);
|
||||
let deposit_funds = coins(1, TEST_MIX_DENOM);
|
||||
let release_funds = coins(2, TEST_MIX_DENOM);
|
||||
let mut app = mock_app(&init_funds);
|
||||
let multisig_addr = String::from(MULTISIG_CONTRACT);
|
||||
let pool_addr = String::from(POOL_CONTRACT);
|
||||
let random_addr = String::from(RANDOM_ADDRESS);
|
||||
|
||||
let code_id = app.store_code(contract_bandwidth());
|
||||
let msg = InstantiateMsg {
|
||||
multisig_addr: multisig_addr.clone(),
|
||||
pool_addr: pool_addr.clone(),
|
||||
mix_denom: TEST_MIX_DENOM.to_string(),
|
||||
};
|
||||
let contract_addr = app
|
||||
.instantiate_contract(
|
||||
code_id,
|
||||
Addr::unchecked(OWNER),
|
||||
&msg,
|
||||
&[],
|
||||
"bandwidth",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = ExecuteMsg::DepositFunds {
|
||||
data: DepositData::new(
|
||||
String::from("info"),
|
||||
String::from("id"),
|
||||
String::from("enc"),
|
||||
),
|
||||
};
|
||||
app.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&deposit_funds,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// try to release more then it's in the contract
|
||||
let msg = ExecuteMsg::ReleaseFunds {
|
||||
funds: release_funds[0].clone(),
|
||||
};
|
||||
let err = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(multisig_addr.clone()),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
|
||||
|
||||
// try to call release from non-admin
|
||||
let msg = ExecuteMsg::ReleaseFunds {
|
||||
funds: deposit_funds[0].clone(),
|
||||
};
|
||||
let err = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(random_addr),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ContractError::Admin(AdminError::NotAdmin {}),
|
||||
err.downcast().unwrap()
|
||||
);
|
||||
|
||||
let msg = ExecuteMsg::ReleaseFunds {
|
||||
funds: deposit_funds[0].clone(),
|
||||
};
|
||||
app.execute_contract(
|
||||
Addr::unchecked(multisig_addr),
|
||||
contract_addr.clone(),
|
||||
&msg,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
|
||||
assert_eq!(pool_bal, deposit_funds[0]);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
|
||||
use cw3_flex_multisig::{state::CONFIG, ContractError};
|
||||
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const OWNER: &str = "admin0001";
|
||||
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
|
||||
pub const POOL_CONTRACT: &str = "mix pool contract address";
|
||||
pub const RANDOM_ADDRESS: &str = "random address";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {
|
||||
pub coconut_bandwidth_address: String,
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
let mut cfg = CONFIG.load(deps.storage)?;
|
||||
cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?;
|
||||
CONFIG.save(deps.storage, &cfg)?;
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
pub fn mock_app(init_funds: &[Coin]) -> App {
|
||||
AppBuilder::new().build(|router, _, storage| {
|
||||
router
|
||||
.bank
|
||||
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
|
||||
.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
|
||||
let contract = ContractWrapper::new(
|
||||
coconut_bandwidth::contract::execute,
|
||||
coconut_bandwidth::contract::instantiate,
|
||||
coconut_bandwidth::contract::query,
|
||||
);
|
||||
Box::new(contract)
|
||||
}
|
||||
|
||||
pub fn contract_multisig() -> Box<dyn Contract<Empty>> {
|
||||
let contract = ContractWrapper::new(
|
||||
cw3_flex_multisig::contract::execute,
|
||||
cw3_flex_multisig::contract::instantiate,
|
||||
cw3_flex_multisig::contract::query,
|
||||
)
|
||||
.with_migrate(migrate);
|
||||
Box::new(contract)
|
||||
}
|
||||
|
||||
pub fn contract_group() -> Box<dyn Contract<Empty>> {
|
||||
let contract = ContractWrapper::new(
|
||||
cw4_group::contract::execute,
|
||||
cw4_group::contract::instantiate,
|
||||
cw4_group::contract::query,
|
||||
);
|
||||
Box::new(contract)
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
use crate::helpers::*;
|
||||
use coconut_bandwidth::error::ContractError;
|
||||
use coconut_bandwidth_contract_common::{
|
||||
msg::{
|
||||
ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg,
|
||||
},
|
||||
spend_credential::SpendCredentialData,
|
||||
};
|
||||
use cosmwasm_std::{coins, Addr, Coin, Decimal};
|
||||
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
|
||||
use cw_multi_test::Executor;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
|
||||
|
||||
pub const TEST_COIN_DENOM: &str = "unym";
|
||||
pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
|
||||
#[test]
|
||||
fn spend_credential_creates_proposal() {
|
||||
let init_funds = coins(10, TEST_COIN_DENOM);
|
||||
let mut app = mock_app(&init_funds);
|
||||
let pool_addr = String::from(POOL_CONTRACT);
|
||||
|
||||
let group_code_id = app.store_code(contract_group());
|
||||
let msg = GroupInstantiateMsg {
|
||||
admin: Some(OWNER.to_string()),
|
||||
members: vec![],
|
||||
};
|
||||
let group_contract_addr = app
|
||||
.instantiate_contract(
|
||||
group_code_id,
|
||||
Addr::unchecked(OWNER),
|
||||
&msg,
|
||||
&[],
|
||||
"group",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let multisig_code_id = app.store_code(contract_multisig());
|
||||
let msg = MultisigInstantiateMsg {
|
||||
group_addr: group_contract_addr.into_string(),
|
||||
threshold: Threshold::AbsolutePercentage {
|
||||
percentage: Decimal::from_ratio(2u128, 3u128),
|
||||
},
|
||||
max_voting_period: Duration::Height(1000),
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
};
|
||||
let multisig_contract_addr = app
|
||||
.instantiate_contract(
|
||||
multisig_code_id,
|
||||
Addr::unchecked(OWNER),
|
||||
&msg,
|
||||
&[],
|
||||
"multisig",
|
||||
Some(OWNER.to_string()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let coconut_bandwidth_code_id = app.store_code(contract_bandwidth());
|
||||
let msg = CoconutBandwidthInstantiateMsg {
|
||||
multisig_addr: multisig_contract_addr.to_string(),
|
||||
pool_addr,
|
||||
mix_denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
let coconut_bandwidth_contract_addr = app
|
||||
.instantiate_contract(
|
||||
coconut_bandwidth_code_id,
|
||||
Addr::unchecked(OWNER),
|
||||
&msg,
|
||||
&[],
|
||||
"coconut bandwidth",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = MigrateMsg {
|
||||
coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(),
|
||||
};
|
||||
app.migrate_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
multisig_contract_addr,
|
||||
&msg,
|
||||
multisig_code_id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
|
||||
data: SpendCredentialData::new(
|
||||
Coin::new(1, TEST_COIN_DENOM),
|
||||
String::from("blinded_serial_number"),
|
||||
String::from("gateway_cosmos_address"),
|
||||
),
|
||||
};
|
||||
let res = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
coconut_bandwidth_contract_addr.clone(),
|
||||
&msg,
|
||||
&vec![],
|
||||
)
|
||||
.unwrap();
|
||||
let proposal_id = res
|
||||
.events
|
||||
.into_iter()
|
||||
.find(|e| &e.ty == "wasm")
|
||||
.unwrap()
|
||||
.attributes
|
||||
.into_iter()
|
||||
.find(|attr| &attr.key == "proposal_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse::<u64>()
|
||||
.unwrap();
|
||||
assert_eq!(1, proposal_id);
|
||||
|
||||
// Trying with the same blinded serial number will detect the double spend attempt
|
||||
let err = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
coconut_bandwidth_contract_addr.clone(),
|
||||
&msg,
|
||||
&vec![],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
ContractError::DuplicateBlindedSerialNumber,
|
||||
err.downcast().unwrap()
|
||||
);
|
||||
|
||||
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
|
||||
data: SpendCredentialData::new(
|
||||
Coin::new(1, TEST_COIN_DENOM),
|
||||
String::from("blinded_serial_number2"),
|
||||
String::from("gateway_cosmos_address"),
|
||||
),
|
||||
};
|
||||
let res = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
coconut_bandwidth_contract_addr.clone(),
|
||||
&msg,
|
||||
&vec![],
|
||||
)
|
||||
.unwrap();
|
||||
let proposal_id = res
|
||||
.events
|
||||
.into_iter()
|
||||
.find(|e| &e.ty == "wasm")
|
||||
.unwrap()
|
||||
.attributes
|
||||
.into_iter()
|
||||
.find(|attr| &attr.key == "proposal_id")
|
||||
.unwrap()
|
||||
.value
|
||||
.parse::<u64>()
|
||||
.unwrap();
|
||||
assert_eq!(2, proposal_id);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod deposit_and_release;
|
||||
mod helpers;
|
||||
mod spend_credential_creates_proposal;
|
||||
@@ -27,16 +27,18 @@ use crate::mixnodes::bonding_queries::{
|
||||
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
|
||||
};
|
||||
use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
use crate::mixnodes::transactions::_try_remove_mixnode;
|
||||
use crate::queued_migrations::migrate_config_from_env;
|
||||
use crate::rewards::queries::{
|
||||
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
|
||||
};
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
Uint128,
|
||||
Storage, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::{
|
||||
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
|
||||
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -61,9 +63,14 @@ pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
|
||||
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
|
||||
}
|
||||
|
||||
fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> ContractState {
|
||||
fn default_initial_state(
|
||||
owner: Addr,
|
||||
mix_denom: String,
|
||||
rewarding_validator_address: Addr,
|
||||
) -> ContractState {
|
||||
ContractState {
|
||||
owner,
|
||||
mix_denom,
|
||||
rewarding_validator_address,
|
||||
params: ContractStateParams {
|
||||
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
|
||||
@@ -88,7 +95,7 @@ pub fn instantiate(
|
||||
msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
|
||||
let state = default_initial_state(info.sender, rewarding_validator_address);
|
||||
let state = default_initial_state(info.sender, msg.mixnet_denom, rewarding_validator_address);
|
||||
init_epoch(deps.storage, env)?;
|
||||
|
||||
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
|
||||
@@ -107,6 +114,19 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CompoundReward {
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
} => crate::rewards::transactions::try_compound_reward(
|
||||
deps,
|
||||
env,
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
),
|
||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
||||
try_update_rewarding_validator_address(deps, info, address)
|
||||
}
|
||||
@@ -122,7 +142,7 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
|
||||
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
@@ -216,7 +236,13 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
|
||||
&env,
|
||||
deps.storage,
|
||||
deps.api,
|
||||
info,
|
||||
owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
@@ -275,7 +301,7 @@ pub fn execute(
|
||||
)
|
||||
}
|
||||
ExecuteMsg::ReconcileDelegations {} => {
|
||||
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
|
||||
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
|
||||
}
|
||||
ExecuteMsg::CheckpointMixnodes {} => {
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
@@ -316,6 +342,9 @@ pub fn execute(
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::GetBlacklistedNodes {} => to_binary(
|
||||
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
|
||||
),
|
||||
QueryMsg::GetRewardingValidatorAddress {} => {
|
||||
to_binary(&query_rewarding_validator_address(deps)?)
|
||||
}
|
||||
@@ -443,16 +472,61 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
|
||||
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
|
||||
.idx
|
||||
.owner
|
||||
.item(storage, owner.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => {
|
||||
return Err(ContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
|
||||
fn remove_malicious_node(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
env: &Env,
|
||||
node: &NodeToRemove,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = node.proxy().map(|p| {
|
||||
api.addr_validate(p)
|
||||
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
|
||||
});
|
||||
let owner_addr = api.addr_validate(node.owner())?;
|
||||
blacklist_malicious_node(storage, &owner_addr)?;
|
||||
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
migrate_config_from_env(deps.storage, &msg)?;
|
||||
let mut response = Response::new();
|
||||
for node in msg.nodes_to_remove().iter() {
|
||||
let mut sub_response = remove_malicious_node(deps.storage, deps.api, &env, node)
|
||||
.unwrap_or_else(|_| panic!("Could not remove node: {:?}", node));
|
||||
response.messages.append(&mut sub_response.messages);
|
||||
response.attributes.append(&mut sub_response.attributes);
|
||||
response.events.append(&mut sub_response.events);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests;
|
||||
use config::defaults::{DEFAULT_NETWORK, MIX_DENOM};
|
||||
use crate::support::tests::fixtures::{TEST_COIN_DENOM, TEST_REWARDING_VALIDATOR_ADDRESS};
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, from_binary};
|
||||
use mixnet_contract_common::PagedMixnodeResponse;
|
||||
@@ -462,7 +536,8 @@ pub mod tests {
|
||||
let mut deps = mock_dependencies();
|
||||
let env = mock_env();
|
||||
let msg = InstantiateMsg {
|
||||
rewarding_validator_address: DEFAULT_NETWORK.rewarding_validator_address().to_string(),
|
||||
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
|
||||
mixnet_denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
|
||||
@@ -484,7 +559,7 @@ pub mod tests {
|
||||
|
||||
// Contract balance should match what we initialized it as
|
||||
assert_eq!(
|
||||
coins(0, MIX_DENOM.base),
|
||||
coins(0, TEST_COIN_DENOM),
|
||||
tests::queries::query_contract_balance(env.contract.address, deps)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,8 +199,7 @@ pub(crate) fn query_mixnode_delegations_paged(
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use crate::support::tests::{fixtures::TEST_COIN_DENOM, test_helpers};
|
||||
use cosmwasm_std::{coin, Addr, Storage};
|
||||
use rand::Rng;
|
||||
|
||||
@@ -385,7 +384,7 @@ pub(crate) mod tests {
|
||||
let delegation = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
node_identity.clone(),
|
||||
coin(1234, MIX_DENOM.base),
|
||||
coin(1234, TEST_COIN_DENOM),
|
||||
1234,
|
||||
None,
|
||||
);
|
||||
@@ -433,7 +432,7 @@ pub(crate) mod tests {
|
||||
let delegation = Delegation::new(
|
||||
delegation_owner2,
|
||||
node_identity1.clone(),
|
||||
coin(1234, MIX_DENOM.base),
|
||||
coin(1234, TEST_COIN_DENOM),
|
||||
1234,
|
||||
None,
|
||||
);
|
||||
@@ -460,7 +459,7 @@ pub(crate) mod tests {
|
||||
let delegation = Delegation::new(
|
||||
delegation_owner1.clone(),
|
||||
node_identity2,
|
||||
coin(1234, MIX_DENOM.base),
|
||||
coin(1234, TEST_COIN_DENOM),
|
||||
1234,
|
||||
None,
|
||||
);
|
||||
|
||||
@@ -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, MIX_DENOM.base);
|
||||
let delegation = coin(12345, TEST_COIN_DENOM);
|
||||
|
||||
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, MIX_DENOM.base);
|
||||
let delegation = coin(12345, TEST_COIN_DENOM);
|
||||
|
||||
assert!(test_helpers::read_delegation(
|
||||
deps.as_ref().storage,
|
||||
|
||||
@@ -4,9 +4,8 @@ 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 as mixnet_params_storage;
|
||||
use crate::mixnet_contract_settings::storage::{self 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,
|
||||
@@ -20,16 +19,7 @@ use mixnet_contract_common::{Delegation, IdentityKey};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
pub fn try_reconcile_all_delegation_events(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<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);
|
||||
}
|
||||
|
||||
pub fn try_reconcile_all_delegation_events(deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||
_try_reconcile_all_delegation_events(deps.storage, deps.api)
|
||||
}
|
||||
|
||||
@@ -74,7 +64,10 @@ pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, ContractError> {
|
||||
fn validate_delegation_stake(
|
||||
mut delegation: Vec<Coin>,
|
||||
mix_denom: String,
|
||||
) -> Result<Coin, ContractError> {
|
||||
// check if anything was put as delegation
|
||||
if delegation.is_empty() {
|
||||
return Err(ContractError::EmptyDelegation);
|
||||
@@ -85,8 +78,8 @@ fn validate_delegation_stake(mut delegation: Vec<Coin>) -> Result<Coin, Contract
|
||||
}
|
||||
|
||||
// check that the denomination is correct
|
||||
if delegation[0].denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom {});
|
||||
if delegation[0].denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
|
||||
// check that we have provided a non-zero amount in the delegation
|
||||
@@ -104,7 +97,8 @@ 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)?;
|
||||
let amount =
|
||||
validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps,
|
||||
@@ -124,7 +118,8 @@ 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)?;
|
||||
let amount =
|
||||
validate_delegation_stake(info.funds, mixnet_params_storage::mix_denom(deps.storage)?)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps,
|
||||
@@ -260,6 +255,7 @@ 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");
|
||||
|
||||
@@ -389,7 +385,7 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
.as_ref()
|
||||
.unwrap_or(&pending_undelegate.delegate())
|
||||
.to_string(),
|
||||
amount: coins(total_funds.u128(), MIX_DENOM.base),
|
||||
amount: coins(total_funds.u128(), mix_denom.clone()),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -401,10 +397,10 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: pending_undelegate.delegate().as_str().to_string(),
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
amount: Coin::new(total_funds.u128(), MIX_DENOM.base),
|
||||
amount: Coin::new(total_funds.u128(), mix_denom.clone()),
|
||||
});
|
||||
|
||||
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?);
|
||||
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?);
|
||||
}
|
||||
|
||||
let event = new_undelegation_event(
|
||||
@@ -468,9 +464,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)]
|
||||
@@ -483,7 +479,7 @@ mod tests {
|
||||
fn stake_cant_be_empty() {
|
||||
assert_eq!(
|
||||
Err(ContractError::EmptyDelegation),
|
||||
validate_delegation_stake(vec![])
|
||||
validate_delegation_stake(vec![], TEST_COIN_DENOM.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -491,19 +487,24 @@ mod tests {
|
||||
fn stake_must_have_single_coin_type() {
|
||||
assert_eq!(
|
||||
Err(ContractError::MultipleDenoms),
|
||||
validate_delegation_stake(vec![
|
||||
coin(123, MIX_DENOM.base),
|
||||
coin(123, "BTC"),
|
||||
coin(123, "DOGE")
|
||||
])
|
||||
validate_delegation_stake(
|
||||
vec![
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
coin(123, "BTC"),
|
||||
coin(123, "DOGE")
|
||||
],
|
||||
TEST_COIN_DENOM.to_string()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stake_coin_must_be_of_correct_type() {
|
||||
assert_eq!(
|
||||
Err(ContractError::WrongDenom {}),
|
||||
validate_delegation_stake(coins(123, "DOGE"))
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
}),
|
||||
validate_delegation_stake(coins(123, "DOGE"), TEST_COIN_DENOM.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -511,16 +512,28 @@ mod tests {
|
||||
fn stake_coin_must_have_value_greater_than_zero() {
|
||||
assert_eq!(
|
||||
Err(ContractError::EmptyDelegation),
|
||||
validate_delegation_stake(coins(0, MIX_DENOM.base))
|
||||
validate_delegation_stake(coins(0, TEST_COIN_DENOM), TEST_COIN_DENOM.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stake_can_have_any_positive_value() {
|
||||
// this might change in the future, but right now an arbitrary (positive) value can be delegated
|
||||
assert!(validate_delegation_stake(coins(1, MIX_DENOM.base)).is_ok());
|
||||
assert!(validate_delegation_stake(coins(123, MIX_DENOM.base)).is_ok());
|
||||
assert!(validate_delegation_stake(coins(10000000000, MIX_DENOM.base)).is_ok());
|
||||
assert!(validate_delegation_stake(
|
||||
coins(1, TEST_COIN_DENOM),
|
||||
TEST_COIN_DENOM.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(validate_delegation_stake(
|
||||
coins(123, TEST_COIN_DENOM),
|
||||
TEST_COIN_DENOM.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(validate_delegation_stake(
|
||||
coins(10000000000, TEST_COIN_DENOM),
|
||||
TEST_COIN_DENOM.to_string()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +556,7 @@ mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("sender", &coins(123, MIX_DENOM.base)),
|
||||
mock_info("sender", &coins(123, TEST_COIN_DENOM)),
|
||||
"non-existent-mix-identity".into(),
|
||||
)
|
||||
);
|
||||
@@ -559,7 +572,7 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
let delegation = coin(123, MIX_DENOM.base);
|
||||
let delegation = coin(123, TEST_COIN_DENOM);
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
@@ -608,7 +621,14 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -616,7 +636,7 @@ mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)),
|
||||
mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)),
|
||||
identity,
|
||||
)
|
||||
);
|
||||
@@ -631,13 +651,20 @@ mod tests {
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation = coin(123, MIX_DENOM.base);
|
||||
let delegation = coin(123, TEST_COIN_DENOM);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
@@ -687,8 +714,8 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
let delegation1 = coin(100, MIX_DENOM.base);
|
||||
let delegation2 = coin(50, MIX_DENOM.base);
|
||||
let delegation1 = coin(100, TEST_COIN_DENOM);
|
||||
let delegation2 = coin(50, TEST_COIN_DENOM);
|
||||
|
||||
let mut env = mock_env();
|
||||
|
||||
@@ -715,7 +742,7 @@ mod tests {
|
||||
// let expected = Delegation::new(
|
||||
// delegation_owner.clone(),
|
||||
// identity.clone(),
|
||||
// coin(delegation1.amount.u128() + delegation2.amount.u128(), MIX_DENOM.base),
|
||||
// coin(delegation1.amount.u128() + delegation2.amount.u128(), TEST_COIN_DENOM),
|
||||
// mock_env().block.height,
|
||||
// None,
|
||||
// );
|
||||
@@ -750,7 +777,7 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
let delegation = coin(100, MIX_DENOM.base);
|
||||
let delegation = coin(100, TEST_COIN_DENOM);
|
||||
let env1 = mock_env();
|
||||
let mut env2 = mock_env();
|
||||
let initial_height = env1.block.height;
|
||||
@@ -815,8 +842,8 @@ mod tests {
|
||||
);
|
||||
let delegation_owner1 = Addr::unchecked("sender1");
|
||||
let delegation_owner2 = Addr::unchecked("sender2");
|
||||
let delegation1 = coin(100, MIX_DENOM.base);
|
||||
let delegation2 = coin(120, MIX_DENOM.base);
|
||||
let delegation1 = coin(100, TEST_COIN_DENOM);
|
||||
let delegation2 = coin(120, TEST_COIN_DENOM);
|
||||
let env1 = mock_env();
|
||||
let mut env2 = mock_env();
|
||||
let initial_height = env1.block.height;
|
||||
@@ -891,11 +918,18 @@ mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(delegation_owner.as_str(), &coins(100, MIX_DENOM.base)),
|
||||
mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)),
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -903,7 +937,7 @@ mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(delegation_owner.as_str(), &coins(50, MIX_DENOM.base)),
|
||||
mock_info(delegation_owner.as_str(), &coins(50, TEST_COIN_DENOM)),
|
||||
identity,
|
||||
)
|
||||
);
|
||||
@@ -928,14 +962,14 @@ mod tests {
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(delegation_owner.as_str(), &coins(123, MIX_DENOM.base)),
|
||||
mock_info(delegation_owner.as_str(), &coins(123, TEST_COIN_DENOM)),
|
||||
identity1.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info(delegation_owner.as_str(), &coins(42, MIX_DENOM.base)),
|
||||
mock_info(delegation_owner.as_str(), &coins(42, TEST_COIN_DENOM)),
|
||||
identity2.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
@@ -945,7 +979,7 @@ mod tests {
|
||||
let expected1 = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity1.clone(),
|
||||
coin(123, MIX_DENOM.base),
|
||||
coin(123, TEST_COIN_DENOM),
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
@@ -953,7 +987,7 @@ mod tests {
|
||||
let expected2 = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
identity2.clone(),
|
||||
coin(42, MIX_DENOM.base),
|
||||
coin(42, TEST_COIN_DENOM),
|
||||
mock_env().block.height,
|
||||
None,
|
||||
);
|
||||
@@ -989,8 +1023,8 @@ mod tests {
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation1 = coin(123, MIX_DENOM.base);
|
||||
let delegation2 = coin(234, MIX_DENOM.base);
|
||||
let delegation1 = coin(123, TEST_COIN_DENOM);
|
||||
let delegation2 = coin(234, TEST_COIN_DENOM);
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
@@ -1026,7 +1060,7 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
let delegation_amount = coin(100, MIX_DENOM.base);
|
||||
let delegation_amount = coin(100, TEST_COIN_DENOM);
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
@@ -1036,8 +1070,14 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -1062,182 +1102,12 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod removing_mix_stake_delegation {
|
||||
use crate::delegations::queries::query_mixnode_delegation;
|
||||
use super::*;
|
||||
use crate::support::tests;
|
||||
use cosmwasm_std::coin;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Addr;
|
||||
use 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() {
|
||||
@@ -1251,8 +1121,8 @@ mod tests {
|
||||
);
|
||||
let delegation_owner1 = Addr::unchecked("sender1");
|
||||
let delegation_owner2 = Addr::unchecked("sender2");
|
||||
let delegation1 = coin(123, MIX_DENOM.base);
|
||||
let delegation2 = coin(234, MIX_DENOM.base);
|
||||
let delegation1 = coin(123, TEST_COIN_DENOM);
|
||||
let delegation2 = coin(234, TEST_COIN_DENOM);
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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;
|
||||
@@ -33,14 +32,14 @@ pub enum ContractError {
|
||||
#[error("MIXNET ({}): Unauthorized", line!())]
|
||||
Unauthorized,
|
||||
|
||||
#[error("MIXNET ({}): Wrong coin denomination, you must send {}", line!(), MIX_DENOM.base)]
|
||||
WrongDenom,
|
||||
#[error("MIXNET ({}): Wrong coin denomination, you must send {mix_denom}", line!())]
|
||||
WrongDenom { mix_denom: String },
|
||||
|
||||
#[error("MIXNET ({}): Received multiple coin types during staking", line!())]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("MIXNET ({}): No coin was sent for the bonding, you must send {}", line!(), MIX_DENOM.base)]
|
||||
NoBondFound,
|
||||
#[error("MIXNET ({}): No coin was sent for the bonding, you must send {mix_denom}", line!())]
|
||||
NoBondFound { mix_denom: String },
|
||||
|
||||
#[error("MIXNET ({}): Provided active set size is bigger than the rewarded set", line!())]
|
||||
InvalidActiveSetSize,
|
||||
@@ -179,4 +178,11 @@ 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 },
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(crate) fn gateways<'a>() -> IndexedMap<'a, IdentityKeyRef<'a>, GatewayBond,
|
||||
mod tests {
|
||||
use super::super::storage;
|
||||
use crate::support::tests;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use cosmwasm_std::StdResult;
|
||||
use cosmwasm_std::Storage;
|
||||
@@ -84,7 +84,7 @@ mod tests {
|
||||
let pledge_amount = 1000;
|
||||
|
||||
let gateway_bond = GatewayBond {
|
||||
pledge_amount: coin(pledge_amount, MIX_DENOM.base),
|
||||
pledge_amount: coin(pledge_amount, TEST_COIN_DENOM),
|
||||
owner: node_owner,
|
||||
block_height: 12_345,
|
||||
gateway: Gateway {
|
||||
|
||||
@@ -5,7 +5,6 @@ use super::storage;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{
|
||||
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128,
|
||||
};
|
||||
@@ -26,7 +25,8 @@ pub fn try_add_gateway(
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
|
||||
let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?;
|
||||
let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?;
|
||||
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
@@ -52,7 +52,8 @@ pub fn try_add_gateway_on_behalf(
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
|
||||
let mix_denom = mixnet_params_storage::mix_denom(deps.storage)?;
|
||||
let pledge = validate_gateway_pledge(info.funds, minimum_pledge, mix_denom)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
_try_add_gateway(
|
||||
@@ -138,6 +139,7 @@ pub(crate) fn _try_remove_gateway(
|
||||
proxy: Option<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
|
||||
@@ -177,7 +179,7 @@ pub(crate) fn _try_remove_gateway(
|
||||
amount: gateway_bond.pledge_amount(),
|
||||
};
|
||||
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
@@ -192,10 +194,11 @@ pub(crate) fn _try_remove_gateway(
|
||||
fn validate_gateway_pledge(
|
||||
mut pledge: Vec<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);
|
||||
return Err(ContractError::NoBondFound { mix_denom });
|
||||
}
|
||||
|
||||
if pledge.len() > 1 {
|
||||
@@ -203,8 +206,8 @@ fn validate_gateway_pledge(
|
||||
}
|
||||
|
||||
// check that the denomination is correct
|
||||
if pledge[0].denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom {});
|
||||
if pledge[0].denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
|
||||
// check that we have at least 100 coins in our pledge
|
||||
@@ -225,8 +228,8 @@ pub mod tests {
|
||||
use crate::error::ContractError;
|
||||
use crate::gateways::transactions::validate_gateway_pledge;
|
||||
use crate::support::tests;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, BankMsg, Response};
|
||||
use cosmwasm_std::{from_binary, Addr, Uint128};
|
||||
@@ -238,7 +241,7 @@ pub mod tests {
|
||||
|
||||
// if we fail validation (by say not sending enough funds
|
||||
let insufficient_bond = Into::<u128>::into(INITIAL_GATEWAY_PLEDGE) - 1;
|
||||
let info = mock_info("anyone", &coins(insufficient_bond, MIX_DENOM.base));
|
||||
let info = mock_info("anyone", &coins(insufficient_bond, TEST_COIN_DENOM));
|
||||
let (msg, _) = tests::messages::valid_bond_gateway_msg("anyone");
|
||||
|
||||
// we are informed that we didn't send enough funds
|
||||
@@ -544,13 +547,26 @@ pub mod tests {
|
||||
#[test]
|
||||
fn validating_gateway_bond() {
|
||||
// you must send SOME funds
|
||||
let result = validate_gateway_pledge(Vec::new(), INITIAL_GATEWAY_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::NoBondFound));
|
||||
let result = validate_gateway_pledge(
|
||||
Vec::new(),
|
||||
INITIAL_GATEWAY_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::NoBondFound {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// you must send at least 100 coins...
|
||||
let mut bond = tests::fixtures::good_gateway_pledge();
|
||||
bond[0].amount = INITIAL_GATEWAY_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
|
||||
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
|
||||
let result = validate_gateway_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_GATEWAY_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::InsufficientGatewayBond {
|
||||
@@ -562,18 +578,40 @@ pub mod tests {
|
||||
// more than that is still fine
|
||||
let mut bond = tests::fixtures::good_gateway_pledge();
|
||||
bond[0].amount = INITIAL_GATEWAY_PLEDGE + Uint128::new(1);
|
||||
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
|
||||
let result = validate_gateway_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_GATEWAY_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// it must be sent in the defined denom!
|
||||
let mut bond = tests::fixtures::good_gateway_pledge();
|
||||
bond[0].denom = "baddenom".to_string();
|
||||
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::WrongDenom {}));
|
||||
let result = validate_gateway_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_GATEWAY_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
|
||||
let mut bond = tests::fixtures::good_gateway_pledge();
|
||||
bond[0].denom = "foomp".to_string();
|
||||
let result = validate_gateway_pledge(bond.clone(), INITIAL_GATEWAY_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::WrongDenom {}));
|
||||
let result = validate_gateway_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_GATEWAY_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ mod gateways;
|
||||
mod interval;
|
||||
mod mixnet_contract_settings;
|
||||
mod mixnodes;
|
||||
mod queued_migrations;
|
||||
mod rewards;
|
||||
mod support;
|
||||
|
||||
@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContractState {
|
||||
pub owner: Addr, // only the owner account can update state
|
||||
pub mix_denom: String,
|
||||
pub rewarding_validator_address: Addr,
|
||||
pub params: ContractStateParams,
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub(crate) mod tests {
|
||||
|
||||
let dummy_state = ContractState {
|
||||
owner: Addr::unchecked("someowner"),
|
||||
mix_denom: String::from("unym"),
|
||||
rewarding_validator_address: Addr::unchecked("monitor"),
|
||||
params: ContractStateParams {
|
||||
minimum_mixnode_pledge: 123u128.into(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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;
|
||||
@@ -11,10 +10,14 @@ use mixnet_contract_common::{Layer, LayerDistribution};
|
||||
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new("config");
|
||||
pub(crate) const LAYERS: Item<'_, LayerDistribution> = Item::new("layers");
|
||||
|
||||
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<String, ContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
pub fn rewarding_validator_address(storage: &dyn Storage) -> StdResult<String> {
|
||||
CONTRACT_STATE
|
||||
.load(storage)
|
||||
.map(|state| state.rewarding_validator_address.to_string())?)
|
||||
.map(|state| state.rewarding_validator_address.to_string())
|
||||
}
|
||||
|
||||
pub fn mix_denom(storage: &dyn Storage) -> StdResult<String> {
|
||||
CONTRACT_STATE.load(storage).map(|state| state.mix_denom)
|
||||
}
|
||||
|
||||
pub fn increment_layer_count(storage: &mut dyn Storage, layer: Layer) -> StdResult<()> {
|
||||
|
||||
@@ -8,6 +8,13 @@ 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,
|
||||
@@ -252,10 +259,13 @@ pub(crate) mod tests {
|
||||
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
|
||||
assert!(res.mixnode.is_some());
|
||||
|
||||
let api = deps.api.clone();
|
||||
|
||||
// but after unbonding it, he doesn't own one anymore
|
||||
crate::mixnodes::transactions::try_remove_mixnode(
|
||||
env,
|
||||
deps.as_mut(),
|
||||
&env,
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info("fred", &[]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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::{
|
||||
@@ -11,6 +10,8 @@ use mixnet_contract_common::{SphinxKey, U128};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::mixnet_contract_settings::storage::mix_denom;
|
||||
|
||||
// storage prefixes
|
||||
const TOTAL_DELEGATION_NAMESPACE: &str = "td";
|
||||
const MIXNODES_PK_NAMESPACE: &str = "mn";
|
||||
@@ -18,6 +19,7 @@ const MIXNODES_PK_CHECKPOINTS: &str = "mn__check";
|
||||
const MIXNODES_PK_CHANGELOG: &str = "mn__change";
|
||||
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
||||
const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
|
||||
const MIXNODES_BOND_BLACKLIST_NAMESPACE: &str = "mbb";
|
||||
|
||||
const LAST_PM_UPDATE_NAMESPACE: &str = "lpm";
|
||||
|
||||
@@ -31,6 +33,9 @@ pub(crate) const TOTAL_DELEGATION: Map<'_, IdentityKeyRef<'_>, Uint128> =
|
||||
pub(crate) const LAST_PM_UPDATE_TIME: Map<'_, IdentityKeyRef<'_>, u64> =
|
||||
Map::new(LAST_PM_UPDATE_NAMESPACE);
|
||||
|
||||
pub(crate) const MIXNODES_BOND_BLACKLIST: Map<'_, IdentityKeyRef<'_>, u8> =
|
||||
Map::new(MIXNODES_BOND_BLACKLIST_NAMESPACE);
|
||||
|
||||
pub(crate) struct MixnodeBondIndex<'a> {
|
||||
pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>,
|
||||
|
||||
@@ -171,7 +176,7 @@ pub(crate) fn read_full_mixnode_bond(
|
||||
Ok(Some(MixNodeBond {
|
||||
pledge_amount: stored_bond.pledge_amount,
|
||||
total_delegation: Coin {
|
||||
denom: MIX_DENOM.base.to_owned(),
|
||||
denom: mix_denom(storage)?,
|
||||
amount: total_delegation.unwrap_or_default(),
|
||||
},
|
||||
owner: stored_bond.owner,
|
||||
@@ -190,7 +195,8 @@ mod tests {
|
||||
use super::super::storage;
|
||||
use super::*;
|
||||
use crate::support::tests;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers::init_contract;
|
||||
use cosmwasm_std::testing::MockStorage;
|
||||
use cosmwasm_std::{coin, Addr, Uint128};
|
||||
use mixnet_contract_common::{IdentityKey, MixNode};
|
||||
@@ -211,7 +217,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reading_mixnode_bond() {
|
||||
let mut mock_storage = MockStorage::new();
|
||||
let mut mock_storage = init_contract().storage;
|
||||
let node_owner: Addr = Addr::unchecked("node-owner");
|
||||
let node_identity: IdentityKey = "nodeidentity".into();
|
||||
|
||||
@@ -223,7 +229,7 @@ mod tests {
|
||||
let pledge_value = 1000000000;
|
||||
|
||||
let mixnode_bond = StoredMixnodeBond {
|
||||
pledge_amount: coin(pledge_value, MIX_DENOM.base),
|
||||
pledge_amount: coin(pledge_value, TEST_COIN_DENOM),
|
||||
owner: node_owner,
|
||||
layer: Layer::One,
|
||||
block_height: 12_345,
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
|
||||
use super::storage::{self, LAST_PM_UPDATE_TIME};
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnet_contract_settings::storage::{self as mixnet_params_storage, mix_denom};
|
||||
use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
use crate::mixnodes::storage::StoredMixnodeBond;
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{
|
||||
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
|
||||
wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::events::{
|
||||
new_checkpoint_mixnodes_event, new_mixnode_bonding_event, new_mixnode_unbonding_event,
|
||||
};
|
||||
use mixnet_contract_common::MixNode;
|
||||
use mixnet_contract_common::{IdentityKeyRef, MixNode};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
@@ -48,7 +47,7 @@ pub fn try_add_mixnode(
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
|
||||
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge, mix_denom(deps.storage)?)?;
|
||||
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
@@ -74,7 +73,7 @@ pub fn try_add_mixnode_on_behalf(
|
||||
.load(deps.storage)?
|
||||
.params
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
|
||||
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge, mix_denom(deps.storage)?)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
_try_add_mixnode(
|
||||
@@ -88,6 +87,15 @@ pub fn try_add_mixnode_on_behalf(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_blacklisted(
|
||||
storage: &dyn Storage,
|
||||
identity: IdentityKeyRef,
|
||||
) -> Result<bool, ContractError> {
|
||||
Ok(storage::MIXNODES_BOND_BLACKLIST
|
||||
.may_load(storage, identity)?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
fn _try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -101,6 +109,12 @@ fn _try_add_mixnode(
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(deps.storage, &owner)?;
|
||||
|
||||
if is_blacklisted(deps.storage, &mix_node.identity_key)? {
|
||||
return Err(ContractError::MixnodeBlacklisted {
|
||||
identity: mix_node.identity_key,
|
||||
});
|
||||
};
|
||||
|
||||
// We don't have to check lower bound as its an u8
|
||||
if mix_node.profit_margin_percent > 100 {
|
||||
return Err(ContractError::InvalidProfitMarginPercent(
|
||||
@@ -168,45 +182,47 @@ fn _try_add_mixnode(
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = info.sender;
|
||||
_try_remove_mixnode(env, deps, &owner, Some(proxy))
|
||||
_try_remove_mixnode(env, storage, api, &owner, Some(proxy), true)
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
_try_remove_mixnode(env, deps, info.sender.as_ref(), None)
|
||||
_try_remove_mixnode(env, storage, api, info.sender.as_ref(), None, true)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
collect_rewards: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner)?;
|
||||
let owner = api.addr_validate(owner)?;
|
||||
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
)?;
|
||||
if collect_rewards {
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
storage,
|
||||
api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
// try to find the node of the sender
|
||||
let mixnode_bond = match storage::mixnodes()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
{
|
||||
let mixnode_bond = match storage::mixnodes().idx.owner.item(storage, owner.clone())? {
|
||||
Some(record) => record.1,
|
||||
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
|
||||
};
|
||||
@@ -226,10 +242,10 @@ pub(crate) fn _try_remove_mixnode(
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::mixnodes().remove(deps.storage, mixnode_bond.identity(), env.block.height)?;
|
||||
storage::mixnodes().remove(storage, mixnode_bond.identity(), env.block.height)?;
|
||||
|
||||
// decrement layer count
|
||||
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
mixnet_params_storage::decrement_layer_count(storage, mixnode_bond.layer)?;
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
@@ -239,7 +255,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
amount: mixnode_bond.pledge_amount(),
|
||||
};
|
||||
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(storage)?)])?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
@@ -288,6 +304,7 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
.item(deps.storage, owner.clone())?
|
||||
.ok_or(ContractError::NoAssociatedMixNodeBond { owner })?
|
||||
.1;
|
||||
let mix_denom = mix_denom(deps.storage)?;
|
||||
|
||||
if proxy != mixnode_bond.proxy {
|
||||
return Err(ContractError::ProxyMismatch {
|
||||
@@ -329,7 +346,9 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
mixnode_bond.block_height = env.block.height;
|
||||
mixnode_bond
|
||||
})
|
||||
.ok_or(ContractError::NoBondFound)
|
||||
.ok_or(ContractError::NoBondFound {
|
||||
mix_denom: mix_denom.clone(),
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -342,7 +361,7 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
// and they could potentially leak 1 unym per transaction, altough I'm pretty sure transaction fees make that silly.
|
||||
let return_one_ucoint = BankMsg::Send {
|
||||
to_address: proxy.as_str().to_string(),
|
||||
amount: vec![one_ucoin()],
|
||||
amount: vec![one_ucoin(mix_denom)],
|
||||
};
|
||||
response = response.add_message(return_one_ucoint);
|
||||
}
|
||||
@@ -353,10 +372,11 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
fn validate_mixnode_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);
|
||||
return Err(ContractError::NoBondFound { mix_denom });
|
||||
}
|
||||
|
||||
if pledge.len() > 1 {
|
||||
@@ -364,8 +384,8 @@ fn validate_mixnode_pledge(
|
||||
}
|
||||
|
||||
// check that the denomination is correct
|
||||
if pledge[0].denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom {});
|
||||
if pledge[0].denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom { mix_denom });
|
||||
}
|
||||
|
||||
// check that we have at least MIXNODE_BOND coins in our pledge
|
||||
@@ -386,8 +406,8 @@ pub mod tests {
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnodes::transactions::validate_mixnode_pledge;
|
||||
use crate::support::tests;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coins, BankMsg, Response};
|
||||
use cosmwasm_std::{from_binary, Addr, Uint128};
|
||||
@@ -402,7 +422,7 @@ pub mod tests {
|
||||
|
||||
// if we don't send enough funds
|
||||
let insufficient_bond = Into::<u128>::into(INITIAL_MIXNODE_PLEDGE) - 1;
|
||||
let info = mock_info("anyone", &coins(insufficient_bond, MIX_DENOM.base));
|
||||
let info = mock_info("anyone", &coins(insufficient_bond, TEST_COIN_DENOM));
|
||||
let (msg, _) = tests::messages::valid_bond_mixnode_msg("anyone");
|
||||
|
||||
// we are informed that we didn't send enough funds
|
||||
@@ -808,13 +828,26 @@ pub mod tests {
|
||||
#[test]
|
||||
fn validating_mixnode_bond() {
|
||||
// you must send SOME funds
|
||||
let result = validate_mixnode_pledge(Vec::new(), INITIAL_MIXNODE_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::NoBondFound));
|
||||
let result = validate_mixnode_pledge(
|
||||
Vec::new(),
|
||||
INITIAL_MIXNODE_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::NoBondFound {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
|
||||
// you must send at least 100 coins...
|
||||
let mut bond = tests::fixtures::good_mixnode_pledge();
|
||||
bond[0].amount = INITIAL_MIXNODE_PLEDGE.checked_sub(Uint128::new(1)).unwrap();
|
||||
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
|
||||
let result = validate_mixnode_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_MIXNODE_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::InsufficientMixNodeBond {
|
||||
@@ -826,19 +859,41 @@ pub mod tests {
|
||||
// more than that is still fine
|
||||
let mut bond = tests::fixtures::good_mixnode_pledge();
|
||||
bond[0].amount = INITIAL_MIXNODE_PLEDGE + Uint128::new(1);
|
||||
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
|
||||
let result = validate_mixnode_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_MIXNODE_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// it must be sent in the defined denom!
|
||||
let mut bond = tests::fixtures::good_mixnode_pledge();
|
||||
bond[0].denom = "baddenom".to_string();
|
||||
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::WrongDenom {}));
|
||||
let result = validate_mixnode_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_MIXNODE_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
|
||||
let mut bond = tests::fixtures::good_mixnode_pledge();
|
||||
bond[0].denom = "foomp".to_string();
|
||||
let result = validate_mixnode_pledge(bond.clone(), INITIAL_MIXNODE_PLEDGE);
|
||||
assert_eq!(result, Err(ContractError::WrongDenom {}));
|
||||
let result = validate_mixnode_pledge(
|
||||
bond.clone(),
|
||||
INITIAL_MIXNODE_PLEDGE,
|
||||
TEST_COIN_DENOM.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ContractError::WrongDenom {
|
||||
mix_denom: TEST_COIN_DENOM.to_string()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Addr, Response, Storage};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::{ContractStateParams, MigrateMsg};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::models::ContractState;
|
||||
use crate::mixnet_contract_settings::storage::CONTRACT_STATE;
|
||||
|
||||
pub fn migrate_config_from_env(
|
||||
storage: &mut dyn Storage,
|
||||
msg: &MigrateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct OldContractState {
|
||||
pub owner: Addr,
|
||||
pub mix_denom: String,
|
||||
pub rewarding_validator_address: Addr,
|
||||
pub params: ContractStateParams,
|
||||
}
|
||||
const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config");
|
||||
|
||||
let old_state = OLD_CONTRACT_STATE.load(storage)?;
|
||||
let new_state = ContractState {
|
||||
owner: old_state.owner,
|
||||
mix_denom: msg.mixnet_denom.clone(),
|
||||
rewarding_validator_address: old_state.rewarding_validator_address,
|
||||
params: old_state.params,
|
||||
};
|
||||
|
||||
CONTRACT_STATE.save(storage, &new_state)?;
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -83,7 +83,7 @@ pub(crate) mod tests {
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode;
|
||||
use crate::interval::storage::{save_epoch, save_epoch_reward_params};
|
||||
use crate::rewards::transactions::try_reward_mixnode;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use cosmwasm_std::{coin, Addr};
|
||||
use mixnet_contract_common::{
|
||||
Interval, RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT,
|
||||
@@ -187,7 +187,7 @@ pub(crate) mod tests {
|
||||
env.clone(),
|
||||
mock_info(
|
||||
&*format!("delegator{:04}", i),
|
||||
&[coin(200_000000, MIX_DENOM.base)],
|
||||
&[coin(200_000000, TEST_COIN_DENOM)],
|
||||
),
|
||||
node_identity.clone(),
|
||||
)
|
||||
|
||||
@@ -10,11 +10,12 @@ use crate::contract::debug_with_visibility;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::_try_delegate_to_mixnode;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage::mix_denom;
|
||||
use crate::mixnodes::storage::mixnodes;
|
||||
use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond};
|
||||
use crate::mixnodes::transactions::is_blacklisted;
|
||||
use crate::rewards::helpers;
|
||||
use crate::support::helpers::{is_authorized, operator_cost_at_epoch};
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{
|
||||
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response,
|
||||
Storage, Uint128,
|
||||
@@ -69,6 +70,7 @@ fn _try_claim_operator_reward(
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = api.addr_validate(owner)?;
|
||||
let mix_denom = mix_denom(storage)?;
|
||||
|
||||
let bond = match crate::mixnodes::storage::mixnodes()
|
||||
.idx
|
||||
@@ -105,7 +107,7 @@ fn _try_claim_operator_reward(
|
||||
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
amount: coins(reward.u128(), MIX_DENOM.base),
|
||||
amount: coins(reward.u128(), mix_denom.clone()),
|
||||
};
|
||||
|
||||
let mut response = Response::default()
|
||||
@@ -115,10 +117,10 @@ fn _try_claim_operator_reward(
|
||||
if let Some(proxy) = proxy {
|
||||
let msg = Some(VestingContractExecuteMsg::TrackReward {
|
||||
address: owner.to_string(),
|
||||
amount: Coin::new(reward.u128(), MIX_DENOM.base),
|
||||
amount: Coin::new(reward.u128(), mix_denom.clone()),
|
||||
});
|
||||
|
||||
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
|
||||
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?;
|
||||
response = response.add_message(wasm_msg);
|
||||
}
|
||||
|
||||
@@ -134,6 +136,7 @@ pub fn _try_claim_delegator_reward(
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = api.addr_validate(owner)?;
|
||||
let mix_denom = mix_denom(storage)?;
|
||||
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(&owner, proxy.as_ref());
|
||||
let reward = calculate_delegator_reward(storage, api, key.clone(), mix_identity)?;
|
||||
@@ -153,7 +156,7 @@ pub fn _try_claim_delegator_reward(
|
||||
|
||||
let return_tokens = BankMsg::Send {
|
||||
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
|
||||
amount: coins(reward.u128(), MIX_DENOM.base),
|
||||
amount: coins(reward.u128(), mix_denom.clone()),
|
||||
};
|
||||
|
||||
let mut response =
|
||||
@@ -169,10 +172,10 @@ pub fn _try_claim_delegator_reward(
|
||||
if let Some(proxy) = proxy {
|
||||
let msg = Some(VestingContractExecuteMsg::TrackReward {
|
||||
address: owner.to_string(),
|
||||
amount: Coin::new(reward.u128(), MIX_DENOM.base),
|
||||
amount: Coin::new(reward.u128(), mix_denom.clone()),
|
||||
});
|
||||
|
||||
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin()])?;
|
||||
let wasm_msg = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom)])?;
|
||||
response = response.add_message(wasm_msg);
|
||||
}
|
||||
|
||||
@@ -370,6 +373,56 @@ pub fn try_compound_delegator_reward_on_behalf(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_compound_reward(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = proxy.and_then(|p| deps.api.addr_validate(&p).ok());
|
||||
if let Some(operator_address) = operator {
|
||||
let operator_address = deps.api.addr_validate(&operator_address)?;
|
||||
let reward = _try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&operator_address,
|
||||
proxy,
|
||||
)?;
|
||||
Ok(
|
||||
Response::default().add_event(new_compound_operator_reward_event(
|
||||
&operator_address,
|
||||
reward,
|
||||
)),
|
||||
)
|
||||
} else if let Some(delegator_address) = delegator {
|
||||
if mix_identity.is_none() {
|
||||
return Err(ContractError::MissingMixIdentity);
|
||||
}
|
||||
|
||||
let delegator_address = deps.api.addr_validate(&delegator_address)?;
|
||||
let reward = _try_compound_delegator_reward(
|
||||
env.block.height,
|
||||
deps,
|
||||
delegator_address.as_str(),
|
||||
mix_identity.as_ref().unwrap(),
|
||||
proxy,
|
||||
)?;
|
||||
Ok(
|
||||
Response::default().add_event(new_compound_delegator_reward_event(
|
||||
&delegator_address,
|
||||
&None,
|
||||
reward,
|
||||
&mix_identity.unwrap(),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
Ok(Response::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_compound_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -403,13 +456,14 @@ pub fn _try_compound_delegator_reward(
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let delegation_map = crate::delegations::storage::delegations();
|
||||
let mix_denom = mix_denom(deps.storage)?;
|
||||
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&deps.api.addr_validate(owner_address)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?;
|
||||
let mut compounded_delegation = reward;
|
||||
let mut total_delegation_delegate = Uint128::zero();
|
||||
|
||||
// Might want to introduce paging here
|
||||
let delegation_heights = delegation_map
|
||||
@@ -421,7 +475,7 @@ pub fn _try_compound_delegator_reward(
|
||||
for h in delegation_heights {
|
||||
let delegation =
|
||||
delegation_map.load(deps.storage, (mix_identity.to_string(), key.clone(), h))?;
|
||||
compounded_delegation += delegation.amount.amount;
|
||||
total_delegation_delegate += delegation.amount.amount;
|
||||
delegation_map.replace(
|
||||
deps.storage,
|
||||
(mix_identity.to_string(), key.clone(), h),
|
||||
@@ -430,7 +484,22 @@ pub fn _try_compound_delegator_reward(
|
||||
)?;
|
||||
}
|
||||
|
||||
let compounded_delegation = total_delegation_delegate + reward;
|
||||
|
||||
if compounded_delegation != Uint128::zero() {
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
deps.storage,
|
||||
mix_identity,
|
||||
|total_delegation| {
|
||||
// since we know that the target node exists and because the total_delegation bucket
|
||||
// entry is created whenever the node itself is added, the unwrap here is fine
|
||||
// as the entry MUST exist
|
||||
Ok(total_delegation
|
||||
.unwrap()
|
||||
.saturating_sub(total_delegation_delegate))
|
||||
},
|
||||
)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps.branch(),
|
||||
block_height,
|
||||
@@ -438,7 +507,7 @@ pub fn _try_compound_delegator_reward(
|
||||
owner_address,
|
||||
Coin {
|
||||
amount: compounded_delegation,
|
||||
denom: MIX_DENOM.base.to_string(),
|
||||
denom: mix_denom,
|
||||
},
|
||||
proxy,
|
||||
)?;
|
||||
@@ -469,6 +538,10 @@ pub fn calculate_delegator_reward(
|
||||
key: Vec<u8>,
|
||||
mix_identity: &str,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
if is_blacklisted(storage, mix_identity)? {
|
||||
return Ok(Uint128::zero());
|
||||
};
|
||||
|
||||
let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT
|
||||
.load(storage, (key.clone(), mix_identity.to_string()))
|
||||
.unwrap_or(0);
|
||||
@@ -724,9 +797,9 @@ pub mod tests {
|
||||
use crate::rewards::transactions::try_reward_mixnode;
|
||||
use crate::support::helpers::{current_operator_epoch_cost, epochs_in_interval};
|
||||
use crate::support::tests;
|
||||
use crate::support::tests::fixtures::TEST_COIN_DENOM;
|
||||
use crate::support::tests::test_helpers;
|
||||
use az::CheckedCast;
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coin, coins, Addr, StdError, Timestamp, Uint128};
|
||||
use mixnet_contract_common::events::{
|
||||
@@ -744,7 +817,7 @@ pub mod tests {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
let sender = rewarding_validator_address(&deps.storage).unwrap();
|
||||
let info = mock_info(&sender, &coins(1000, MIX_DENOM.base));
|
||||
let info = mock_info(&sender, &coins(1000, TEST_COIN_DENOM));
|
||||
crate::interval::transactions::init_epoch(&mut deps.storage, env.clone()).unwrap();
|
||||
|
||||
// bond the node
|
||||
@@ -885,7 +958,7 @@ pub mod tests {
|
||||
let initial_bond = 10000_000000;
|
||||
let initial_delegation = 20000_000000;
|
||||
let mixnode_bond = StoredMixnodeBond {
|
||||
pledge_amount: coin(initial_bond, MIX_DENOM.base),
|
||||
pledge_amount: coin(initial_bond, TEST_COIN_DENOM),
|
||||
owner: node_owner,
|
||||
layer: Layer::One,
|
||||
block_height: env.block.height,
|
||||
@@ -925,7 +998,7 @@ pub mod tests {
|
||||
&Delegation::new(
|
||||
Addr::unchecked("delegator"),
|
||||
node_identity.clone(),
|
||||
coin(initial_delegation, MIX_DENOM.base),
|
||||
coin(initial_delegation, TEST_COIN_DENOM),
|
||||
env.block.height,
|
||||
None,
|
||||
),
|
||||
@@ -1109,7 +1182,7 @@ pub mod tests {
|
||||
assert_eq!(staking_supply, 100_000_000_000_000u128);
|
||||
|
||||
let sender = Addr::unchecked("alice");
|
||||
let stake = coins(10_000_000_000, MIX_DENOM.base);
|
||||
let stake = coins(10_000_000_000, TEST_COIN_DENOM);
|
||||
|
||||
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
|
||||
let owner_signature = keypair
|
||||
@@ -1160,14 +1233,14 @@ pub mod tests {
|
||||
let node_owner: Addr = Addr::unchecked("johnny");
|
||||
let node_identity_2 = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10_000_000_000, MIX_DENOM.base),
|
||||
coins(10_000_000_000, TEST_COIN_DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]),
|
||||
mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1175,7 +1248,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d2", &[coin(2000_000000, MIX_DENOM.base)]),
|
||||
mock_info("alice_d2", &[coin(2000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1183,7 +1256,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob_d1", &[coin(8000_000000, MIX_DENOM.base)]),
|
||||
mock_info("bob_d1", &[coin(8000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity_2.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1191,7 +1264,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("bob_d2", &[coin(2000_000000, MIX_DENOM.base)]),
|
||||
mock_info("bob_d2", &[coin(2000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity_2.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1199,14 +1272,14 @@ pub mod tests {
|
||||
let node_owner: Addr = Addr::unchecked("alicebob");
|
||||
let node_identity_3 = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10_000_000_000 * 2, MIX_DENOM.base),
|
||||
coins(10_000_000_000 * 2, TEST_COIN_DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alicebob_d1", &[coin(8000_000000 * 2, MIX_DENOM.base)]),
|
||||
mock_info("alicebob_d1", &[coin(8000_000000 * 2, TEST_COIN_DENOM)]),
|
||||
node_identity_3.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1214,7 +1287,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alicebob_d2", &[coin(2000_000000 * 2, MIX_DENOM.base)]),
|
||||
mock_info("alicebob_d2", &[coin(2000_000000 * 2, TEST_COIN_DENOM)]),
|
||||
node_identity_3.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1320,7 +1393,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]),
|
||||
mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1559,14 +1632,14 @@ pub mod tests {
|
||||
let node_owner: Addr = Addr::unchecked("alice");
|
||||
let node_identity = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10_000_000_000, MIX_DENOM.base),
|
||||
coins(10_000_000_000, TEST_COIN_DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d1", &[coin(8000_000000, MIX_DENOM.base)]),
|
||||
mock_info("alice_d1", &[coin(8000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1574,7 +1647,7 @@ pub mod tests {
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d2", &[coin(2000_000000, MIX_DENOM.base)]),
|
||||
mock_info("alice_d2", &[coin(2000_000000, TEST_COIN_DENOM)]),
|
||||
node_identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1750,7 +1823,7 @@ pub mod tests {
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
let node_identity = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10000_000_000, MIX_DENOM.base),
|
||||
coins(10000_000_000, TEST_COIN_DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
@@ -1777,7 +1850,7 @@ pub mod tests {
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
let node_identity = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10000_000_000, MIX_DENOM.base),
|
||||
coins(10000_000_000, TEST_COIN_DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
@@ -1787,7 +1860,7 @@ pub mod tests {
|
||||
env.clone(),
|
||||
mock_info(
|
||||
&*format!("delegator{:04}", i),
|
||||
&[coin(2000_000000, MIX_DENOM.base)],
|
||||
&[coin(2000_000000, TEST_COIN_DENOM)],
|
||||
),
|
||||
node_identity.clone(),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::contract::INITIAL_MIXNODE_PLEDGE;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::{mixnodes::storage::StoredMixnodeBond, support::tests};
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{coin, Addr, Coin};
|
||||
use mixnet_contract_common::reward_params::NodeRewardParams;
|
||||
use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode};
|
||||
|
||||
pub const TEST_COIN_DENOM: &str = "unym";
|
||||
pub const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
|
||||
pub fn mix_node_fixture() -> MixNode {
|
||||
MixNode {
|
||||
host: "mix.node.org".to_string(),
|
||||
@@ -37,7 +39,7 @@ pub fn gateway_bond_fixture(owner: &str) -> GatewayBond {
|
||||
..tests::fixtures::gateway_fixture()
|
||||
};
|
||||
GatewayBond::new(
|
||||
coin(50, MIX_DENOM.base),
|
||||
coin(50, TEST_COIN_DENOM),
|
||||
Addr::unchecked(owner),
|
||||
12_345,
|
||||
gateway,
|
||||
@@ -47,7 +49,7 @@ pub fn gateway_bond_fixture(owner: &str) -> GatewayBond {
|
||||
|
||||
pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::StoredMixnodeBond {
|
||||
StoredMixnodeBond::new(
|
||||
coin(50, MIX_DENOM.base),
|
||||
coin(50, TEST_COIN_DENOM),
|
||||
Addr::unchecked(owner),
|
||||
Layer::One,
|
||||
12_345,
|
||||
@@ -64,14 +66,14 @@ pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::Stor
|
||||
|
||||
pub fn good_mixnode_pledge() -> Vec<Coin> {
|
||||
vec![Coin {
|
||||
denom: MIX_DENOM.base.to_string(),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
amount: INITIAL_MIXNODE_PLEDGE,
|
||||
}]
|
||||
}
|
||||
|
||||
pub fn good_gateway_pledge() -> Vec<Coin> {
|
||||
vec![Coin {
|
||||
denom: MIX_DENOM.base.to_string(),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
amount: INITIAL_MIXNODE_PLEDGE,
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ pub mod test_helpers {
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::mixnodes::transactions::try_add_mixnode;
|
||||
use crate::support::tests;
|
||||
use config::defaults::{DEFAULT_NETWORK, MIX_DENOM};
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
@@ -32,6 +31,9 @@ pub mod test_helpers {
|
||||
use mixnet_contract_common::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode};
|
||||
use rand::thread_rng;
|
||||
|
||||
use super::fixtures::TEST_COIN_DENOM;
|
||||
use super::fixtures::TEST_REWARDING_VALIDATOR_ADDRESS;
|
||||
|
||||
pub fn add_mixnode(sender: &str, stake: Vec<Coin>, deps: DepsMut<'_>) -> String {
|
||||
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
|
||||
let owner_signature = keypair
|
||||
@@ -85,7 +87,8 @@ pub mod test_helpers {
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InstantiateMsg {
|
||||
rewarding_validator_address: DEFAULT_NETWORK.rewarding_validator_address().to_string(),
|
||||
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
|
||||
mixnet_denom: TEST_COIN_DENOM.to_string(),
|
||||
};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
@@ -111,7 +114,7 @@ pub mod test_helpers {
|
||||
let delegation = Delegation {
|
||||
owner: Addr::unchecked(owner.into()),
|
||||
node_identity: mix.into(),
|
||||
amount: coin(12345, MIX_DENOM.base),
|
||||
amount: coin(12345, TEST_COIN_DENOM),
|
||||
block_height: block_height,
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{
|
||||
from_binary,
|
||||
testing::{mock_env, MockApi, MockQuerier, MockStorage},
|
||||
@@ -10,6 +9,8 @@ use mixnet_contract_common::{
|
||||
|
||||
use crate::contract::query;
|
||||
|
||||
use super::fixtures::TEST_COIN_DENOM;
|
||||
|
||||
pub fn get_mix_nodes(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Vec<MixNodeBond> {
|
||||
let result = query(
|
||||
deps.as_ref(),
|
||||
@@ -45,5 +46,5 @@ pub fn query_contract_balance(
|
||||
deps: OwnedDeps<MockStorage, MockApi, MockQuerier>,
|
||||
) -> Vec<Coin> {
|
||||
let querier = deps.as_ref().querier;
|
||||
vec![querier.query_balance(address, MIX_DENOM.base).unwrap()]
|
||||
vec![querier.query_balance(address, TEST_COIN_DENOM).unwrap()]
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use cw_utils::{maybe_addr, Expiration, ThresholdResponse};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::state::{Config, CONFIG};
|
||||
use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
|
||||
use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
// version info for migration info
|
||||
const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig";
|
||||
@@ -37,6 +37,11 @@ pub fn instantiate(
|
||||
addr: msg.group_addr.clone(),
|
||||
}
|
||||
})?);
|
||||
// This might need to be changed via a migration, due to circular dependency
|
||||
// of deploying the two contracts
|
||||
let coconut_bandwidth_addr = deps
|
||||
.api
|
||||
.addr_validate(&msg.coconut_bandwidth_contract_address)?;
|
||||
let total_weight = group_addr.total_weight(&deps.querier)?;
|
||||
msg.threshold.validate(total_weight)?;
|
||||
|
||||
@@ -46,12 +51,21 @@ pub fn instantiate(
|
||||
threshold: msg.threshold,
|
||||
max_voting_period: msg.max_voting_period,
|
||||
group_addr,
|
||||
coconut_bandwidth_addr,
|
||||
};
|
||||
CONFIG.save(deps.storage, &cfg)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<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())
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn execute(
|
||||
deps: DepsMut,
|
||||
@@ -88,15 +102,12 @@ pub fn execute_propose(
|
||||
// only members of the multisig can create a proposal
|
||||
let cfg = CONFIG.load(deps.storage)?;
|
||||
|
||||
// Only members of the multisig can create a proposal
|
||||
// Non-voting members are special - they are allowed to create a proposal and
|
||||
// therefore "vote", but they aren't allowed to vote otherwise.
|
||||
// Such vote is also special, because despite having 0 weight it still counts when
|
||||
// counting threshold passing
|
||||
let vote_power = cfg
|
||||
.group_addr
|
||||
.is_member(&deps.querier, &info.sender, None)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
// Only the coconut bandwidth contract can create proposals
|
||||
if info.sender != cfg.coconut_bandwidth_addr {
|
||||
return Err(ContractError::Unauthorized {});
|
||||
}
|
||||
// The contract doesn't have any say in the voting outcome
|
||||
let vote_power = 0;
|
||||
|
||||
// max expires also used as default
|
||||
let max_expires = cfg.max_voting_period.after(&env.block);
|
||||
@@ -433,7 +444,9 @@ mod tests {
|
||||
use cw2::{query_contract_info, ContractVersion};
|
||||
use cw4::{Cw4ExecuteMsg, Member};
|
||||
use cw4_group::helpers::Cw4GroupContract;
|
||||
use cw_multi_test::{next_block, App, AppBuilder, Contract, ContractWrapper, Executor};
|
||||
use cw_multi_test::{
|
||||
next_block, App, AppBuilder, AppResponse, Contract, ContractWrapper, Executor,
|
||||
};
|
||||
use cw_utils::{Duration, Threshold};
|
||||
|
||||
use super::*;
|
||||
@@ -445,6 +458,8 @@ mod tests {
|
||||
const VOTER4: &str = "voter0004";
|
||||
const VOTER5: &str = "voter0005";
|
||||
const SOMEBODY: &str = "somebody";
|
||||
const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
|
||||
fn member<T: Into<String>>(addr: T, weight: u64) -> Member {
|
||||
Member {
|
||||
@@ -491,6 +506,26 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn propose_and_vote(
|
||||
app: &mut App,
|
||||
flex_addr: Addr,
|
||||
proposal: ExecuteMsg,
|
||||
voter: &str,
|
||||
) -> AppResponse {
|
||||
let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(proposer), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
|
||||
let yes_vote = ExecuteMsg::Vote {
|
||||
proposal_id: res.custom_attrs(1)[2].value.parse().unwrap(),
|
||||
vote: Vote::Yes,
|
||||
};
|
||||
let _ = app.execute_contract(Addr::unchecked(voter), flex_addr, &yes_vote, &[]);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn instantiate_flex(
|
||||
app: &mut App,
|
||||
@@ -503,6 +538,7 @@ mod tests {
|
||||
group_addr: group.to_string(),
|
||||
threshold,
|
||||
max_voting_period,
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
};
|
||||
app.instantiate_contract(flex_id, Addr::unchecked(OWNER), &msg, &[], "flex", None)
|
||||
.unwrap()
|
||||
@@ -538,8 +574,10 @@ mod tests {
|
||||
init_funds: Vec<Coin>,
|
||||
multisig_as_group_admin: bool,
|
||||
) -> (Addr, Addr) {
|
||||
// 1. Instantiate group contract with members (and OWNER as admin)
|
||||
let coconut_bandwidth_contract = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
|
||||
// 1. Instantiate group contract with members (and OWNER as admin) and coconut bandwidth contract
|
||||
let members = vec![
|
||||
member(coconut_bandwidth_contract, 0),
|
||||
member(OWNER, 0),
|
||||
member(VOTER1, 1),
|
||||
member(VOTER2, 2),
|
||||
@@ -616,6 +654,7 @@ mod tests {
|
||||
quorum: Decimal::percent(1),
|
||||
},
|
||||
max_voting_period,
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
};
|
||||
let err = app
|
||||
.instantiate_contract(
|
||||
@@ -637,6 +676,7 @@ mod tests {
|
||||
group_addr: group_addr.to_string(),
|
||||
threshold: Threshold::AbsoluteCount { weight: 100 },
|
||||
max_voting_period,
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
};
|
||||
let err = app
|
||||
.instantiate_contract(
|
||||
@@ -658,6 +698,7 @@ mod tests {
|
||||
group_addr: group_addr.to_string(),
|
||||
threshold: Threshold::AbsoluteCount { weight: 1 },
|
||||
max_voting_period,
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
};
|
||||
let flex_addr = app
|
||||
.instantiate_contract(
|
||||
@@ -730,41 +771,13 @@ mod tests {
|
||||
};
|
||||
let err = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(OWNER),
|
||||
Addr::unchecked(TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string()),
|
||||
flex_addr.clone(),
|
||||
&proposal_wrong_exp,
|
||||
&[],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::WrongExpiration {}, err.downcast().unwrap());
|
||||
|
||||
// Proposal from voter works
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
res.custom_attrs(1),
|
||||
[
|
||||
("action", "propose"),
|
||||
("sender", VOTER3),
|
||||
("proposal_id", "1"),
|
||||
("status", "Open"),
|
||||
],
|
||||
);
|
||||
|
||||
// Proposal from voter with enough vote power directly passes
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER4), flex_addr, &proposal, &[])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
res.custom_attrs(1),
|
||||
[
|
||||
("action", "propose"),
|
||||
("sender", VOTER4),
|
||||
("proposal_id", "2"),
|
||||
("status", "Passed"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn get_tally(app: &App, flex_addr: &str, proposal_id: u64) -> u64 {
|
||||
@@ -805,6 +818,54 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proposer_limited_to_coconut_bandwidth() {
|
||||
let init_funds = coins(10, "BTC");
|
||||
let mut app = mock_app(&init_funds);
|
||||
|
||||
let voting_period = Duration::Time(2000000);
|
||||
let threshold = Threshold::ThresholdQuorum {
|
||||
threshold: Decimal::percent(80),
|
||||
quorum: Decimal::percent(20),
|
||||
};
|
||||
let (flex_addr, _) = setup_test_case(&mut app, threshold, voting_period, init_funds, false);
|
||||
let proposal = pay_somebody_proposal();
|
||||
|
||||
let err = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap());
|
||||
|
||||
let err = app
|
||||
.execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap());
|
||||
|
||||
let err = app
|
||||
.execute_contract(Addr::unchecked(SOMEBODY), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap_err();
|
||||
assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap());
|
||||
|
||||
let proposer = TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string();
|
||||
let res = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(&proposer),
|
||||
flex_addr.clone(),
|
||||
&proposal,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
res.custom_attrs(1),
|
||||
[
|
||||
("action", "propose"),
|
||||
("sender", &proposer),
|
||||
("proposal_id", "1"),
|
||||
("status", "Open"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proposal_queries() {
|
||||
let init_funds = coins(10, "BTC");
|
||||
@@ -819,17 +880,13 @@ mod tests {
|
||||
|
||||
// create proposal with 1 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER1);
|
||||
let proposal_id1: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
|
||||
// another proposal immediately passes
|
||||
app.update_block(next_block);
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER4), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER4);
|
||||
let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
|
||||
// expire them both
|
||||
@@ -837,9 +894,7 @@ mod tests {
|
||||
|
||||
// add one more open proposal, 2 votes
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER2), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER2);
|
||||
let proposal_id3: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
let proposed_at = app.block_info();
|
||||
|
||||
@@ -914,9 +969,7 @@ mod tests {
|
||||
|
||||
// create proposal with 0 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER);
|
||||
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
@@ -1017,22 +1070,6 @@ mod tests {
|
||||
assert_eq!(ContractError::NotOpen {}, err.downcast().unwrap());
|
||||
|
||||
// query individual votes
|
||||
// initial (with 0 weight)
|
||||
let voter = OWNER.into();
|
||||
let vote: VoteResponse = app
|
||||
.wrap()
|
||||
.query_wasm_smart(&flex_addr, &QueryMsg::Vote { proposal_id, voter })
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vote.vote.unwrap(),
|
||||
VoteInfo {
|
||||
proposal_id,
|
||||
voter: OWNER.into(),
|
||||
vote: Vote::Yes,
|
||||
weight: 0
|
||||
}
|
||||
);
|
||||
|
||||
// nay sayer
|
||||
let voter = VOTER2.into();
|
||||
let vote: VoteResponse = app
|
||||
@@ -1059,9 +1096,7 @@ mod tests {
|
||||
|
||||
// create proposal with 0 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER);
|
||||
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
@@ -1109,9 +1144,7 @@ mod tests {
|
||||
|
||||
// create proposal with 0 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER);
|
||||
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
@@ -1215,9 +1248,7 @@ mod tests {
|
||||
|
||||
// create proposal with 0 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER);
|
||||
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
@@ -1286,9 +1317,7 @@ mod tests {
|
||||
|
||||
// create proposal with 0 vote power
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(OWNER), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, OWNER);
|
||||
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
@@ -1338,9 +1367,7 @@ mod tests {
|
||||
|
||||
// VOTER1 starts a proposal to send some tokens (1/4 votes)
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER1);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
let prop_status = |app: &App, proposal_id: u64| -> Status {
|
||||
@@ -1400,9 +1427,7 @@ mod tests {
|
||||
|
||||
// make a second proposal
|
||||
let proposal2 = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER1), flex_addr.clone(), &proposal2, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal2, VOTER1);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
|
||||
@@ -1472,14 +1497,7 @@ mod tests {
|
||||
msgs: vec![update_msg],
|
||||
latest: None,
|
||||
};
|
||||
let res = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(VOTER1),
|
||||
flex_addr.clone(),
|
||||
&update_proposal,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), update_proposal, VOTER1);
|
||||
// Get the proposal id from the logs
|
||||
let update_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
|
||||
@@ -1488,14 +1506,7 @@ mod tests {
|
||||
|
||||
// VOTER1 starts a proposal to send some tokens
|
||||
let cash_proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(
|
||||
Addr::unchecked(VOTER1),
|
||||
flex_addr.clone(),
|
||||
&cash_proposal,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), cash_proposal, VOTER1);
|
||||
// Get the proposal id from the logs
|
||||
let cash_proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
assert_ne!(cash_proposal_id, update_proposal_id);
|
||||
@@ -1584,9 +1595,7 @@ mod tests {
|
||||
|
||||
// VOTER3 starts a proposal to send some tokens (3/12 votes)
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER3);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
let prop_status = |app: &App| -> Status {
|
||||
@@ -1628,9 +1637,7 @@ mod tests {
|
||||
|
||||
// new proposal can be passed single-handedly by newbie
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(newbie), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, newbie);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id2: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
|
||||
@@ -1667,9 +1674,7 @@ mod tests {
|
||||
|
||||
// VOTER3 starts a proposal to send some tokens (3 votes)
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER3), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER3);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
let prop_status = |app: &App| -> Status {
|
||||
@@ -1737,9 +1742,7 @@ mod tests {
|
||||
|
||||
// create proposal
|
||||
let proposal = pay_somebody_proposal();
|
||||
let res = app
|
||||
.execute_contract(Addr::unchecked(VOTER5), flex_addr.clone(), &proposal, &[])
|
||||
.unwrap();
|
||||
let res = propose_and_vote(&mut app, flex_addr.clone(), proposal, VOTER5);
|
||||
// Get the proposal id from the logs
|
||||
let proposal_id: u64 = res.custom_attrs(1)[2].value.parse().unwrap();
|
||||
let prop_status = |app: &App| -> Status {
|
||||
|
||||
@@ -14,6 +14,9 @@ pub enum ContractError {
|
||||
#[error("Group contract invalid address '{addr}'")]
|
||||
InvalidGroup { addr: String },
|
||||
|
||||
#[error("Coconut bandwidth contract address not found")]
|
||||
InvalidCoconutBandwidth {},
|
||||
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized {},
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -11,6 +12,7 @@ pub struct Config {
|
||||
pub max_voting_period: Duration,
|
||||
// Total weight and voters are queried from this contract
|
||||
pub group_addr: Cw4Contract,
|
||||
pub coconut_bandwidth_addr: Addr,
|
||||
}
|
||||
|
||||
// unique items
|
||||
|
||||
@@ -16,7 +16,6 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
cosmwasm-std = { version = "1.0.0 "}
|
||||
cw-storage-plus = { version = "0.13.4", features = ["iterator"] }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::queued_migrations::migrate_config_from_env;
|
||||
use crate::storage::{
|
||||
account_from_address, locked_pledge_cap, update_locked_pledge_cap, ADMIN,
|
||||
MIXNET_CONTRACT_ADDRESS,
|
||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
};
|
||||
use crate::vesting::{populate_vesting_periods, Account};
|
||||
use config::defaults::MIX_DENOM;
|
||||
use cosmwasm_std::{
|
||||
coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
||||
Response, Timestamp, Uint128,
|
||||
@@ -36,11 +36,13 @@ pub fn instantiate(
|
||||
// ADMIN is set to the address that instantiated the contract, TODO: make this updatable
|
||||
ADMIN.save(deps.storage, &info.sender.to_string())?;
|
||||
MIXNET_CONTRACT_ADDRESS.save(deps.storage, &msg.mixnet_contract_address)?;
|
||||
MIX_DENOM.save(deps.storage, &msg.mix_denom)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
migrate_config_from_env(_deps, _env, _msg)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
@@ -167,11 +169,9 @@ pub fn try_withdraw_vested_coins(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
if amount.denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom(
|
||||
amount.denom,
|
||||
MIX_DENOM.base.to_string(),
|
||||
));
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
if amount.denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom(amount.denom, mix_denom));
|
||||
}
|
||||
|
||||
let address = info.sender.clone();
|
||||
@@ -245,7 +245,8 @@ pub fn try_bond_gateway(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let pledge = validate_funds(&[amount])?;
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
let pledge = validate_funds(&[amount], mix_denom)?;
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
|
||||
}
|
||||
@@ -285,7 +286,8 @@ pub fn try_bond_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let pledge = validate_funds(&[amount])?;
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
let pledge = validate_funds(&[amount], mix_denom)?;
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_bond_mixnode(mix_node, owner_signature, pledge, &env, deps.storage)
|
||||
}
|
||||
@@ -345,7 +347,8 @@ fn try_delegate_to_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let amount = validate_funds(&[amount])?;
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
let amount = validate_funds(&[amount], mix_denom)?;
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
||||
}
|
||||
@@ -396,6 +399,7 @@ fn try_create_periodic_vesting_account(
|
||||
if info.sender != ADMIN.load(deps.storage)? {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let mix_denom = MIX_DENOM.load(deps.storage)?;
|
||||
|
||||
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
|
||||
if account_exists {
|
||||
@@ -406,7 +410,7 @@ fn try_create_periodic_vesting_account(
|
||||
|
||||
let vesting_spec = vesting_spec.unwrap_or_default();
|
||||
|
||||
let coin = validate_funds(&info.funds)?;
|
||||
let coin = validate_funds(&info.funds, mix_denom)?;
|
||||
|
||||
let owner_address = deps.api.addr_validate(owner_address)?;
|
||||
let staking_address = if let Some(staking_address) = staking_address {
|
||||
@@ -573,7 +577,7 @@ pub fn try_get_vested_coins(
|
||||
deps: Deps<'_>,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||
account.get_vested_coins(block_time, &env)
|
||||
account.get_vested_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
pub fn try_get_vesting_coins(
|
||||
@@ -583,7 +587,7 @@ pub fn try_get_vesting_coins(
|
||||
deps: Deps<'_>,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
||||
account.get_vesting_coins(block_time, &env)
|
||||
account.get_vesting_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
pub fn try_get_start_time(
|
||||
@@ -630,7 +634,7 @@ pub fn try_get_delegated_vesting(
|
||||
account.get_delegated_vesting(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
|
||||
fn validate_funds(funds: &[Coin], mix_denom: String) -> Result<Coin, ContractError> {
|
||||
if funds.is_empty() || funds[0].amount.is_zero() {
|
||||
return Err(ContractError::EmptyFunds);
|
||||
}
|
||||
@@ -639,11 +643,8 @@ fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
|
||||
return Err(ContractError::MultipleDenoms);
|
||||
}
|
||||
|
||||
if funds[0].denom != MIX_DENOM.base {
|
||||
return Err(ContractError::WrongDenom(
|
||||
funds[0].denom.clone(),
|
||||
MIX_DENOM.base.to_string(),
|
||||
));
|
||||
if funds[0].denom != mix_denom {
|
||||
return Err(ContractError::WrongDenom(funds[0].denom.clone(), mix_denom));
|
||||
}
|
||||
|
||||
Ok(funds[0].clone())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
mod queued_migrations;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod traits;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{DepsMut, Env, Response};
|
||||
use vesting_contract_common::MigrateMsg;
|
||||
|
||||
use crate::{errors::ContractError, storage::MIX_DENOM};
|
||||
|
||||
pub fn migrate_config_from_env(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
MIX_DENOM.save(deps.storage, &msg.mix_denom)?;
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -17,6 +17,7 @@ const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
|
||||
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
|
||||
pub const ADMIN: Item<'_, String> = Item::new("adm");
|
||||
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
|
||||
pub const MIX_DENOM: Item<'_, String> = Item::new("den");
|
||||
pub const LOCKED_PLEDGE_CAP: Item<'_, Uint128> = Item::new("lck");
|
||||
|
||||
pub fn locked_pledge_cap(storage: &dyn Storage) -> Uint128 {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user