Compare commits

..

24 Commits

Author SHA1 Message Date
tommy 0881d4cbc3 Revert "Validator Command Binary - Draft"
This reverts commit 3429e0a8e9.
2022-08-05 12:19:47 +02:00
tommy 3429e0a8e9 Validator Command Binary - Draft
- validator-action rust cli tool
- enables ease of use functionality to do commands to respective environments
2022-08-04 12:57:56 +02:00
Bogdan-Ștefan Neacşu 9c19ae322d Bump regex version to fix dependabot (#1488) 2022-08-03 12:45:21 +03:00
Bogdan-Ștefan Neacşu 07893828d8 Fix NC filter for domains suffix-only domains (#1487)
* Fix NC filter for domains suffix-only domains

* Update CHANGELOG

* Fix unit test for filter

Some domains might be composed of the suffix only.

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

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

* feat(wallet-receive): some ui work

* feat(wallet): simple modal component

show or not the Ok button based on onOk props

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

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

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

* Remove malicious nodes migration

* Reduce total delegation, before adding to it

* Blacklist malicious nodes, prevent future bonding

* Blacklisted gets no reward, enable compound

* Add GetBlacklistedNodes message

* Rebase on develop

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

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

* Explorer api uses env

* Mainnet and qa env files

* Set CONFIGURED on the mainnet defaulting

* Gateway uses env

* Mixnode uses env

* Wallet error simplification

* Network requester takes only mainnet client address

* Validator api uses env

* Mixnet contract uses denom from instantiate

* Vesting contract uses denom from instantiate

* More contract test refactoring

* Coconut bandwidth contract uses denom from instantiate

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

* More remove from Cargos and one missed DEFAULT_NETWORK

* Refactor some other missed places

* Minor fixes

* Test and clippy fixes

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

* use filter schema

* filter mixnode based on selected filters

* only show filters on the mixnode page

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

* prevent additional request when status changes

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

* Store spent credentials support

* Add query endpoint for spent credentials

* Proposals allowed only from special (contract) address

* Include check for admin in tests

* Create proposal from CBC

* Refactor into coconut integration tests

* Create proposal with spend credential integration test

* Resolve mixnet warnings

* Refactor to re-enable build

* Call CBC from gateway and remove validator-api workaround

* Include migration for the first deployment of multisig

* Fix bug in proposal id parsing

* Remove more validator-api create proposal code

* Check for InProgress status of credential

* Check the proposed voucher value

* Unwrapping cosmos msg from gateway

* Improve error message

* More nit fixing

* Test getting validator api cosmos address endpoint

* Refactor to prepare for distributed comm channel

* Refactor coconut e2e test for reuse

* Verification of cred endpoint test

* Update CHANGELOG
2022-07-18 12:37:07 +03:00
261 changed files with 6156 additions and 4750 deletions
+12
View File
@@ -1,6 +1,7 @@
name: CI for Network Explorer
on:
workflow_dispatch:
push:
paths:
- 'explorer/**'
@@ -75,3 +76,14 @@ jobs:
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
- name: Deploy
if: github.event_name == 'workflow_dispatch'
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
EXCLUDE: "/dist/, /node_modules/"
+27 -5
View File
@@ -8,10 +8,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
- explorer-api: add apy values to `mix_nodes` endpoint
@@ -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,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- native & socks5 clients: rerun init will now reuse previous gateway configuration instead of failing ([#1353])
- native & socks5 clients: deduplicate big chunks of init logic
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
- network-requester: fix filter for suffix-only domains ([#1487])
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
- all: updated `rocket` to `0.5.0-rc.2`.
@@ -49,6 +48,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 +72,25 @@ 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
## [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
+6 -6
View File
@@ -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",
@@ -5387,7 +5389,6 @@ version = "1.0.1"
dependencies = [
"async-trait",
"log",
"network-defaults",
"reqwest",
"serde",
"serde_json",
@@ -6335,7 +6336,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+1 -1
View File
@@ -1,5 +1,5 @@
test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
test: build clippy-all cargo-test wasm fmt
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
+1 -2
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::*;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -366,7 +365,7 @@ impl<T: NymConfig> Default for Client<T> {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
disabled_credentials_mode: true,
validator_api_urls: default_api_endpoints(),
validator_api_urls: vec![],
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_encryption_key_file: Default::default(),
+9 -4
View File
@@ -4,7 +4,7 @@
use crate::error::Result;
use crate::{MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use network_defaults::{DEFAULT_NETWORK, MIX_DENOM, VOUCHER_INFO};
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use std::str::FromStr;
use url::Url;
use validator_client::nymd;
@@ -13,18 +13,23 @@ use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
pub(crate) struct Client {
nymd_client: NymdClient<SigningNymdClient>,
mix_denom_base: String,
}
impl Client {
pub fn new() -> Self {
let nymd_url = Url::from_str(NYMD_URL).unwrap();
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
let config = nymd::Config::try_from_nym_network_details(&DEFAULT_NETWORK.details())
let network_details = NymNetworkDetails::new_from_env();
let config = nymd::Config::try_from_nym_network_details(&network_details)
.expect("failed to construct valid validator client config with the provided network");
let nymd_client =
NymdClient::connect_with_mnemonic(config, nymd_url.as_ref(), mnemonic, None).unwrap();
Client { nymd_client }
Client {
nymd_client,
mix_denom_base: network_details.chain_details.mix_denom.base,
}
}
pub async fn deposit(
@@ -34,7 +39,7 @@ impl Client {
encryption_key: String,
fee: Option<Fee>,
) -> Result<String> {
let amount = Coin::new(amount as u128, MIX_DENOM.base.to_string());
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
Ok(self
.nymd_client
.deposit(
-1
View File
@@ -22,7 +22,6 @@ url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
+10 -4
View File
@@ -3,7 +3,6 @@
use crate::client::config::{Config, SocketType};
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
#[cfg(not(feature = "coconut"))]
@@ -28,7 +27,6 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -46,8 +44,6 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -113,6 +113,12 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR)
.expect("api validator not set");
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if args.disable_socket {
+1 -10
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::{defaults::default_api_endpoints, NymConfig};
use config::NymConfig;
use version_checker::Version;
use clap::Args;
@@ -105,15 +105,6 @@ fn minor_0_12_upgrade(
print_start_upgrade(&config_version, &to_version);
println!(
"Setting validator API endpoints to {:?}",
default_api_endpoints()
);
config
.get_base_mut()
.set_custom_validator_apis(default_api_endpoints());
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
+2 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
pub mod commands;
@@ -9,11 +10,11 @@ pub mod websocket;
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
}
-1
View File
@@ -13,7 +13,6 @@ path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0"
futures = "0.3"
log = "0.4"
pin-project = "1.0"
+9 -5
View File
@@ -3,7 +3,6 @@
use crate::client::config::Config;
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
pub mod init;
@@ -28,7 +27,6 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -46,8 +44,6 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
@@ -58,6 +54,10 @@ fn long_version_static() -> &'static str {
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Commands,
}
@@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) {
}
}
fn parse_validators(raw: &str) -> Vec<Url> {
pub fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
@@ -112,6 +112,10 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::API_VALIDATOR) {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if let Some(port) = args.port {
+1 -10
View File
@@ -3,7 +3,7 @@
use crate::client::config::{Config, MISSING_VALUE};
use config::{defaults::default_api_endpoints, NymConfig};
use config::NymConfig;
use version_checker::Version;
use clap::Args;
@@ -104,15 +104,6 @@ fn minor_0_12_upgrade(
print_start_upgrade(&config_version, &to_version);
println!(
"Setting validator API endpoints to {:?}",
default_api_endpoints()
);
config
.get_base_mut()
.set_custom_validator_apis(default_api_endpoints());
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
+2 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use network_defaults::setup_env;
pub mod client;
mod commands;
@@ -9,11 +10,11 @@ pub mod socks;
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
}
+7 -3
View File
@@ -5,7 +5,7 @@ use futures::StreamExt;
use log::*;
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use socks5_requests::Response;
use socks5_requests::Message;
pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender,
@@ -44,12 +44,16 @@ impl MixnetResponseListener {
warn!("this message had a surb - we didn't do anything with it");
}
let response = match Response::try_from_bytes(&raw_message) {
let response = match Message::try_from_bytes(&raw_message) {
Err(err) => {
warn!("failed to parse received response - {:?}", err);
return;
}
Ok(data) => data,
Ok(Message::Request(_)) => {
warn!("unexpected request");
return;
}
Ok(Message::Response(data)) => data,
};
self.controller_sender
@@ -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
}
}
@@ -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 {
-1
View File
@@ -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" }
+11 -7
View File
@@ -1,4 +1,3 @@
use config::defaults::DEFAULT_NETWORK;
use subtle_encoding::bech32;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -15,16 +14,15 @@ pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
}
}
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
pub fn validate_bech32_prefix(bech32_prefix: &str, address: &str) -> Result<(), Bech32Error> {
let prefix = try_bech32_decode(address)?;
if prefix == DEFAULT_NETWORK.bech32_prefix() {
if prefix == bech32_prefix {
Ok(())
} else {
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not {}",
DEFAULT_NETWORK.bech32_prefix(),
prefix
bech32_prefix, prefix
)))
}
}
@@ -33,6 +31,8 @@ pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
mod tests {
use super::*;
const TEST_BECH32_PREFIX: &str = "n";
mod decoding_bech32_addresses {
use super::*;
@@ -71,9 +71,12 @@ mod tests {
assert_eq!(
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not punk",
DEFAULT_NETWORK.bech32_prefix()
TEST_BECH32_PREFIX
))),
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0"
)
)
}
@@ -82,6 +85,7 @@ mod tests {
assert_eq!(
Ok(()),
validate_bech32_prefix(
TEST_BECH32_PREFIX,
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"
)
)
+1
View File
@@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
cfg-if = "1.0.0"
dotenv = "0.15.0"
hex-literal = "0.3.3"
once_cell = "1.7.2"
serde = {version = "1.0", features = ["derive"]}
-11
View File
@@ -1,11 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
fn main() {
match option_env!("NETWORK") {
None | Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
_ => panic!("No such network"),
}
}
+63 -61
View File
@@ -3,6 +3,7 @@
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, path::PathBuf};
use url::Url;
pub mod all;
@@ -10,36 +11,10 @@ pub mod eth_contract;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
// The set of defaults that are decided at compile time. Ideally we want to reduce these to a
// minimum.
// Keep DENOM around mostly for use in contracts. (TODO: consider moving it there, or renaming?)
cfg_if::cfg_if! {
if #[cfg(network = "mainnet")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::MAINNET;
pub const MIX_DENOM: DenomDetails = mainnet::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = mainnet::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
} else if #[cfg(network = "qa")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::QA;
pub const MIX_DENOM: DenomDetails = qa::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = qa::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = qa::_ETH_ERC20_CONTRACT_ADDRESS;
} else if #[cfg(network = "sandbox")] {
pub const DEFAULT_NETWORK: all::Network = all::Network::SANDBOX;
pub const MIX_DENOM: DenomDetails = sandbox::MIX_DENOM;
pub const STAKE_DENOM: DenomDetails = sandbox::STAKE_DENOM;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = sandbox::_ETH_ERC20_CONTRACT_ADDRESS;
}
}
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
pub const ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_ERC20_CONTRACT_ADDRESS;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ChainDetails {
@@ -82,23 +57,56 @@ impl NymNetworkDetails {
NymNetworkDetails::default()
}
pub fn new_qa() -> Self {
(&*QA_DEFAULTS).into()
}
pub fn new_sandbox() -> Self {
(&*SANDBOX_DEFAULTS).into()
pub fn new_from_env() -> Self {
NymNetworkDetails::new()
.with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
)
.with_mix_denom(DenomDetailsOwned {
base: var(var_names::MIX_DENOM).expect("mix denomination base not set"),
display: var(var_names::MIX_DENOM_DISPLAY)
.expect("mix denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
})
.with_stake_denom(DenomDetailsOwned {
base: var(var_names::STAKE_DENOM).expect("stake denomination base not set"),
display: var(var_names::STAKE_DENOM_DISPLAY)
.expect("stake denomination display not set"),
display_exponent: var(var_names::DENOMS_EXPONENT)
.expect("denomination exponent not set")
.parse()
.expect("denomination exponent is not u32"),
})
.with_validator_endpoint(ValidatorDetails::new(
var(var_names::NYMD_VALIDATOR).expect("nymd validator not set"),
Some(var(var_names::API_VALIDATOR).expect("api validator not set")),
))
.with_mixnet_contract(Some(
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
))
.with_vesting_contract(Some(
var(var_names::VESTING_CONTRACT_ADDRESS).expect("vesting contract not set"),
))
.with_bandwidth_claim_contract(Some(
var(var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS)
.expect("bandwidth claim contract not set"),
))
.with_coconut_bandwidth_contract(Some(
var(var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
.expect("coconut bandwidth contract not set"),
))
.with_multisig_contract(Some(
var(var_names::MULTISIG_CONTRACT_ADDRESS).expect("multisig contract not set"),
))
}
pub fn new_mainnet() -> Self {
(&*MAINNET_DEFAULTS).into()
}
pub fn current_default() -> Self {
// backwards compatibility reasons
DEFAULT_NETWORK.details()
}
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
@@ -333,27 +341,21 @@ impl ValidatorDetails {
}
}
pub fn default_statistics_service_url() -> Url {
DEFAULT_NETWORK
.statistics_service_url()
.parse()
.expect("the provided statistics service url is invalid!")
}
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.map(ValidatorDetails::nymd_url)
.collect()
}
pub fn default_api_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
.iter()
.filter_map(ValidatorDetails::api_url)
.collect()
pub fn setup_env(config_env_file: Option<PathBuf>) {
match std::env::var(var_names::CONFIGURED) {
// if the configuration is not already set in the env vars
Err(std::env::VarError::NotPresent) => {
if let Some(config_env_file) = config_env_file {
dotenv::from_path(config_env_file)
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
}
// Name of the event triggered by the eth contract. If the event name is changed,
+44 -4
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::var_names;
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
@@ -24,9 +25,48 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
pub const NYMD_VALIDATOR: &str = "https://rpc.nyx.nodes.guru/";
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://rpc.nyx.nodes.guru/",
Some("https://validator.nymtech.net/api/"),
)]
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
}
pub fn export_to_env() {
std::env::set_var(var_names::CONFIGURED, "true");
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
std::env::set_var(
var_names::DENOMS_EXPONENT,
STAKE_DENOM.display_exponent.to_string(),
);
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
std::env::set_var(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
std::env::set_var(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
std::env::set_var(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// Environment variable that, if set, shows the environment is currently configured
pub const CONFIGURED: &str = "CONFIGURED";
pub const BECH32_PREFIX: &str = "BECH32_PREFIX";
pub const MIX_DENOM: &str = "MIX_DENOM";
pub const MIX_DENOM_DISPLAY: &str = "MIX_DENOM_DISPLAY";
pub const STAKE_DENOM: &str = "STAKE_DENOM";
pub const STAKE_DENOM_DISPLAY: &str = "STAKE_DENOM_DISPLAY";
pub const DENOMS_EXPONENT: &str = "DENOMS_EXPONENT";
pub const MIXNET_CONTRACT_ADDRESS: &str = "MIXNET_CONTRACT_ADDRESS";
pub const VESTING_CONTRACT_ADDRESS: &str = "VESTING_CONTRACT_ADDRESS";
pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "BANDWIDTH_CLAIM_CONTRACT_ADDRESS";
pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT_ADDRESS";
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
pub const NYMD_VALIDATOR: &str = "NYMD_VALIDATOR";
pub const API_VALIDATOR: &str = "API_VALIDATOR";
+1 -2
View File
@@ -34,8 +34,7 @@ mod error;
mod impls;
mod proofs;
mod scheme;
#[cfg(test)]
mod tests;
pub mod tests;
mod traits;
mod utils;
+3 -63
View File
@@ -1,23 +1,13 @@
use crate::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign,
prove_bandwidth_credential, setup, ttp_keygen, verify_credential, CoconutError, Signature,
SignatureShare, VerificationKey,
aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen,
verify_credential, CoconutError, VerificationKey,
};
use itertools::izip;
#[test]
fn main() -> Result<(), CoconutError> {
let params = setup(5)?;
let public_attributes = params.n_random_scalars(2);
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(&params, &private_attributes, &public_attributes)?;
// generate_keys
let coconut_keypairs = ttp_keygen(&params, 2, 3)?;
@@ -30,58 +20,8 @@ fn main() -> Result<(), CoconutError> {
// aggregate verification keys
let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
&params,
&keypair.secret_key(),
&blind_sign_request,
&public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
&params,
vk,
&private_attributes,
&public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(&public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(&params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
&params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
let theta = theta_from_keys_and_attributes(&params, &coconut_keypairs, &public_attributes)?;
// Verify credentials
assert!(verify_credential(
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::*;
use itertools::izip;
pub fn theta_from_keys_and_attributes(
params: &Parameters,
coconut_keypairs: &Vec<KeyPair>,
public_attributes: &Vec<PublicAttribute>,
) -> Result<Theta, CoconutError> {
let serial_number = params.random_scalar();
let binding_number = params.random_scalar();
let private_attributes = vec![serial_number, binding_number];
// generate commitment
let (commitments_openings, blind_sign_request) =
prepare_blind_sign(params, &private_attributes, public_attributes)?;
let verification_keys: Vec<VerificationKey> = coconut_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
// aggregate verification keys
let indices: Vec<u64> = coconut_keypairs
.iter()
.enumerate()
.map(|(idx, _)| (idx + 1) as u64)
.collect();
let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?;
// generate blinded signatures
let mut blinded_signatures = Vec::new();
for keypair in coconut_keypairs {
let blinded_signature = blind_sign(
params,
&keypair.secret_key(),
&blind_sign_request,
public_attributes,
)?;
blinded_signatures.push(blinded_signature)
}
// Unblind
let unblinded_signatures: Vec<Signature> =
izip!(blinded_signatures.iter(), verification_keys.iter())
.map(|(s, vk)| {
s.unblind(
params,
vk,
&private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
&commitments_openings,
)
.unwrap()
})
.collect();
// Aggregate signatures
let signature_shares: Vec<SignatureShare> = unblinded_signatures
.iter()
.enumerate()
.map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64))
.collect();
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(&private_attributes);
attributes.extend_from_slice(public_attributes);
// Randomize credentials and generate any cryptographic material to verify them
let signature =
aggregate_signature_shares(params, &verification_key, &attributes, &signature_shares)?;
// Generate cryptographic material to verify them
let theta = prove_bandwidth_credential(
params,
&verification_key,
&signature,
serial_number,
binding_number,
)?;
Ok(theta)
}
+2
View File
@@ -1 +1,3 @@
#[cfg(test)]
mod e2e;
pub mod helpers;
-2
View File
@@ -17,5 +17,3 @@ serde_json = "1"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
tokio = { version = "1.19.1", features = [ "time" ] }
network-defaults = { path = "../network-defaults" }
+6 -2
View File
@@ -34,12 +34,16 @@ pub enum StatsData {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsGatewayData {
pub gateway_id: String,
pub inbox_count: u32,
}
impl StatsGatewayData {
pub fn new(inbox_count: u32) -> Self {
StatsGatewayData { inbox_count }
pub fn new(gateway_id: String, inbox_count: u32) -> Self {
StatsGatewayData {
gateway_id,
inbox_count,
}
}
}
+34 -9
View File
@@ -50,7 +50,6 @@ name = "bandwidth-claim"
version = "1.0.0"
dependencies = [
"bandwidth-claim-contract",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"schemars",
@@ -221,12 +220,11 @@ version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth-contract-common",
"config",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
@@ -237,10 +235,32 @@ name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"multisig-contract-common",
"schemars",
"serde",
]
[[package]]
name = "coconut-test"
version = "0.1.0"
dependencies = [
"bandwidth-claim-contract",
"coconut-bandwidth",
"coconut-bandwidth-contract-common",
"cosmwasm-std",
"cosmwasm-storage",
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw-utils",
"cw3-flex-multisig",
"cw4-group",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -592,6 +612,12 @@ dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dyn-clone"
version = "1.0.4"
@@ -1056,7 +1082,6 @@ dependencies = [
"cosmwasm-std",
"fixed",
"log",
"network-defaults",
"schemars",
"serde",
"serde_repr",
@@ -1082,6 +1107,7 @@ name = "network-defaults"
version = "0.1.0"
dependencies = [
"cfg-if",
"dotenv",
"hex-literal",
"once_cell",
"serde",
@@ -1366,18 +1392,18 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rfc6979"
@@ -1828,7 +1854,6 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract"
version = "1.0.1"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"mixnet-contract-common",
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group"]
members = ["bandwidth-claim", "coconut-bandwidth", "mixnet", "vesting", "multisig/cw3-flex-multisig", "multisig/cw4-group", "coconut-test"]
[profile.release]
opt-level = 3
-2
View File
@@ -9,8 +9,6 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dev-dependencies]
config = { path = "../../common/config"}
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
+4 -3
View File
@@ -62,10 +62,11 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
pub mod tests {
use super::*;
use bandwidth_claim_contract::payment::PagedPaymentResponse;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn initialize_contract() {
let mut deps = mock_dependencies();
@@ -91,11 +92,11 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, MIX_DENOM.base)
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
+1 -4
View File
@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
config = { path = "../../common/config"}
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
@@ -21,6 +21,3 @@ cw-controllers = "0.13.4"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
[dev-dependencies]
cw-multi-test = { version = "0.13.2" }
+23 -77
View File
@@ -1,11 +1,14 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use coconut_bandwidth_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::error::ContractError;
use crate::queries::{query_all_spent_credentials_paged, query_spent_credential};
use crate::state::{Config, ADMIN, CONFIG};
use crate::transactions;
@@ -22,12 +25,14 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
let pool_addr = deps.api.addr_validate(&msg.pool_addr)?;
let mix_denom = msg.mix_denom;
ADMIN.set(deps.branch(), Some(multisig_addr.clone()))?;
let cfg = Config {
multisig_addr,
pool_addr,
mix_denom,
};
CONFIG.save(deps.storage, &cfg)?;
@@ -44,13 +49,23 @@ pub fn execute(
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::DepositFunds { data } => transactions::deposit_funds(deps, env, info, data),
ExecuteMsg::SpendCredential { data } => {
transactions::spend_credential(deps, env, info, data)
}
ExecuteMsg::ReleaseFunds { funds } => transactions::release_funds(deps, env, info, funds),
}
}
#[entry_point]
pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult<Binary> {
unimplemented!();
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetAllSpentCredentials { limit, start_after } => to_binary(
&query_all_spent_credentials_paged(deps, start_after, limit)?,
),
QueryMsg::GetSpentCredential {
blinded_serial_number,
} => to_binary(&query_spent_credential(deps, blinded_serial_number)?),
}
}
#[entry_point]
@@ -61,12 +76,10 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respon
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use crate::support::tests::helpers::*;
use coconut_bandwidth_contract_common::deposit::DepositData;
use config::defaults::MIX_DENOM;
use cosmwasm_std::coins;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, Addr};
use cw_multi_test::Executor;
#[test]
fn initialize_contract() {
@@ -75,6 +88,7 @@ mod tests {
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
@@ -83,80 +97,12 @@ mod tests {
// Contract balance should be 0
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_MIX_DENOM),
vec![deps
.as_ref()
.querier
.query_balance(env.contract.address, MIX_DENOM.base)
.query_balance(env.contract.address, TEST_MIX_DENOM)
.unwrap()]
);
}
#[test]
fn deposit_and_release() {
let init_funds = coins(10, MIX_DENOM.base);
let deposit_funds = coins(1, MIX_DENOM.base);
let release_funds = coins(2, MIX_DENOM.base);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, MIX_DENOM.base).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
}
+5 -4
View File
@@ -5,8 +5,6 @@ use cosmwasm_std::StdError;
use cw_controllers::AdminError;
use thiserror::Error;
use config::defaults::MIX_DENOM;
/// Custom errors for contract failure conditions.
///
/// Add any other custom errors you like here.
@@ -22,12 +20,15 @@ pub enum ContractError {
#[error("No coin was sent for voucher")]
NoCoin,
#[error("Wrong coin denomination, you must send {}", MIX_DENOM.base)]
WrongDenom,
#[error("Wrong coin denomination, you must send {mix_denom}")]
WrongDenom { mix_denom: String },
#[error("There aren't enough funds in the contract")]
NotEnoughFunds,
#[error("Credential already spent or in process of spending")]
DuplicateBlindedSerialNumber,
#[error("{0}")]
Admin(#[from] AdminError),
}
+3 -1
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
pub mod contract;
mod error;
pub mod error;
mod queries;
mod state;
mod storage;
mod support;
mod transactions;
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
PagedSpendCredentialResponse, SpendCredential, SpendCredentialResponse,
};
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use crate::storage::{self, SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT, SPEND_CREDENTIAL_PAGE_MAX_LIMIT};
pub(crate) fn query_all_spent_credentials_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedSpendCredentialResponse> {
let limit = limit
.unwrap_or(SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT)
.min(SPEND_CREDENTIAL_PAGE_MAX_LIMIT) as usize;
let start = start_after.as_deref().map(Bound::exclusive);
let nodes = storage::spent_credentials()
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<SpendCredential>>>()?;
let start_next_after = nodes
.last()
.map(|spend_credential| spend_credential.blinded_serial_number().to_string());
Ok(PagedSpendCredentialResponse::new(
nodes,
limit,
start_next_after,
))
}
pub(crate) fn query_spent_credential(
deps: Deps<'_>,
blinded_serial_number: String,
) -> StdResult<SpendCredentialResponse> {
let spend_credential =
storage::spent_credentials().may_load(deps.storage, &blinded_serial_number)?;
Ok(SpendCredentialResponse::new(spend_credential))
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::init_contract;
use crate::transactions::spend_credential;
use cosmwasm_std::testing::{mock_env, mock_info};
#[test]
fn spent_credentials_empty_on_init() {
let deps = init_contract();
let response =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(2)).unwrap();
assert_eq!(0, response.spend_credentials.len());
}
#[test]
fn spent_credentials_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let limit = 2;
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query without explicitly setting a limit
let page1 = query_all_spent_credentials_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(
SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT,
page1.spend_credentials.len() as u32
);
}
#[test]
fn spent_credentials_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
for n in 0..1000 {
let data = spend_credential_data_fixture(&format!("blinded_serial_number{}", n));
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
}
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = SPEND_CREDENTIAL_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.spend_credentials.len() as u32);
}
#[test]
fn spent_credentials_pagination_works() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number1");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
let per_page = 2;
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.spend_credentials.len());
// save another
let data = spend_credential_data_fixture("blinded_serial_number2");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 should have 2 results on it
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number3");
spend_credential(deps.as_mut(), env.clone(), info.clone(), data).unwrap();
// page1 still has 2 results
let page1 =
query_all_spent_credentials_paged(deps.as_ref(), None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.spend_credentials.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_all_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after.clone()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.spend_credentials.len());
let data = spend_credential_data_fixture("blinded_serial_number4");
spend_credential(deps.as_mut(), env, info, data).unwrap();
let page2 = query_all_spent_credentials_paged(
deps.as_ref(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.spend_credentials.len());
}
}
+1
View File
@@ -13,6 +13,7 @@ pub const ADMIN: Admin = Admin::new("admin");
pub struct Config {
pub multisig_addr: Addr,
pub pool_addr: Addr,
pub mix_denom: String,
}
pub const CONFIG: Item<Config> = Item::new("config");
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::SpendCredential;
use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex};
// storage prefixes
const SPEND_CREDENTIAL_PK_NAMESPACE: &str = "sc";
const SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE: &str = "scn";
// paged retrieval limits for all queries and transactions
pub(crate) const SPEND_CREDENTIAL_PAGE_MAX_LIMIT: u32 = 75;
pub(crate) const SPEND_CREDENTIAL_PAGE_DEFAULT_LIMIT: u32 = 50;
pub(crate) struct SpendCredentialIndex<'a> {
pub(crate) blinded_serial_number: UniqueIndex<'a, String, SpendCredential>,
}
// IndexList is just boilerplate code for fetching a struct's indexes
// note that from my understanding this will be converted into a macro at some point in the future
impl<'a> IndexList<SpendCredential> for SpendCredentialIndex<'a> {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<SpendCredential>> + '_> {
let v: Vec<&dyn Index<SpendCredential>> = vec![&self.blinded_serial_number];
Box::new(v.into_iter())
}
}
// gateways() is the storage access function.
pub(crate) fn spent_credentials<'a>(
) -> IndexedMap<'a, &'a str, SpendCredential, SpendCredentialIndex<'a>> {
let indexes = SpendCredentialIndex {
blinded_serial_number: UniqueIndex::new(
|d| d.blinded_serial_number().to_string(),
SPEND_CREDENTIAL_BLINDED_SERIAL_NO_IDX_NAMESPACE,
),
};
IndexedMap::new(SPEND_CREDENTIAL_PK_NAMESPACE, indexes)
}
// currently not used outside tests
#[cfg(test)]
mod tests {
use super::super::storage;
use crate::storage::SpendCredential;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::TEST_MIX_DENOM;
use coconut_bandwidth_contract_common::spend_credential::SpendCredentialStatus;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
#[test]
fn spend_credential_single_read_retrieval() {
let mut storage = MockStorage::new();
let blind_serial_number1 = "number1";
let blind_serial_number2 = "number2";
let spend1 = fixtures::spend_credential_fixture(blind_serial_number1);
let spend2 = fixtures::spend_credential_fixture(blind_serial_number2);
storage::spent_credentials()
.save(&mut storage, blind_serial_number1, &spend1)
.unwrap();
storage::spent_credentials()
.save(&mut storage, blind_serial_number2, &spend2)
.unwrap();
let res1 = storage::spent_credentials()
.load(&storage, blind_serial_number1)
.unwrap();
let res2 = storage::spent_credentials()
.load(&storage, blind_serial_number2)
.unwrap();
assert_eq!(spend1, res1);
assert_eq!(spend2, res2);
}
#[test]
fn mark_as_spent_credential() {
let mut mock_storage = MockStorage::new();
let funds = Coin::new(100, TEST_MIX_DENOM);
let blind_serial_number = "blind_serial_number";
let gateway_cosmos_address: Addr = Addr::unchecked("gateway_cosmos_address");
let res = storage::spent_credentials()
.may_load(&mock_storage, blind_serial_number)
.unwrap();
assert!(res.is_none());
let mut spend_credential = SpendCredential::new(
funds.clone(),
blind_serial_number.to_string(),
gateway_cosmos_address.clone(),
);
spend_credential.mark_as_spent();
storage::spent_credentials()
.save(&mut mock_storage, blind_serial_number, &spend_credential)
.unwrap();
let ret = storage::spent_credentials()
.load(&mock_storage, blind_serial_number)
.unwrap();
assert_eq!(ret, spend_credential);
assert_eq!(ret.status(), SpendCredentialStatus::Spent);
}
}
@@ -1,3 +1,5 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod tests;
@@ -1,45 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
pub mod helpers {
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Addr, Coin, Empty, MemoryStorage, OwnedDeps};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
);
Box::new(contract)
}
}
@@ -0,0 +1,23 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{SpendCredential, SpendCredentialData};
use cosmwasm_std::{Addr, Coin};
pub const TEST_MIX_DENOM: &str = "unym";
pub fn spend_credential_fixture(blinded_serial_number: &str) -> SpendCredential {
SpendCredential::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
Addr::unchecked("gateway_owner_addr"),
)
}
pub fn spend_credential_data_fixture(blinded_serial_number: &str) -> SpendCredentialData {
SpendCredentialData::new(
Coin::new(100, TEST_MIX_DENOM),
blinded_serial_number.to_string(),
"gateway_owner_addr".to_string(),
)
}
@@ -0,0 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
use crate::contract::instantiate;
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Empty, MemoryStorage, OwnedDeps};
use super::fixtures::TEST_MIX_DENOM;
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {
multisig_addr: String::from(MULTISIG_CONTRACT),
pool_addr: String::from(POOL_CONTRACT),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let env = mock_env();
let info = mock_info("creator", &[]);
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
deps
}
@@ -0,0 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod fixtures;
pub mod helpers;
+161 -16
View File
@@ -1,20 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_bandwidth_contract_common::spend_credential::{
to_cosmos_msg, SpendCredential, SpendCredentialData,
};
use cosmwasm_std::{BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Response};
use crate::error::ContractError;
use crate::state::{ADMIN, CONFIG};
use crate::storage;
use coconut_bandwidth_contract_common::deposit::DepositData;
use coconut_bandwidth_contract_common::events::{
DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO,
DEPOSIT_VALUE,
};
use config::defaults::MIX_DENOM;
pub(crate) fn deposit_funds(
_deps: DepsMut<'_>,
deps: DepsMut<'_>,
_env: Env,
info: MessageInfo,
data: DepositData,
@@ -25,8 +28,9 @@ pub(crate) fn deposit_funds(
if info.funds.len() > 1 {
return Err(ContractError::MultipleDenoms);
}
if info.funds[0].denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if info.funds[0].denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let voucher_value = info.funds.last().unwrap();
@@ -39,18 +43,55 @@ pub(crate) fn deposit_funds(
Ok(Response::new().add_event(event))
}
pub(crate) fn spend_credential(
deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
data: SpendCredentialData,
) -> Result<Response, ContractError> {
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if data.funds().denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
if storage::spent_credentials().has(deps.storage, data.blinded_serial_number()) {
return Err(ContractError::DuplicateBlindedSerialNumber);
}
let cfg = CONFIG.load(deps.storage)?;
let gateway_cosmos_address = deps.api.addr_validate(data.gateway_cosmos_address())?;
storage::spent_credentials().save(
deps.storage,
data.blinded_serial_number(),
&SpendCredential::new(
data.funds().to_owned(),
data.blinded_serial_number().to_owned(),
gateway_cosmos_address,
),
)?;
let msg = to_cosmos_msg(
data.funds().clone(),
data.blinded_serial_number().to_string(),
env.contract.address.into_string(),
cfg.multisig_addr.into_string(),
)?;
Ok(Response::new().add_message(msg))
}
pub(crate) fn release_funds(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
funds: Coin,
) -> Result<Response, ContractError> {
if funds.denom != MIX_DENOM.base {
return Err(ContractError::WrongDenom);
let mix_denom = CONFIG.load(deps.storage)?.mix_denom;
if funds.denom != mix_denom {
return Err(ContractError::WrongDenom { mix_denom });
}
let current_balance = deps
.querier
.query_balance(env.contract.address, MIX_DENOM.base)?;
.query_balance(env.contract.address, mix_denom)?;
if funds.amount > current_balance.amount {
return Err(ContractError::NotEnoughFunds);
}
@@ -70,11 +111,13 @@ pub(crate) fn release_funds(
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::helpers;
use crate::support::tests::helpers::{MULTISIG_CONTRACT, POOL_CONTRACT};
use crate::support::tests::fixtures::spend_credential_data_fixture;
use crate::support::tests::helpers::{self, MULTISIG_CONTRACT, POOL_CONTRACT};
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{Coin, CosmosMsg};
use cosmwasm_std::{from_binary, Coin, CosmosMsg, WasmMsg};
use cw_controllers::AdminError;
use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg;
#[test]
fn invalid_deposit() {
@@ -92,7 +135,7 @@ mod tests {
Err(ContractError::NoCoin)
);
let coin = Coin::new(1000000, MIX_DENOM.base);
let coin = Coin::new(1000000, crate::support::tests::fixtures::TEST_MIX_DENOM);
let second_coin = Coin::new(1000000, "some_denom");
let info = mock_info("requester", &[coin, second_coin.clone()]);
@@ -104,7 +147,9 @@ mod tests {
let info = mock_info("requester", &[second_coin]);
assert_eq!(
deposit_funds(deps.as_mut(), env, info, data),
Err(ContractError::WrongDenom)
Err(ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
})
);
}
@@ -122,7 +167,10 @@ mod tests {
verification_key.clone(),
encryption_key.clone(),
);
let coin = Coin::new(deposit_value, MIX_DENOM.base);
let coin = Coin::new(
deposit_value,
crate::support::tests::fixtures::TEST_MIX_DENOM,
);
let info = mock_info("requester", &[coin]);
let tx = deposit_funds(deps.as_mut(), env.clone(), info, data).unwrap();
@@ -171,7 +219,7 @@ mod tests {
let mut deps = helpers::init_contract();
let env = mock_env();
let invalid_admin = "invalid admin";
let funds = Coin::new(1, MIX_DENOM.base);
let funds = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
let err = release_funds(
deps.as_mut(),
@@ -180,7 +228,12 @@ mod tests {
Coin::new(1, "invalid denom"),
)
.unwrap_err();
assert_eq!(err, ContractError::WrongDenom);
assert_eq!(
err,
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let err = release_funds(
deps.as_mut(),
@@ -207,7 +260,7 @@ mod tests {
fn valid_release() {
let mut deps = helpers::init_contract();
let env = mock_env();
let coin = Coin::new(1, MIX_DENOM.base);
let coin = Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM);
deps.querier
.update_balance(env.contract.address.clone(), vec![coin.clone()]);
@@ -226,4 +279,96 @@ mod tests {
})
);
}
#[test]
fn valid_spend() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let data = spend_credential_data_fixture("blinded_serial_number");
let res = spend_credential(deps.as_mut(), env.clone(), info, data.clone()).unwrap();
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &res.messages[0].msg
{
assert_eq!(contract_addr, MULTISIG_CONTRACT);
assert!(funds.is_empty());
let multisig_msg: MultisigExecuteMsg = from_binary(&msg).unwrap();
if let MultisigExecuteMsg::Propose {
title: _,
description,
msgs,
latest,
} = multisig_msg
{
assert_eq!(description, data.blinded_serial_number().to_string());
assert!(latest.is_none());
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msgs[0]
{
assert_eq!(*contract_addr, env.contract.address.into_string());
assert!(funds.is_empty());
let release_funds_req: ExecuteMsg = from_binary(&msg).unwrap();
if let ExecuteMsg::ReleaseFunds { funds } = release_funds_req {
assert_eq!(funds, *data.funds());
} else {
panic!("Could not extract release funds message from proposal");
}
}
} else {
panic!("Could not extract proposal from binary blob");
}
} else {
panic!("Wasm execute message not found");
}
}
#[test]
fn invalid_spend_attempts() {
let mut deps = helpers::init_contract();
let env = mock_env();
let info = mock_info("requester", &[]);
let invalid_data = SpendCredentialData::new(
Coin::new(1, "invalid_denom".to_string()),
String::new(),
String::new(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::WrongDenom {
mix_denom: crate::support::tests::fixtures::TEST_MIX_DENOM.to_string()
}
);
let invalid_data = SpendCredentialData::new(
Coin::new(1, crate::support::tests::fixtures::TEST_MIX_DENOM),
String::new(),
"Blinded Serial Number".to_string(),
);
let ret = spend_credential(deps.as_mut(), env.clone(), info.clone(), invalid_data);
assert_eq!(
ret.unwrap_err().to_string(),
"Generic error: Invalid input: address not normalized".to_string()
);
let invalid_data = spend_credential_data_fixture("blined_serial_number");
spend_credential(
deps.as_mut(),
env.clone(),
info.clone(),
invalid_data.clone(),
)
.unwrap();
let ret = spend_credential(deps.as_mut(), env, info, invalid_data);
assert_eq!(
ret.unwrap_err(),
ContractError::DuplicateBlindedSerialNumber
);
}
}
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "coconut-test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
cosmwasm-std = "1.0.0"
cosmwasm-storage = "1.0.0"
cw-storage-plus = "0.13.4"
cw-controllers = "0.13.4"
cw-utils = "0.13.4"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
coconut-bandwidth = { path = "../coconut-bandwidth" }
cw-multi-test = { version = "0.13.2" }
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
cw4-group = { path = "../multisig/cw4-group" }
[[test]]
name = "coconut-test"
path = "src/tests.rs"
@@ -0,0 +1,101 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
deposit::DepositData,
msg::{ExecuteMsg, InstantiateMsg},
};
use cosmwasm_std::{coins, Addr};
use cw_controllers::AdminError;
use cw_multi_test::Executor;
const TEST_MIX_DENOM: &str = "unym";
#[test]
fn deposit_and_release() {
let init_funds = coins(10, TEST_MIX_DENOM);
let deposit_funds = coins(1, TEST_MIX_DENOM);
let release_funds = coins(2, TEST_MIX_DENOM);
let mut app = mock_app(&init_funds);
let multisig_addr = String::from(MULTISIG_CONTRACT);
let pool_addr = String::from(POOL_CONTRACT);
let random_addr = String::from(RANDOM_ADDRESS);
let code_id = app.store_code(contract_bandwidth());
let msg = InstantiateMsg {
multisig_addr: multisig_addr.clone(),
pool_addr: pool_addr.clone(),
mix_denom: TEST_MIX_DENOM.to_string(),
};
let contract_addr = app
.instantiate_contract(
code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"bandwidth",
None,
)
.unwrap();
let msg = ExecuteMsg::DepositFunds {
data: DepositData::new(
String::from("info"),
String::from("id"),
String::from("enc"),
),
};
app.execute_contract(
Addr::unchecked(OWNER),
contract_addr.clone(),
&msg,
&deposit_funds,
)
.unwrap();
// try to release more then it's in the contract
let msg = ExecuteMsg::ReleaseFunds {
funds: release_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(multisig_addr.clone()),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(ContractError::NotEnoughFunds, err.downcast().unwrap());
// try to call release from non-admin
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
let err = app
.execute_contract(
Addr::unchecked(random_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap_err();
assert_eq!(
ContractError::Admin(AdminError::NotAdmin {}),
err.downcast().unwrap()
);
let msg = ExecuteMsg::ReleaseFunds {
funds: deposit_funds[0].clone(),
};
app.execute_contract(
Addr::unchecked(multisig_addr),
contract_addr.clone(),
&msg,
&[],
)
.unwrap();
let pool_bal = app.wrap().query_balance(pool_addr, TEST_MIX_DENOM).unwrap();
assert_eq!(pool_bal, deposit_funds[0]);
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
use cw3_flex_multisig::{state::CONFIG, ContractError};
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub const OWNER: &str = "admin0001";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
pub const POOL_CONTRACT: &str = "mix pool contract address";
pub const RANDOM_ADDRESS: &str = "random address";
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {
pub coconut_bandwidth_address: String,
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
let mut cfg = CONFIG.load(deps.storage)?;
cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?;
CONFIG.save(deps.storage, &cfg)?;
Ok(Default::default())
}
pub fn mock_app(init_funds: &[Coin]) -> App {
AppBuilder::new().build(|router, _, storage| {
router
.bank
.init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec())
.unwrap();
})
}
pub fn contract_bandwidth() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
coconut_bandwidth::contract::execute,
coconut_bandwidth::contract::instantiate,
coconut_bandwidth::contract::query,
);
Box::new(contract)
}
pub fn contract_multisig() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw3_flex_multisig::contract::execute,
cw3_flex_multisig::contract::instantiate,
cw3_flex_multisig::contract::query,
)
.with_migrate(migrate);
Box::new(contract)
}
pub fn contract_group() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw4_group::contract::execute,
cw4_group::contract::instantiate,
cw4_group::contract::query,
);
Box::new(contract)
}
@@ -0,0 +1,160 @@
use crate::helpers::*;
use coconut_bandwidth::error::ContractError;
use coconut_bandwidth_contract_common::{
msg::{
ExecuteMsg as CoconutBandwidthExecuteMsg, InstantiateMsg as CoconutBandwidthInstantiateMsg,
},
spend_credential::SpendCredentialData,
};
use cosmwasm_std::{coins, Addr, Coin, Decimal};
use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg;
pub const TEST_COIN_DENOM: &str = "unym";
pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
#[test]
fn spend_credential_creates_proposal() {
let init_funds = coins(10, TEST_COIN_DENOM);
let mut app = mock_app(&init_funds);
let pool_addr = String::from(POOL_CONTRACT);
let group_code_id = app.store_code(contract_group());
let msg = GroupInstantiateMsg {
admin: Some(OWNER.to_string()),
members: vec![],
};
let group_contract_addr = app
.instantiate_contract(
group_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"group",
None,
)
.unwrap();
let multisig_code_id = app.store_code(contract_multisig());
let msg = MultisigInstantiateMsg {
group_addr: group_contract_addr.into_string(),
threshold: Threshold::AbsolutePercentage {
percentage: Decimal::from_ratio(2u128, 3u128),
},
max_voting_period: Duration::Height(1000),
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
};
let multisig_contract_addr = app
.instantiate_contract(
multisig_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"multisig",
Some(OWNER.to_string()),
)
.unwrap();
let coconut_bandwidth_code_id = app.store_code(contract_bandwidth());
let msg = CoconutBandwidthInstantiateMsg {
multisig_addr: multisig_contract_addr.to_string(),
pool_addr,
mix_denom: TEST_COIN_DENOM.to_string(),
};
let coconut_bandwidth_contract_addr = app
.instantiate_contract(
coconut_bandwidth_code_id,
Addr::unchecked(OWNER),
&msg,
&[],
"coconut bandwidth",
None,
)
.unwrap();
let msg = MigrateMsg {
coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(),
};
app.migrate_contract(
Addr::unchecked(OWNER),
multisig_contract_addr,
&msg,
multisig_code_id,
)
.unwrap();
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(1, proposal_id);
// Trying with the same blinded serial number will detect the double spend attempt
let err = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap_err();
assert_eq!(
ContractError::DuplicateBlindedSerialNumber,
err.downcast().unwrap()
);
let msg = CoconutBandwidthExecuteMsg::SpendCredential {
data: SpendCredentialData::new(
Coin::new(1, TEST_COIN_DENOM),
String::from("blinded_serial_number2"),
String::from("gateway_cosmos_address"),
),
};
let res = app
.execute_contract(
Addr::unchecked(OWNER),
coconut_bandwidth_contract_addr.clone(),
&msg,
&vec![],
)
.unwrap();
let proposal_id = res
.events
.into_iter()
.find(|e| &e.ty == "wasm")
.unwrap()
.attributes
.into_iter()
.find(|attr| &attr.key == "proposal_id")
.unwrap()
.value
.parse::<u64>()
.unwrap();
assert_eq!(2, proposal_id);
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod deposit_and_release;
mod helpers;
mod spend_credential_creates_proposal;
+87 -12
View File
@@ -27,16 +27,18 @@ use crate::mixnodes::bonding_queries::{
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
};
use crate::mixnodes::layer_queries::query_layer_distribution;
use crate::mixnodes::transactions::_try_remove_mixnode;
use crate::queued_migrations::migrate_config_from_env;
use crate::rewards::queries::{
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
};
use crate::rewards::storage as rewards_storage;
use cosmwasm_std::{
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
Uint128,
Storage, Uint128,
};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
};
use time::OffsetDateTime;
@@ -61,9 +63,14 @@ pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
}
fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> ContractState {
fn default_initial_state(
owner: Addr,
mix_denom: String,
rewarding_validator_address: Addr,
) -> ContractState {
ContractState {
owner,
mix_denom,
rewarding_validator_address,
params: ContractStateParams {
minimum_mixnode_pledge: INITIAL_MIXNODE_PLEDGE,
@@ -88,7 +95,7 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let rewarding_validator_address = deps.api.addr_validate(&msg.rewarding_validator_address)?;
let state = default_initial_state(info.sender, rewarding_validator_address);
let state = default_initial_state(info.sender, msg.mixnet_denom, rewarding_validator_address);
init_epoch(deps.storage, env)?;
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?;
@@ -107,6 +114,19 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::CompoundReward {
operator,
delegator,
mix_identity,
proxy,
} => crate::rewards::transactions::try_compound_reward(
deps,
env,
operator,
delegator,
mix_identity,
proxy,
),
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
try_update_rewarding_validator_address(deps, info, address)
}
@@ -122,7 +142,7 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
}
ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
@@ -216,7 +236,13 @@ pub fn execute(
owner_signature,
),
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
&env,
deps.storage,
deps.api,
info,
owner,
)
}
ExecuteMsg::BondGatewayOnBehalf {
gateway,
@@ -275,7 +301,7 @@ pub fn execute(
)
}
ExecuteMsg::ReconcileDelegations {} => {
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
}
ExecuteMsg::CheckpointMixnodes {} => {
crate::mixnodes::transactions::try_checkpoint_mixnodes(
@@ -316,6 +342,9 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetBlacklistedNodes {} => to_binary(
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
),
QueryMsg::GetRewardingValidatorAddress {} => {
to_binary(&query_rewarding_validator_address(deps)?)
}
@@ -443,16 +472,61 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
Ok(query_res?)
}
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
.idx
.owner
.item(storage, owner.clone())?
{
Some(record) => record.1,
None => {
return Err(ContractError::NoAssociatedMixNodeBond {
owner: owner.to_owned(),
})
}
};
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
Ok(())
}
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
fn remove_malicious_node(
storage: &mut dyn Storage,
api: &dyn Api,
env: &Env,
node: &NodeToRemove,
) -> Result<Response, ContractError> {
let proxy = node.proxy().map(|p| {
api.addr_validate(p)
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
});
let owner_addr = api.addr_validate(node.owner())?;
blacklist_malicious_node(storage, &owner_addr)?;
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
migrate_config_from_env(deps.storage, &msg)?;
let mut response = Response::new();
for node in msg.nodes_to_remove().iter() {
let mut sub_response = remove_malicious_node(deps.storage, deps.api, &env, node)
.unwrap_or_else(|_| panic!("Could not remove node: {:?}", node));
response.messages.append(&mut sub_response.messages);
response.attributes.append(&mut sub_response.attributes);
response.events.append(&mut sub_response.events);
}
Ok(response)
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::support::tests;
use config::defaults::{DEFAULT_NETWORK, MIX_DENOM};
use crate::support::tests::fixtures::{TEST_COIN_DENOM, TEST_REWARDING_VALIDATOR_ADDRESS};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{coins, from_binary};
use mixnet_contract_common::PagedMixnodeResponse;
@@ -462,7 +536,8 @@ pub mod tests {
let mut deps = mock_dependencies();
let env = mock_env();
let msg = InstantiateMsg {
rewarding_validator_address: DEFAULT_NETWORK.rewarding_validator_address().to_string(),
rewarding_validator_address: TEST_REWARDING_VALIDATOR_ADDRESS.to_string(),
mixnet_denom: TEST_COIN_DENOM.to_string(),
};
let info = mock_info("creator", &[]);
@@ -484,7 +559,7 @@ pub mod tests {
// Contract balance should match what we initialized it as
assert_eq!(
coins(0, MIX_DENOM.base),
coins(0, TEST_COIN_DENOM),
tests::queries::query_contract_balance(env.contract.address, deps)
);
}
+4 -5
View File
@@ -199,8 +199,7 @@ pub(crate) fn query_mixnode_delegations_paged(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::support::tests::test_helpers;
use config::defaults::MIX_DENOM;
use crate::support::tests::{fixtures::TEST_COIN_DENOM, test_helpers};
use cosmwasm_std::{coin, Addr, Storage};
use rand::Rng;
@@ -385,7 +384,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner.clone(),
node_identity.clone(),
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);
@@ -433,7 +432,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner2,
node_identity1.clone(),
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);
@@ -460,7 +459,7 @@ pub(crate) mod tests {
let delegation = Delegation::new(
delegation_owner1.clone(),
node_identity2,
coin(1234, MIX_DENOM.base),
coin(1234, TEST_COIN_DENOM),
1234,
None,
);
+3 -3
View File
@@ -76,8 +76,8 @@ mod tests {
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers;
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{coin, Order};
use mixnet_contract_common::Delegation;
@@ -87,7 +87,7 @@ mod tests {
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
let delegation = coin(12345, 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,
+101 -231
View File
@@ -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(),
+11 -5
View File
@@ -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 },
}
+2 -2
View File
@@ -36,7 +36,7 @@ pub(crate) fn gateways<'a>() -> IndexedMap<'a, IdentityKeyRef<'a>, GatewayBond,
mod tests {
use super::super::storage;
use crate::support::tests;
use 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 {
+55 -17
View File
@@ -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()
})
);
}
}
+1
View File
@@ -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();
+11 -5
View File
@@ -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,
+100 -45
View File
@@ -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]
+38
View File
@@ -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())
}
+2 -2
View File
@@ -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(),
)
+103 -30
View File
@@ -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,
}]
}
+6 -3
View File
@@ -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
-1
View File
@@ -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"] }
+20 -19
View File
@@ -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
View File
@@ -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())
}
+1
View File
@@ -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 {
+5 -3
View File
@@ -2,15 +2,17 @@
pub mod helpers {
use crate::contract::instantiate;
use crate::vesting::{populate_vesting_periods, Account};
use config::defaults::MIX_DENOM;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128};
use vesting_contract_common::messages::{InitMsg, VestingSpecification};
pub const TEST_COIN_DENOM: &str = "unym";
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
let mut deps = mock_dependencies();
let msg = InitMsg {
mixnet_contract_address: "test".to_string(),
mix_denom: TEST_COIN_DENOM.to_string(),
};
let env = mock_env();
let info = mock_info("admin", &[]);
@@ -31,7 +33,7 @@ pub mod helpers {
Some(Addr::unchecked("staking")),
Coin {
amount: Uint128::new(1_000_000_000_000),
denom: MIX_DENOM.base.to_string(),
denom: TEST_COIN_DENOM.to_string(),
},
start_time_ts,
periods,
@@ -50,7 +52,7 @@ pub mod helpers {
Some(Addr::unchecked("staking")),
Coin {
amount: Uint128::new(1_000_000_000_000),
denom: MIX_DENOM.base.to_string(),
denom: TEST_COIN_DENOM.to_string(),
},
start_time,
periods,
@@ -34,11 +34,13 @@ pub trait VestingAccount {
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_vesting_coins(
&self,
block_time: Option<Timestamp>,
env: &Env,
storage: &dyn Storage,
) -> Result<Coin, ContractError>;
fn get_start_time(&self) -> Timestamp;
@@ -2,6 +2,7 @@ use crate::errors::ContractError;
use crate::storage::locked_pledge_cap;
use crate::storage::save_delegation;
use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::storage::MIX_DENOM;
use crate::traits::DelegatingAccount;
use crate::traits::VestingAccount;
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
@@ -28,7 +29,7 @@ impl DelegatingAccount for Account {
let compound_delegator_reward_msg = wasm_execute(
MIXNET_CONTRACT_ADDRESS.load(storage)?,
&msg,
vec![one_ucoin()],
vec![one_ucoin(MIX_DENOM.load(storage)?)],
)?;
Ok(Response::new().add_message(compound_delegator_reward_msg))
@@ -46,7 +47,7 @@ impl DelegatingAccount for Account {
let compound_delegator_reward_msg = wasm_execute(
MIXNET_CONTRACT_ADDRESS.load(storage)?,
&msg,
vec![one_ucoin()],
vec![one_ucoin(MIX_DENOM.load(storage)?)],
)?;
Ok(Response::new().add_message(compound_delegator_reward_msg))
@@ -118,7 +119,7 @@ impl DelegatingAccount for Account {
let undelegate_from_mixnode = wasm_execute(
MIXNET_CONTRACT_ADDRESS.load(storage)?,
&msg,
vec![one_ucoin()],
vec![one_ucoin(MIX_DENOM.load(storage)?)],
)?;
Ok(Response::new()

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