Compare commits

...

20 Commits

Author SHA1 Message Date
pierre f3279ab3da fix: remove dead code 2023-03-20 12:37:58 +01:00
pierre 8f5e6992c1 refactor(nc-mobile): adjust ui 2023-03-20 12:25:08 +01:00
pierre 76a53c8ac9 feat(nc-mobile): select service provider ui 2023-03-20 11:09:19 +01:00
Jędrzej Stuczyński 7ff0becf72 replaced usage of 'serde_json' in mixnet contract in favour of 'serde_json_wasm' (#3191)
* replaced usage of 'serde_json' in mixnet contract in favour of 'serde_json_wasm'

* using stricter version number
2023-03-17 14:31:38 +00:00
Jon Häggblad 4c2967a733 Delete stray .gitignore file 2023-03-16 15:27:45 +00:00
Jon Häggblad 2b80e5d1c9 Remove leftover file from deleted crate 2023-03-16 12:59:46 +00:00
Jon Häggblad 4e7ff53214 Contract and client support for updating gateway config (#3166)
* mixnet-contract: add update gateway config

* mixnet-contract: tests for updating gateway config

* vesting-contract: add update gateway config

* validator-client: add update gateway config

* wallet: add update_gateway_config

* common/commands: add support for setting gateway config

* Remove commented out line

* Review fixes

* Generate ts file for GatewayConfigUpdate type

* Add generated GatewayConfigUpdate.ts file
2023-03-16 13:43:56 +01:00
Jon Häggblad 9ec36e49b7 contracts: remove .gitignore with Cargo.lock in it
While developing the service-provider-directory contract I ran into
issues with the lock file being inconsistent for cosmwasm-std (1.0 vs
1.2) and was hidden due to ignoring the lock file
2023-03-16 12:28:35 +00:00
pierre dd13073037 docs(connect-android): add build note 2023-03-15 12:29:23 +01:00
Pierre Dommerc 1010df1077 refactor(wallet): ui adjustments (#3182) 2023-03-15 12:22:00 +01:00
Bogdan-Ștefan Neacşu 9eaf9cf491 Feature/fix resharing (#3139)
* Compare verified vks against current group instead of initial dealers

* Fix various dkg logs

* API auto-advance epoch even on corrupt states

* Use verified vks as ultimate truth for dealers

* Set initial dealers based of verified vk

* Extend register period even more

* Fix test

* Use shares from current epoch

* Save initial dealers only when triggering resharing

* Fix tests

* Backup the last InProgress state too

* Reset previous signers that are not initial dealers

* Add unit test for bug reproduction

* More verbose debug logging

* Handle edge case for coconut keypair removal

* Update dkg api test

* Remove dealings directly for each key

* Replacement data is saved only on the first reshare start

* More debug logging

* On failed DKG, just reset

* Clippy fix
2023-03-15 11:16:43 +00:00
pierre 8e96318478 build(connect-android): try to fix fdroid build 2023-03-15 10:25:53 +01:00
Jędrzej Stuczyński 54287666e8 chore: simplify mnemonic zeroize story (#3165)
* updated bip39 dependency to simplify our zeroize story

* Replaced UserPassword wrapper with Zeroizing type alias

* fixed wallet-types cosmwasm-std dependency version
2023-03-13 11:29:56 +00:00
Tommy Verrall 6de829163d Merge pull request #3173 from nymtech/feature/nym-cli-tweak
Feature/nym cli tweak
2023-03-13 13:24:13 +02:00
benedettadavico adb5ed7c30 typo fix 2023-03-13 12:14:14 +01:00
benedettadavico 2b019e57df merge develop 2023-03-13 12:12:00 +01:00
benedettadavico 30c07712e3 format 2023-03-13 12:03:38 +01:00
benedettadavico 82c92501d9 vesting stuff 2023-03-13 12:01:22 +01:00
benedettadavico c2a871a1a7 typo 2023-03-10 17:10:27 +01:00
benedettadavico dfd7bd5889 adding pledge more 2023-03-10 17:10:01 +01:00
114 changed files with 2077 additions and 1518 deletions
Generated
+32 -31
View File
@@ -302,22 +302,23 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.0.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.9.7"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
[[package]]
name = "bitflags"
@@ -2845,12 +2846,6 @@ dependencies = [
"serde",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -3482,7 +3477,7 @@ dependencies = [
"rand_chacha 0.3.1",
"schemars",
"serde",
"serde_json",
"serde-json-wasm",
"serde_repr",
"thiserror",
"time 0.3.17",
@@ -4171,7 +4166,7 @@ dependencies = [
"instant",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"winapi",
]
@@ -4184,7 +4179,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -5057,7 +5052,7 @@ dependencies = [
"pin-project-lite",
"ref-cast",
"serde",
"smallvec 1.10.0",
"smallvec",
"stable-pattern",
"state",
"time 0.3.17",
@@ -5552,15 +5547,6 @@ dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@@ -5725,7 +5711,7 @@ dependencies = [
"percent-encoding",
"rustls 0.19.1",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.1.8",
"sqlx-rt 0.5.13",
"stringprep",
@@ -5773,7 +5759,7 @@ dependencies = [
"rustls 0.20.8",
"rustls-pemfile",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.2.1",
"sqlx-rt 0.6.2",
"stringprep",
@@ -6198,6 +6184,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
@@ -6495,7 +6496,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -6661,11 +6662,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
+1
View File
@@ -105,6 +105,7 @@ license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
dotenvy = "0.15.6"
lazy_static = "1.4.0"
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip39 = "1.0.1"
bip39 = { workspace = true }
clap = { version = "4.0", features = ["cargo", "derive"] }
log = "0.4"
rand = "0.7.3"
-3
View File
@@ -1,3 +0,0 @@
/target
**/*.rs.bk
Cargo.lock
View File
@@ -37,7 +37,7 @@ nym-execute = { path = "../../execute" }
# at some point it might be possible to make it wasm-compatible
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { workspace = true, optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
bip39 = { workspace = true, features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true }
@@ -47,7 +47,7 @@ flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0", optional = true }
zeroize = { version = "1.5.7", optional = true }
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -9,6 +9,7 @@ use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use nym_mixnet_contract_common::{
@@ -498,6 +499,36 @@ pub trait MixnetSigningClient {
.await
}
async fn update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateGatewayConfig { new_config },
vec![],
)
.await
}
async fn update_gateway_config_on_behalf(
&self,
owner: AccountId,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateGatewayConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, NyxdClient};
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::{Gateway, MixId, MixNode};
use nym_vesting_contract_common::messages::{
@@ -35,6 +36,12 @@ pub trait VestingSigningClient {
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn vesting_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn update_mixnet_address(
&self,
address: &str,
@@ -185,6 +192,19 @@ impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for Ny
.await
}
async fn vesting_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateGatewayConfig { new_config },
vec![],
)
.await
}
async fn update_mixnet_address(
&self,
address: &str,
@@ -8,7 +8,7 @@ use cosmrs::crypto::PublicKey;
use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
use nym_config::defaults;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug, Clone)]
@@ -41,7 +41,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
@@ -54,30 +54,10 @@ pub struct DirectSecp256k1HdWallet {
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
/// Derivation instructions
#[zeroize(skip)]
accounts: Vec<Secp256k1Derivation>,
}
impl Zeroize for DirectSecp256k1HdWallet {
fn zeroize(&mut self) {
// in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it)
// and the memory would have been filled with zeroes.
//
// we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing
// of overwriting it with a fresh mnemonic that was never used before
//
// note: this function can only fail on an invalid word count, which clearly is not the case here
self.secret = bip39::Mnemonic::generate(self.secret.word_count()).unwrap();
self.seed.zeroize();
// there's nothing secret about derivation paths
}
}
impl Drop for DirectSecp256k1HdWallet {
fn drop(&mut self) {
self.zeroize()
}
}
impl DirectSecp256k1HdWallet {
pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder {
DirectSecp256k1HdWalletBuilder::new(prefix)
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
base64 = "0.13.0"
bip39 = "1.0.1"
bip39 = { workspace = true }
bs58 = "0.4"
comfy-table = "6.0.0"
cfg-if = "1.0.0"
@@ -6,9 +6,11 @@ use clap::{Args, Subcommand};
pub mod rewards;
pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)]
@@ -32,4 +34,8 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
}
@@ -0,0 +1,29 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use validator_client::nyxd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub amount: u128,
}
pub async fn pledge_more(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to pledge more");
let coin = Coin::new(args.amount, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to pledge more!");
info!("pledging more: {:?}", res);
}
@@ -0,0 +1,30 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use validator_client::nyxd::VestingSigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub amount: u128,
}
pub async fn vesting_pledge_more(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting pledge more");
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_pledge_more(coin.into(), None)
.await
.expect("failed to pledge more!");
info!("vesting pledge more: {:?}", res);
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod bond_gateway;
pub mod gateway_bonding_sign_payload;
pub mod settings;
pub mod unbond_gateway;
pub mod vesting_bond_gateway;
pub mod vesting_unbond_gateway;
@@ -18,14 +19,16 @@ pub struct MixnetOperatorsGateway {
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsGatewayCommands {
/// Manage your gateway settings stored in the directory
Settings(settings::MixnetOperatorsGatewaySettings),
/// Bond to a gateway
Bond(bond_gateway::Args),
/// Unbound from a gateway
Unbound(unbond_gateway::Args),
/// Unbond from a gateway
Unbond(unbond_gateway::Args),
/// Bond to a gateway with locked tokens
VestingBond(vesting_bond_gateway::Args),
/// Unbound from a gateway (when originally using locked tokens)
VestingUnbound(vesting_unbond_gateway::Args),
/// Unbond from a gateway (when originally using locked tokens)
VestingUnbond(vesting_unbond_gateway::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateGatewayBondingSignPayload(gateway_bonding_sign_payload::Args),
}
@@ -0,0 +1,22 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod update_config;
pub mod vesting_update_config;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsGatewaySettings {
#[clap(subcommand)]
pub command: MixnetOperatorsGatewaySettingsCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsGatewaySettingsCommands {
/// Update gateway configuration
UpdateConfig(update_config::Args),
/// Update gateway configuration for a gateway bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
}
@@ -0,0 +1,60 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::GatewayConfigUpdate;
use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update gateway config!");
let current_details = match client
.get_owned_gateway(client.address())
.await
.expect("failed to query the chain for gateway details")
.gateway
{
Some(details) => details,
None => {
log::warn!("this operator does not own a gateway to update");
return;
}
};
let update = GatewayConfigUpdate {
host: args.host.unwrap_or(current_details.gateway.host),
mix_port: args.mix_port.unwrap_or(current_details.gateway.mix_port),
clients_port: args
.clients_port
.unwrap_or(current_details.gateway.clients_port),
location: args.location.unwrap_or(current_details.gateway.location),
version: args.version.unwrap_or(current_details.gateway.version),
};
let res = client
.update_gateway_config(update, None)
.await
.expect("updating gateway config");
info!("gateway config updated: {:?}", res)
}
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::GatewayConfigUpdate;
use validator_client::nyxd::traits::MixnetQueryClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting gateway config!");
let current_details = match client
.get_owned_gateway(client.address())
.await
.expect("failed to query the chain for gateway details")
.gateway
{
Some(details) => details,
None => {
log::warn!("this operator does not own a gateway to update");
return;
}
};
let update = GatewayConfigUpdate {
host: args.host.unwrap_or(current_details.gateway.host),
mix_port: args.mix_port.unwrap_or(current_details.gateway.mix_port),
clients_port: args
.clients_port
.unwrap_or(current_details.gateway.clients_port),
location: args.location.unwrap_or(current_details.gateway.location),
version: args.version.unwrap_or(current_details.gateway.version),
};
let res = client
.vesting_update_gateway_config(update, None)
.await
.expect("updating vesting gateway config");
info!("gateway config updated: {:?}", res)
}
@@ -29,12 +29,12 @@ pub enum MixnetOperatorsMixnodeCommands {
Settings(settings::MixnetOperatorsMixnodeSettings),
/// Bond to a mixnode
Bond(bond_mixnode::Args),
/// Unbound from a mixnode
Unbound(unbond_mixnode::Args),
/// Unbond from a mixnode
Unbond(unbond_mixnode::Args),
/// Bond to a mixnode with locked tokens
BondVesting(vesting_bond_mixnode::Args),
/// Unbound from a mixnode (when originally using locked tokens)
UnboundVesting(vesting_unbond_mixnode::Args),
/// Unbond from a mixnode (when originally using locked tokens)
UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
}
@@ -21,7 +21,7 @@ pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct InitialReplacementData {
pub initial_dealers: Vec<Addr>,
pub initial_height: Option<u64>,
pub initial_height: u64,
}
#[derive(
@@ -10,13 +10,15 @@ repository = { workspace = true }
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0"
cosmwasm-std = "=1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" }
serde_json = "1.0.0"
# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0
# (and ideally we don't want to pull the same dependency twice)
serde-json-wasm = "=0.4.1"
humantime-serde = "1.1.1"
# TO CHECK WHETHER STILL NEEDED:
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::gateway::GatewayConfigUpdate;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
@@ -42,6 +43,7 @@ pub enum MixnetEventType {
ReconcilePendingEvents,
PendingIntervalConfigUpdate,
IntervalConfigUpdate,
GatewayConfigUpdate,
}
impl From<MixnetEventType> for String {
@@ -86,6 +88,7 @@ impl ToString for MixnetEventType {
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
MixnetEventType::GatewayConfigUpdate => "gateway_config_update",
};
format!("{EVENT_VERSION_PREFIX}{event_name}")
@@ -122,6 +125,7 @@ pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_a
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
pub const UPDATED_MIXNODE_CONFIG_KEY: &str = "updated_mixnode_config";
pub const UPDATED_GATEWAY_CONFIG_KEY: &str = "updated_gateway_config";
pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
// rewarding
@@ -382,6 +386,17 @@ pub fn new_mixnode_config_update_event(
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_gateway_config_update_event(
owner: &Addr,
proxy: &Option<Addr>,
update: &GatewayConfigUpdate,
) -> Event {
Event::new(MixnetEventType::GatewayConfigUpdate)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: MixId,
owner: &Addr,
@@ -112,6 +112,26 @@ impl Display for GatewayBond {
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/GatewayConfigUpdate.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayConfigUpdate {
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
pub location: String,
pub version: String,
}
impl GatewayConfigUpdate {
pub fn to_inline_json(&self) -> String {
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedGatewayResponse {
pub nodes: Vec<GatewayBond>,
@@ -27,7 +27,8 @@ pub use delegation::{
PagedMixNodeDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse,
PagedGatewayResponse,
};
pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
@@ -542,7 +542,7 @@ pub struct MixNodeCostParams {
impl MixNodeCostParams {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -636,7 +636,7 @@ pub struct MixNodeConfigUpdate {
impl MixNodeConfigUpdate {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -3,6 +3,7 @@
use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::gateway::GatewayConfigUpdate;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
@@ -199,6 +200,13 @@ pub enum ExecuteMsg {
UnbondGatewayOnBehalf {
owner: String,
},
UpdateGatewayConfig {
new_config: GatewayConfigUpdate,
},
UpdateGatewayConfigOnBehalf {
new_config: GatewayConfigUpdate,
owner: String,
},
// delegation-related:
DelegateToMixnode {
@@ -313,6 +321,10 @@ impl ExecuteMsg {
}
ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(),
ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(),
ExecuteMsg::UpdateGatewayConfig { .. } => "updating gateway configuration".into(),
ExecuteMsg::UpdateGatewayConfigOnBehalf { .. } => {
"updating gateway configuration on behalf".into()
}
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {mix_id}"),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {mix_id} on behalf")
@@ -70,7 +70,7 @@ pub struct IntervalRewardParams {
impl IntervalRewardParams {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -282,6 +282,6 @@ impl IntervalRewardingParamsUpdate {
}
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
@@ -17,6 +17,7 @@ pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
@@ -121,6 +122,10 @@ pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_gateway_config_event() -> Event {
Event::new(VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_cost_params_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE)
}
@@ -1,6 +1,7 @@
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, IdentityKey, MixId, MixNode,
};
@@ -140,6 +141,9 @@ pub enum ExecuteMsg {
owner: String,
amount: Coin,
},
UpdateGatewayConfig {
new_config: GatewayConfigUpdate,
},
TransferOwnership {
to_address: String,
},
@@ -179,6 +183,7 @@ impl ExecuteMsg {
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
ExecuteMsg::UpdateGatewayConfig { .. } => "VestingExecuteMsg::UpdateGatewayConfig",
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
-1
View File
@@ -1 +0,0 @@
Cargo.lock
@@ -22,7 +22,7 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
let state = STATE.load(deps.storage)?;
let height = if resharing {
INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
} else {
None
};
@@ -105,7 +105,7 @@ pub(crate) mod tests {
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![details1.address, details2.address, details3.address],
initial_height: Some(1),
initial_height: 1,
},
)
.unwrap();
@@ -126,7 +126,7 @@ pub(crate) mod tests {
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_height = Some(2);
data.initial_height = 2;
Ok(data)
})
.unwrap();
@@ -110,7 +110,7 @@ pub(crate) mod tests {
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
initial_height: 1,
},
)
.unwrap();
@@ -7,6 +7,7 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRES
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::verification_key_shares::storage::verified_dealers;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
@@ -19,7 +20,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
for dealer_addr in dealers {
let details = current_dealers().load(storage, &dealer_addr)?;
for dealings in DEALINGS_BYTES {
dealings.remove(storage, &details.address);
let dealing_keys: Vec<_> = dealings
.keys(storage, None, None, Order::Ascending)
.flatten()
.collect();
for key in dealing_keys {
dealings.remove(storage, &key);
}
}
current_dealers().remove(storage, &dealer_addr)?;
past_dealers().save(storage, &dealer_addr, &details)?;
@@ -46,15 +53,9 @@ fn dealers_still_active(
}
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let dealers_still_active = dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten(),
)?;
let all_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.count();
let verified_dealers = verified_dealers(deps.storage)?;
let all_dealers = verified_dealers.len();
let dealers_still_active = dealers_still_active(&deps.as_ref(), verified_dealers.into_iter())?;
let group_members = STATE
.load(deps.storage)?
.group_addr
@@ -66,7 +67,11 @@ fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let threshold = THRESHOLD.load(deps.storage)? as usize;
let initial_dealers = INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_dealers;
let initial_dealers = verified_dealers(deps.storage)?;
if initial_dealers.is_empty() {
// possibly failed DKG, just reset and start again
return Ok(true);
}
let initial_dealer_count = initial_dealers.len();
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
let removed_dealer_count =
@@ -90,24 +95,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process
let mut new_state = state;
if let EpochState::DealingExchange { resharing } = state {
if let EpochState::DealingExchange { .. } = state {
let current_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.collect::<Result<Vec<Addr>, _>>()?;
if current_dealers.is_empty() {
// If no dealer registered yet, we just stay in the same state until there's at least one
let group_members =
STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?;
if current_dealers.len() < group_members.len() {
// If not all group members registered yet, we just stay in the same state until
// they either register or they get kicked out of the group
new_state = current_epoch.state;
} else {
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
THRESHOLD.save(deps.storage, &threshold)?;
if !resharing {
let replacement_data = InitialReplacementData {
initial_dealers: current_dealers,
initial_height: None,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
}
};
Epoch::new(
@@ -129,13 +133,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
// Dealer set changed, we need to redo DKG...
let state = if replacement_threshold_surpassed(&deps)? {
// ... in reset mode
INITIAL_REPLACEMENT_DATA.remove(deps.storage);
EpochState::default()
} else {
// ... in reshare mode
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
data.initial_height = Some(env.block.height);
Ok(data)
})?;
if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() {
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
data.initial_height = env.block.height;
Ok(data)
})?;
} else {
let replacement_data = InitialReplacementData {
initial_dealers: verified_dealers(deps.storage)?,
initial_height: env.block.height,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
EpochState::PublicKeySubmission { resharing: true }
};
reset_epoch_state(deps.storage)?;
@@ -158,10 +172,8 @@ pub(crate) fn try_surpassed_threshold(
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten();
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
let dealers = verified_dealers(deps.storage)?;
if dealers_still_active(&deps.as_ref(), dealers.into_iter())? < threshold as usize {
reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
@@ -180,8 +192,9 @@ pub(crate) fn try_surpassed_threshold(
pub(crate) mod tests {
use super::*;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::Addr;
use cw4::Member;
@@ -204,11 +217,15 @@ pub(crate) mod tests {
for n in [10, 25, 50, 100] {
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
let shares: Vec<_> = (0..n).map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)).collect();
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
let data = InitialReplacementData {
initial_dealers,
initial_height: None,
initial_height: 1,
};
for share in shares {
vk_shares().save(deps.as_mut().storage, (&share.owner, 0), &share).unwrap();
}
for f in [two_thirds, three_fourths, ninty_pc] {
let threshold = f(n);
THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap();
@@ -247,39 +264,39 @@ pub(crate) mod tests {
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
let details = dealer_details_fixture(1);
let different_details = dealer_details_fixture(2);
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
let share = vk_share_fixture("owner2", 0);
let different_share = vk_share_fixture("owner4", 0);
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), &share)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &details.address)
vk_shares()
.remove(deps.as_mut().storage, (&share.owner, 0))
.unwrap();
GROUP_MEMBERS.lock().unwrap().push((
Member {
addr: "owner1".to_string(),
addr: "owner2".to_string(),
weight: 10,
},
1,
));
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
vk_shares()
.save(
deps.as_mut().storage,
&different_details.address,
&different_details,
(&different_share.owner, 0),
&different_share,
)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &different_details.address)
vk_shares()
.remove(deps.as_mut().storage, (&different_share.owner, 0))
.unwrap();
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), &share)
.unwrap();
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
}
@@ -407,6 +424,12 @@ pub(crate) mod tests {
);
// setup dealer details
let all_shares: [_; 4] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
@@ -431,12 +454,6 @@ pub(crate) mod tests {
.time
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
);
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
let expected_replacement_data = InitialReplacementData {
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
initial_height: None,
};
assert_eq!(replacement_data, expected_replacement_data);
env.block.time = env
.block
@@ -588,8 +605,14 @@ pub(crate) mod tests {
);
assert_eq!(curr_epoch, expected_epoch);
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
let expected_replacement_data = InitialReplacementData {
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
initial_height: 12345,
};
assert_eq!(replacement_data, expected_replacement_data);
let all_details: [_; 2] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
for details in all_details.iter() {
past_dealers().remove(deps.as_mut().storage, &details.address).unwrap();
current_dealers()
@@ -607,6 +630,17 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
}
let all_shares: [_; 4] = std::array::from_fn(|i| {
let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1);
share.verified = i % 2 == 0;
share
});
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
// Group changed even more, surpassing threshold, so re-run dkg in reset mode
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
Member {
@@ -623,7 +657,7 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let expected_epoch = Epoch::new(
EpochState::PublicKeySubmission { resharing: false },
EpochState::PublicKeySubmission { resharing: true },
prev_epoch.epoch_id + 1,
prev_epoch.time_configuration,
env.block.time,
@@ -672,12 +706,25 @@ pub(crate) mod tests {
}
);
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
.unwrap();
}
for times in [
time_configuration.public_key_submission_time_secs,
@@ -4,7 +4,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
use cosmwasm_std::Addr;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::error::ContractError;
use cosmwasm_std::{Addr, Order, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use nym_coconut_dkg_common::types::EpochId;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
@@ -35,3 +37,21 @@ pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare,
};
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
}
pub(crate) fn verified_dealers(storage: &dyn Storage) -> Result<Vec<Addr>, ContractError> {
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
Ok(vk_shares()
.idx
.epoch_id
.prefix(epoch_id)
.range(storage, None, None, Order::Ascending)
.flatten()
.filter_map(|(_, share)| {
if share.verified {
Some(share.owner)
} else {
None
}
})
.collect())
}
+8
View File
@@ -318,6 +318,14 @@ pub fn execute(
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
}
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
crate::gateways::transactions::try_update_gateway_config_on_behalf(
deps, info, new_config, owner,
)
}
// delegation-related:
ExecuteMsg::DelegateToMixnode { mix_id } => {
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::{Addr, Storage};
use mixnet_contract_common::{error::MixnetContractError, GatewayBond};
pub(crate) fn must_get_gateway_bond_by_owner(
store: &dyn Storage,
owner: &Addr,
) -> Result<GatewayBond, MixnetContractError> {
Ok(storage::gateways()
.idx
.owner
.item(store, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedGatewayBond {
owner: owner.clone(),
})?
.1)
}
+1
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod helpers;
pub mod queries;
pub mod signature_helpers;
pub mod storage;
+148 -2
View File
@@ -1,16 +1,20 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::helpers::must_get_gateway_bond_by_owner;
use super::storage;
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_pledge,
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{new_gateway_bonding_event, new_gateway_unbonding_event};
use mixnet_contract_common::events::{
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
};
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::{Gateway, GatewayBond};
use nym_contracts_common::signing::MessageSignature;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
@@ -189,6 +193,56 @@ pub(crate) fn _try_remove_gateway(
)))
}
pub(crate) fn try_update_gateway_config(
deps: DepsMut<'_>,
info: MessageInfo,
new_config: GatewayConfigUpdate,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_gateway_config(deps, new_config, owner, None)
}
pub(crate) fn try_update_gateway_config_on_behalf(
deps: DepsMut,
info: MessageInfo,
new_config: GatewayConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_gateway_config(
deps: DepsMut,
new_config: GatewayConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
let mut updated_bond = existing_bond.clone();
updated_bond.gateway.host = new_config.host;
updated_bond.gateway.mix_port = new_config.mix_port;
updated_bond.gateway.clients_port = new_config.clients_port;
updated_bond.gateway.location = new_config.location;
updated_bond.gateway.version = new_config.version;
storage::gateways().replace(
deps.storage,
existing_bond.identity(),
Some(&updated_bond),
Some(&existing_bond),
)?;
Ok(Response::new().add_event(cfg_update_event))
}
#[cfg(test)]
pub mod tests {
use super::*;
@@ -196,6 +250,7 @@ pub mod tests {
use crate::gateways::queries;
use crate::gateways::transactions::{
try_add_gateway, try_add_gateway_on_behalf, try_remove_gateway_on_behalf,
try_update_gateway_config, try_update_gateway_config_on_behalf,
};
use crate::interval::pending_events;
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
@@ -207,6 +262,7 @@ pub mod tests {
use cosmwasm_std::{Addr, BankMsg, Response, Uint128};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::new_gateway_unbonding_event;
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::ExecuteMsg;
#[test]
@@ -485,4 +541,94 @@ pub mod tests {
}
)
}
#[test]
fn update_gateway_config() {
let mut test = TestSetup::new();
let owner = "alice";
let info = mock_info(owner, &[]);
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "home".to_string(),
version: "v1.2.3".to_string(),
};
// try updating a non existing gateway bond
let res = try_update_gateway_config(test.deps_mut(), info.clone(), update.clone());
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked(owner)
})
);
test.add_dummy_gateway(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
assert!(res.is_ok());
// and the config has actually been updated
let bond =
must_get_gateway_bond_by_owner(test.deps().storage, &Addr::unchecked(owner)).unwrap();
assert_eq!(bond.gateway.host, update.host);
assert_eq!(bond.gateway.mix_port, update.mix_port);
assert_eq!(bond.gateway.clients_port, update.clients_port);
assert_eq!(bond.gateway.location, update.location);
assert_eq!(bond.gateway.version, update.version);
}
#[test]
fn updating_gateway_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "at home".to_string(),
version: "v1.2.3".to_string(),
};
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
@@ -295,7 +295,7 @@ pub(crate) fn try_update_mixnode_config(
}
pub(crate) fn try_update_mixnode_config_on_behalf(
deps: DepsMut,
deps: DepsMut<'_>,
info: MessageInfo,
new_config: MixNodeConfigUpdate,
owner: String,
@@ -308,7 +308,7 @@ pub(crate) fn try_update_mixnode_config_on_behalf(
}
pub(crate) fn _try_update_mixnode_config(
deps: DepsMut,
deps: DepsMut<'_>,
new_config: MixNodeConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
+1 -1
View File
@@ -20,7 +20,7 @@ pub mod test_helpers {
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
};
use crate::interval::{pending_events, storage as interval_storage};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::{self as mixnet_params_storage};
use crate::mixnet_contract_settings::storage::{
minimum_gateway_pledge, minimum_mixnode_pledge, rewarding_denom,
rewarding_validator_address,
+13
View File
@@ -14,6 +14,7 @@ use cosmwasm_std::{
QueryResponse, Response, StdError, StdResult, Timestamp, Uint128,
};
use cw_storage_plus::Bound;
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixId, MixNode};
use semver::Version;
@@ -219,6 +220,9 @@ pub fn execute(
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
try_update_gateway_config(new_config, info, deps)
}
ExecuteMsg::TransferOwnership { to_address } => {
try_transfer_ownership(to_address, info, deps)
}
@@ -300,6 +304,15 @@ pub fn try_update_mixnode_config(
account.try_update_mixnode_config(new_config, deps.storage)
}
pub fn try_update_gateway_config(
new_config: GatewayConfigUpdate,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_update_gateway_config(new_config, deps.storage)
}
pub fn try_update_mixnode_cost_params(
new_costs: MixNodeCostParams,
info: MessageInfo,
@@ -2,6 +2,7 @@ use crate::errors::ContractError;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Env, Response, Storage};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, MixNode,
};
@@ -64,4 +65,10 @@ pub trait GatewayBondingAccount {
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn try_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
storage: &mut dyn Storage,
) -> Result<Response, ContractError>;
}
@@ -4,9 +4,12 @@ use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::GatewayBondingAccount;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, Gateway};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate, ExecuteMsg as MixnetExecuteMsg, Gateway,
};
use vesting_contract_common::events::{
new_vesting_gateway_bonding_event, new_vesting_gateway_unbonding_event,
new_vesting_update_gateway_config_event,
};
use super::Account;
@@ -78,4 +81,22 @@ impl GatewayBondingAccount for Account {
self.remove_gateway_pledge(storage)?;
Ok(())
}
fn try_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
storage: &mut dyn Storage,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::UpdateGatewayConfigOnBehalf {
new_config,
owner: self.owner_address().into_string(),
};
let update_gateway_config_msg =
wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
Ok(Response::new()
.add_message(update_gateway_config_msg)
.add_event(new_vesting_update_gateway_config_event()))
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ rust-version = "1.56"
[dependencies]
anyhow = "1.0.53"
async-trait = { workspace = true }
bip39 = "1.0.1"
bip39 = { workspace = true }
bs58 = "0.4.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
colored = "2.0"
+1 -1
View File
@@ -17,7 +17,7 @@ rust-version = "1.56"
[dependencies]
async-trait = { workspace = true }
bs58 = {version = "0.4.0" }
bip39 = "1"
bip39 = { workspace = true }
cfg-if = "1.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
console-subscriber = { version = "0.1.1", optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
+68 -49
View File
@@ -84,6 +84,18 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
})
}
async fn dump_persistent_state(&self) {
if !self.state.coconut_keypair_is_some().await {
// Delete the files just in case the process is killed before the new keys are generated
std::fs::remove_file(&self.secret_key_path).ok();
std::fs::remove_file(&self.verification_key_path).ok();
}
let persistent_state = PersistentState::from(&self.state);
if let Err(err) = persistent_state.save_to_file(self.state.persistent_state_path()) {
warn!("Could not backup the state for this iteration: {err}");
}
}
pub(crate) async fn handle_epoch_state(&mut self) {
match self.dkg_client.get_current_epoch().await {
Err(err) => warn!("Could not get current epoch state {err}"),
@@ -99,57 +111,64 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
return;
}
if let Err(err) = self.state.is_consistent(epoch.state).await {
error!("Epoch state is corrupted - {err}, the process should be terminated");
return;
}
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
debug!("Epoch state is corrupted - {err}. Awaiting for a DKG restart.");
} else {
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing)
.await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
let persistent_state = PersistentState::from(&self.state);
if let Err(err) =
persistent_state.save_to_file(self.state.persistent_state_path())
{
warn!("Could not backup the state for this iteration: {err}");
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
// We're dumping state here so that we don't do it uselessly during the
// long InProgress state
self.dump_persistent_state().await;
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
self.dump_persistent_state().await;
}
}
if let Ok(current_timestamp) =
+24 -11
View File
@@ -4,6 +4,7 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use log::debug;
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_dkg::bte::setup;
@@ -18,6 +19,7 @@ pub(crate) async fn dealing_exchange(
resharing: bool,
) -> Result<(), CoconutError> {
if state.receiver_index().is_some() {
debug!("Receiver index was set previously, nothing to do");
return Ok(());
}
@@ -45,6 +47,7 @@ pub(crate) async fn dealing_exchange(
return Err(CoconutError::CorruptedCoconutKeyPair);
}
// We can now erase the keypair from memory
debug!("Removing coconut keypair from memory");
state.set_coconut_keypair(None).await;
scalars.push(x);
scalars
@@ -59,6 +62,11 @@ pub(crate) async fn dealing_exchange(
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
let params = setup();
for _ in 0..TOTAL_DEALINGS {
debug!(
"Submitting dealing for indexes {:?} with resharing: {}",
receivers.keys().collect::<Vec<_>>(),
prior_resharing_secrets.front().is_some()
);
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
@@ -71,9 +79,11 @@ pub(crate) async fn dealing_exchange(
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
.await?;
}
} else {
debug!("Nothing to do, waiting for initial dealers to submit dealings");
}
info!("DKG: Finished submitting dealing");
info!("DKG: Finished dealing exchange");
state.set_receiver_index(receiver_index);
Ok(())
@@ -109,7 +119,7 @@ pub(crate) mod tests {
fn insert_dealers(
params: &Params,
dealer_details_db: &Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details_db: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
) -> Vec<DkgKeyPair> {
let mut keypairs = vec![];
for (idx, addr) in TEST_VALIDATORS_ADDRESS.iter().enumerate() {
@@ -119,12 +129,15 @@ pub(crate) mod tests {
keypairs.push(keypair);
dealer_details_db.write().unwrap().insert(
addr.to_string(),
DealerDetails {
address: Addr::unchecked(*addr),
bte_public_key_with_proof,
announce_address: format!("localhost:80{}", idx),
assigned_index: (idx + 1) as u64,
},
(
DealerDetails {
address: Addr::unchecked(*addr),
bte_public_key_with_proof,
announce_address: format!("localhost:80{}", idx),
assigned_index: (idx + 1) as u64,
},
true,
),
);
}
keypairs
@@ -216,7 +229,7 @@ pub(crate) mod tests {
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[1].to_string())
.and_modify(|details| {
let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone())
let mut bytes = bs58::decode(details.0.bte_public_key_with_proof.clone())
.into_vec()
.unwrap();
// Find another value for last byte that still deserializes to a public key with proof
@@ -231,7 +244,7 @@ pub(crate) mod tests {
break;
}
}
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
details.0.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
});
dealing_exchange(&dkg_client, &mut state, OsRng, false)
@@ -257,7 +270,7 @@ pub(crate) mod tests {
let threshold_db = Arc::new(RwLock::new(Some(3)));
let initial_dealers_db = Arc::new(RwLock::new(Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: Some(100),
initial_height: 100,
})));
let dkg_client = DkgClient::new(
DummyClient::new(
+16 -1
View File
@@ -4,6 +4,7 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::State;
use crate::coconut::error::CoconutError;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerType;
pub(crate) async fn public_key_submission(
@@ -12,9 +13,21 @@ pub(crate) async fn public_key_submission(
resharing: bool,
) -> Result<(), CoconutError> {
if state.was_in_progress() {
state.reset_persistent(resharing).await;
let own_address = dkg_client.get_address().await.as_ref().to_string();
let is_initial_dealer = dkg_client
.get_initial_dealers()
.await?
.map(|data| data.initial_dealers.iter().any(|d| *d == own_address))
.unwrap_or(false);
let reset_coconut_keypair = !resharing || !is_initial_dealer;
debug!(
"Resetting state, with coconut keypair reset: {}",
reset_coconut_keypair
);
state.reset_persistent(reset_coconut_keypair).await;
}
if state.node_index().is_some() {
debug!("Node index was set previously, nothing to do");
return Ok(());
}
@@ -23,12 +36,14 @@ pub(crate) async fn public_key_submission(
let index = if let Some(details) = dealer_details.details {
if dealer_details.dealer_type == DealerType::Past {
// If it was a dealer in a previous epoch, re-register it for this epoch
debug!("Registering for the current DKG round, with keys from a previous epoch");
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.await?;
}
details.assigned_index
} else {
debug!("Registering for the first time to be a dealer");
// First time registration
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
+8 -3
View File
@@ -5,6 +5,7 @@ use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::error::CoconutError;
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use cosmwasm_std::Addr;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::EpochState;
use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
@@ -133,7 +134,7 @@ impl ConsistentState for State {
fn proposal_id_value(&self) -> Result<u64, CoconutError> {
self.proposal_id.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Proposal id should have benn set"),
reason: String::from("Proposal id should have been set"),
})
}
}
@@ -242,8 +243,8 @@ impl State {
}
}
pub async fn reset_persistent(&mut self, resharing: bool) {
if !resharing {
pub async fn reset_persistent(&mut self, reset_coconut_keypair: bool) {
if reset_coconut_keypair {
self.coconut_keypair.set(None).await;
}
self.node_index = Default::default();
@@ -360,6 +361,10 @@ impl State {
.iter_mut()
.find(|(addr, _)| *addr == dealer_addr)
{
debug!(
"Dealer {} misbehaved: {:?}. It will be marked locally as bad dealer and ignored",
dealer_addr, reason
);
*value = Err(reason);
}
}
+258 -18
View File
@@ -8,6 +8,7 @@ use crate::coconut::error::CoconutError;
use crate::coconut::helpers::accepted_vote_err;
use cosmwasm_std::Addr;
use cw3::{ProposalResponse, Status};
use log::debug;
use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
@@ -116,6 +117,11 @@ fn derive_partial_keypair(
}
})
.unzip();
debug!(
"Recovering verification keys from dealings of dealers {:?} with receivers {:?}",
filtered_dealers,
filtered_receivers_by_idx.keys().collect::<Vec<_>>()
);
let recovered = try_recover_verification_keys(
&filtered_dealings,
threshold,
@@ -123,10 +129,12 @@ fn derive_partial_keypair(
)?;
recovered_vks.push(recovered);
debug!("Decrypting shares");
let shares = filtered_dealings
.iter()
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
.collect::<Result<_, _>>()?;
debug!("Combining shares into one secret");
let scalar = combine_shares(shares, &filtered_dealers)?;
scalars.push(scalar);
}
@@ -152,13 +160,19 @@ pub(crate) async fn verification_key_submission(
resharing: bool,
) -> Result<(), CoconutError> {
if state.coconut_keypair_is_some().await {
debug!("Coconut keypair was set previously, nothing to do");
return Ok(());
}
let threshold = state.threshold()?;
let dealings_maps =
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
debug!(
"Filtered dealers to {:?}",
dealings_maps[0].keys().collect::<Vec<_>>()
);
let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
debug!("Derived own coconut keypair");
let vk_share = coconut_keypair.verification_key().to_bs58();
nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?;
let res = dkg_client
@@ -173,6 +187,10 @@ pub(crate) async fn verification_key_submission(
.map_err(|_| CoconutError::ProposalIdError {
reason: String::from("proposal id could not be parsed to u64"),
})?;
debug!(
"Submitted own verification key share, proposal id {} is attached to it",
proposal_id
);
state.set_proposal_id(proposal_id);
state.set_coconut_keypair(Some(coconut_keypair)).await;
info!("DKG: Submitted own verification key");
@@ -195,6 +213,7 @@ pub(crate) async fn verification_key_validation(
_resharing: bool,
) -> Result<(), CoconutError> {
if state.voted_vks() {
debug!("Already voted on the verification keys, nothing to do");
return Ok(());
}
@@ -225,10 +244,15 @@ pub(crate) async fn verification_key_validation(
.position(|node_index| contract_share.node_index == *node_index)
{
let ret = if !check_vk_pairing(&params, &recovered_partials[idx], &vk) {
debug!(
"Voting NO to proposal {} because of failed VK pairing",
proposal_id
);
dkg_client
.vote_verification_key_share(proposal_id, false)
.await
} else {
debug!("Voting YES to proposal {}", proposal_id);
dkg_client
.vote_verification_key_share(proposal_id, true)
.await
@@ -237,6 +261,10 @@ pub(crate) async fn verification_key_validation(
}
}
Err(_) => {
debug!(
"Voting NO to proposal {} because of failed base 58 deserialization",
proposal_id
);
let ret = dkg_client
.vote_verification_key_share(proposal_id, false)
.await;
@@ -256,6 +284,7 @@ pub(crate) async fn verification_key_finalization(
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
debug!("Already executed the proposal, nothing to do");
return Ok(());
}
@@ -294,7 +323,7 @@ pub(crate) mod tests {
use validator_client::nyxd::AccountId;
struct MockContractDb {
dealer_details_db: Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details_db: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
dealings_db: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
@@ -315,10 +344,11 @@ pub(crate) mod tests {
}
}
const TEST_VALIDATORS_ADDRESS: [&str; 3] = [
const TEST_VALIDATORS_ADDRESS: [&str; 4] = [
"n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus",
"n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v",
"n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6",
"n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv",
];
async fn prepare_clients_and_states(db: &MockContractDb) -> Vec<(DkgClient, State)> {
@@ -418,7 +448,7 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 3);
assert_eq!(mapping.len(), 4);
}
}
}
@@ -471,7 +501,7 @@ pub(crate) mod tests {
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: None,
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
@@ -504,7 +534,7 @@ pub(crate) mod tests {
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
@@ -542,7 +572,7 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 2);
assert_eq!(mapping.len(), 3);
}
let corrupted_status = state
.all_dealers()
@@ -803,6 +833,9 @@ pub(crate) mod tests {
async fn reshare_preserves_keys() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
let params = Parameters::new(4).unwrap();
let mut vks = vec![];
@@ -839,23 +872,22 @@ pub(crate) mod tests {
KeyPair::new(),
);
let removed_dealer = clients_and_states.first().unwrap().0.get_address().await;
db.dealer_details_db
.write()
.unwrap()
.remove(removed_dealer.as_ref());
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
let mut initial_dealers = vec![];
for (dkg_client, _) in clients_and_states.iter() {
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
initial_dealers.push(client_address);
}
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![
Addr::unchecked(clients_and_states[1].0.get_address().await.as_ref()),
Addr::unchecked(clients_and_states[2].0.get_address().await.as_ref()),
],
initial_height: None,
initial_dealers,
initial_height: 1,
});
*clients_and_states.first_mut().unwrap() = (new_dkg_client, state);
clients_and_states[1].1.set_was_in_progress();
clients_and_states[2].1.set_was_in_progress();
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, true)
@@ -880,6 +912,214 @@ pub(crate) mod tests {
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, true)
.await
.unwrap();
}
assert!(db
.proposal_db
.read()
.unwrap()
.values()
.all(|proposal| { proposal.status == Status::Executed }));
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
assert_eq!(initial_master_vk, reshared_master_vk);
}
#[tokio::test]
#[ignore] // expensive test
async fn reshare_after_reset() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
let new_dkg_client = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(),
)
.with_dealer_details(&db.dealer_details_db)
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
KeyPair::new(),
);
let new_dkg_client2 = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(),
)
.with_dealer_details(&db.dealer_details_db)
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let state2 = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
KeyPair::new(),
);
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
clients_and_states.pop().unwrap();
let (initial_client2, initial_state2) = clients_and_states.pop().unwrap();
clients_and_states.push((new_dkg_client, state));
clients_and_states.push((new_dkg_client2, state2));
// DKG in reset mode
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, false)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, false)
.await
.unwrap();
}
assert!(db
.proposal_db
.read()
.unwrap()
.values()
.all(|proposal| { proposal.status == Status::Executed }));
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
// DKG in reshare mode
let params = Parameters::new(4).unwrap();
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
let mut initial_dealers = vec![];
for (dkg_client, _) in clients_and_states.iter() {
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
initial_dealers.push(client_address);
}
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers,
initial_height: 1,
});
*clients_and_states.last_mut().unwrap() = (initial_client2, initial_state2);
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, true)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, true)
.await
.unwrap();
}
// assert!(db
// .proposal_db
// .read()
// .unwrap()
// .values()
// .all(|proposal| { proposal.status == Status::Executed }));
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
+1 -1
View File
@@ -91,7 +91,7 @@ pub enum CoconutError {
#[error("Failed to recover assigned node index: {reason}")]
NodeIndexRecoveryError { reason: String },
#[error("Unrecoverable state: {reason}. Process should be restarted")]
#[error("Unrecoverable state: {reason}")]
UnrecoverableState { reason: String },
#[error("DKG has not finished yet in order to derive the coconut key")]
+52 -27
View File
@@ -69,7 +69,7 @@ pub(crate) struct DummyClient {
spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
epoch: Arc<RwLock<Epoch>>,
dealer_details: Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
threshold: Arc<RwLock<Option<Threshold>>>,
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
@@ -122,7 +122,7 @@ impl DummyClient {
pub fn with_dealer_details(
mut self,
dealer_details: &Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
) -> Self {
self.dealer_details = Arc::clone(dealer_details);
self
@@ -233,14 +233,25 @@ impl super::client::Client for DummyClient {
}
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
let (details, dealer_type) = if let Some((details, current)) = self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.cloned()
{
let dealer_type = if current {
DealerType::Current
} else {
DealerType::Past
};
(Some(details), dealer_type)
} else {
(None, DealerType::Unknown)
};
Ok(DealerDetailsResponse {
details: self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.cloned(),
dealer_type: DealerType::Current,
details,
dealer_type,
})
}
@@ -251,6 +262,7 @@ impl super::client::Client for DummyClient {
.unwrap()
.values()
.cloned()
.filter_map(|(d, current)| if current { Some(d) } else { None })
.collect())
}
@@ -287,13 +299,11 @@ impl super::client::Client for DummyClient {
_fee: Option<Fee>,
) -> Result<()> {
if let Some(proposal) = self.proposal_db.write().unwrap().get_mut(&proposal_id) {
// for now, just suppose that first vote is honest
if proposal.status == cw3::Status::Open {
if vote_yes {
proposal.status = cw3::Status::Passed;
} else {
proposal.status = cw3::Status::Rejected;
}
// for now, just suppose that every vote is honest
if !vote_yes {
proposal.status = cw3::Status::Rejected;
} else if vote_yes && proposal.status == cw3::Status::Open {
proposal.status = cw3::Status::Passed;
}
}
Ok(())
@@ -323,22 +333,33 @@ impl super::client::Client for DummyClient {
_resharing: bool,
) -> Result<ExecuteResult> {
let mut dealer_details = self.dealer_details.write().unwrap();
let assigned_index =
if let Some(details) = dealer_details.get(self.validator_address.as_ref()) {
details.assigned_index
} else {
let assigned_index = OsRng.gen();
dealer_details.insert(
self.validator_address.to_string(),
let assigned_index = if let Some((details, active)) =
dealer_details.get_mut(self.validator_address.as_ref())
{
*active = true;
details.assigned_index
} else {
// let assigned_index = OsRng.gen();
let assigned_index = dealer_details
.values()
.map(|(d, _)| d.assigned_index)
.max()
.unwrap_or(0)
+ 1;
dealer_details.insert(
self.validator_address.to_string(),
(
DealerDetails {
address: Addr::unchecked(self.validator_address.to_string()),
bte_public_key_with_proof,
announce_address,
assigned_index,
},
);
assigned_index
};
true,
),
);
assigned_index
};
Ok(ExecuteResult {
logs: vec![Log {
msg_index: 0,
@@ -380,13 +401,17 @@ impl super::client::Client for DummyClient {
share: VerificationKeyShare,
resharing: bool,
) -> Result<ExecuteResult> {
let dealer_details = self
let (dealer_details, active) = self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.unwrap()
.clone();
if !active {
// Just throw some error, not really the correct one
return Err(CoconutError::DepositEncrKeyNotFound);
}
self.verification_share.write().unwrap().insert(
self.validator_address.to_string(),
ContractVKShare {
+1 -1
View File
@@ -20,7 +20,7 @@ tauri-macros = "^1.2.1"
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
+2
View File
@@ -54,6 +54,8 @@ yarn webpack:prod
WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build --debug --apk
```
**NOTE**: Production build without the `--debug` flag requires a signed build.
# Storybook
Run storybook with:
+72 -370
View File
@@ -73,15 +73,6 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.69"
@@ -186,15 +177,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -245,15 +227,16 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5b9d9748b5770d1539657653dc5ac3cd9353549e74238dc0d96c22919128b94"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
@@ -501,7 +484,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -520,21 +503,6 @@ dependencies = [
"keystream",
]
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "cipher"
version = "0.3.0"
@@ -636,7 +604,7 @@ dependencies = [
"sqlx 0.6.2",
"tap",
"thiserror",
"time 0.3.19",
"time",
"tokio",
"tokio-stream",
"tokio-tungstenite",
@@ -649,15 +617,6 @@ dependencies = [
"wasm-utils",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -689,16 +648,6 @@ dependencies = [
"objc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@@ -1005,7 +954,7 @@ dependencies = [
"phf 0.8.0",
"proc-macro2",
"quote",
"smallvec 1.10.0",
"smallvec",
"syn",
]
@@ -1150,50 +1099,6 @@ dependencies = [
"serde",
]
[[package]]
name = "cxx"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "darling"
version = "0.13.4"
@@ -1687,12 +1592,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -2028,7 +1927,7 @@ dependencies = [
"libc",
"once_cell",
"pin-project-lite",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2076,7 +1975,7 @@ dependencies = [
"gobject-sys",
"libc",
"once_cell",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2565,30 +2464,6 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "ico"
version = "0.2.0"
@@ -2659,7 +2534,7 @@ version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"hashbrown 0.12.3",
"serde",
]
@@ -2871,30 +2746,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libappindicator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e1edfdc9b0853358306c6dfb4b77c79c779174256fe93d80c0b5ebca451a2f"
dependencies = [
"glib",
"gtk",
"gtk-sys",
"libappindicator-sys",
"log",
]
[[package]]
name = "libappindicator-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08fcb2bea89cee9613982501ec83eaa2d09256b24540ae463c52a28906163918"
dependencies = [
"gtk-sys",
"libloading",
"once_cell",
]
[[package]]
name = "libc"
version = "0.2.139"
@@ -2922,16 +2773,6 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libm"
version = "0.2.6"
@@ -2970,15 +2811,6 @@ dependencies = [
"safemem",
]
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -3009,7 +2841,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"scopeguard",
]
@@ -3054,7 +2886,7 @@ dependencies = [
"dirs-next",
"objc-foundation",
"objc_id",
"time 0.3.19",
"time",
]
[[package]]
@@ -3095,12 +2927,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -3113,7 +2939,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
"autocfg",
]
[[package]]
@@ -3273,7 +3099,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-traits",
]
@@ -3283,7 +3109,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-integer",
"num-traits",
]
@@ -3294,7 +3120,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"libm",
]
@@ -3329,6 +3155,15 @@ dependencies = [
"syn",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "nym-api-requests"
version = "0.1.0"
@@ -3405,11 +3240,10 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.1.9"
version = "1.1.12"
dependencies = [
"anyhow",
"bip39",
"chrono",
"client-core",
"dirs",
"eyre",
@@ -3438,6 +3272,7 @@ dependencies = [
"tempfile",
"tendermint-rpc",
"thiserror",
"time",
"tokio",
"ts-rs",
"url",
@@ -3550,10 +3385,10 @@ dependencies = [
"nym-contracts-common",
"schemars",
"serde",
"serde_json",
"serde-json-wasm",
"serde_repr",
"thiserror",
"time 0.3.19",
"time",
]
[[package]]
@@ -4002,7 +3837,7 @@ version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"cc",
"libc",
"openssl-src",
@@ -4097,7 +3932,7 @@ dependencies = [
"instant",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"winapi",
]
@@ -4110,7 +3945,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -4381,7 +4216,7 @@ dependencies = [
"line-wrap",
"quick-xml 0.26.0",
"serde",
"time 0.3.19",
"time",
]
[[package]]
@@ -4545,25 +4380,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg 0.1.2",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
@@ -4574,8 +4390,8 @@ dependencies = [
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"rand_pcg 0.2.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -4589,16 +4405,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -4619,21 +4425,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -4662,15 +4453,6 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -4680,50 +4462,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
@@ -4733,15 +4471,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "raw-window-handle"
version = "0.5.0"
@@ -4751,15 +4480,6 @@ dependencies = [
"cty",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -5099,12 +4819,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]]
name = "sct"
version = "0.6.1"
@@ -5177,7 +4891,7 @@ dependencies = [
"phf_codegen",
"precomputed-hash",
"servo_arc",
"smallvec 1.10.0",
"smallvec",
"thin-slice",
]
@@ -5445,16 +5159,7 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
"autocfg",
]
[[package]]
@@ -5626,7 +5331,7 @@ dependencies = [
"percent-encoding",
"rustls 0.19.1",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.1.8",
"sqlx-rt 0.5.13",
"stringprep",
@@ -5673,7 +5378,7 @@ dependencies = [
"rustls 0.20.8",
"rustls-pemfile",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.2.1",
"sqlx-rt 0.6.2",
"stringprep",
@@ -5906,7 +5611,6 @@ dependencies = [
"core-foundation",
"core-graphics",
"crossbeam-channel",
"dirs-next",
"dispatch",
"gdk",
"gdk-pixbuf",
@@ -5920,7 +5624,6 @@ dependencies = [
"instant",
"jni",
"lazy_static",
"libappindicator",
"libc",
"log",
"ndk",
@@ -6059,7 +5762,7 @@ dependencies = [
"sha2 0.10.6",
"tauri-utils",
"thiserror",
"time 0.3.19",
"time",
"url",
"uuid 1.3.0",
"walkdir",
@@ -6199,7 +5902,7 @@ dependencies = [
"subtle 2.4.1",
"subtle-encoding",
"tendermint-proto",
"time 0.3.19",
"time",
"zeroize",
]
@@ -6232,7 +5935,7 @@ dependencies = [
"serde",
"serde_bytes",
"subtle-encoding",
"time 0.3.19",
"time",
]
[[package]]
@@ -6260,7 +5963,7 @@ dependencies = [
"tendermint-config",
"tendermint-proto",
"thiserror",
"time 0.3.19",
"time",
"tokio",
"tracing",
"url",
@@ -6324,17 +6027,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.19"
@@ -6343,6 +6035,8 @@ checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2"
dependencies = [
"itoa 1.0.5",
"js-sys",
"libc",
"num_threads",
"serde",
"time-core",
"time-macros",
@@ -6363,13 +6057,28 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"bytes",
"libc",
"memchr",
@@ -6564,7 +6273,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -6667,11 +6376,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
@@ -6680,12 +6389,6 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode-xid"
version = "0.2.4"
@@ -6809,7 +6512,7 @@ dependencies = [
"rustc_version 0.4.0",
"rustversion",
"thiserror",
"time 0.3.19",
"time",
]
[[package]]
@@ -6872,12 +6575,6 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -7438,3 +7135,8 @@ dependencies = [
"syn",
"synstructure",
]
[[patch.unused]]
name = "tauri-mobile"
version = "0.2.4"
source = "git+https://github.com/tauri-apps/tauri-mobile?branch=dev#442f0d2c7328930db61058a55706d22e6a401c16"
+10 -6
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-connect"
version = "1.1.9"
description = "nym-connect for Mobile"
version = "1.1.12"
description = "nym-connect mobile"
authors = ["Nym Technologies SA"]
license = ""
repository = ""
@@ -19,10 +19,13 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.1", features = [] }
# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] }
# TODO untill new tauri version includes https://github.com/tauri-apps/tauri-mobile/pull/111
[patch.crates-io]
tauri-mobile = { git = "https://github.com/tauri-apps/tauri-mobile", branch = "dev" }
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
chrono = "0.4"
bip39 = { version = "2.0.0", features = ["zeroize"] }
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
@@ -38,10 +41,11 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
tap = "1.0.1"
# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
tauri = { version = "2.0.0-alpha.3", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
# tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
tauri = { version = "2.0.0-alpha.3", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
time = { version = "0.3.17", features = ["local-offset"] }
tokio = { version = "1.24.1", features = ["sync", "time"] }
url = "2.2"
yaml-rust = "0.4"
-5
View File
@@ -16,11 +16,6 @@ pub mod operations;
mod state;
mod tasks;
#[cfg(desktop)]
mod menu;
#[cfg(desktop)]
mod window;
pub use state::State;
#[cfg(mobile)]
+33 -4
View File
@@ -1,9 +1,26 @@
use std::str::FromStr;
use fern::colors::{Color, ColoredLevelConfig};
use serde::Serialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::str::FromStr;
use tauri::Manager;
use time::{format_description, OffsetDateTime};
fn formatted_time() -> String {
// if we fail to obtain local time according to local offset, fallback to utc
let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
// if we're running it in the unit test, use the hardcoded value
#[cfg(test)]
let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap();
// the unwraps are fine as we know this description is correct
// note: the reason for this very particular format is a very simple one
// it's what we've always been using since we copied it from the example,
// so feel free to update it to whatever
let format =
format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap();
_now.format(&format).unwrap()
}
pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> {
let colors = ColoredLevelConfig::new()
@@ -21,7 +38,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
formatted_time(),
record.target(),
colors.color(record.level()),
message,
@@ -33,7 +50,7 @@ pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerE
.format(move |out, message, record| {
out.finish(format_args!(
"{}[{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
formatted_time(),
record.target(),
message,
))
@@ -116,3 +133,15 @@ impl From<log::Level> for LogLevel {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_formatting() {
let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string();
let new_time_based = formatted_time();
assert_eq!(new_time_based, expected_chrono_formated)
}
}
-6
View File
@@ -6,11 +6,5 @@
use nym_connect::AppBuilder;
fn main() {
// As per breaking change description here
// https://github.com/tauri-apps/tauri/blob/feac1d193c6d618e49916ad0707201f43d5cdd36/tooling/bundler/CHANGELOG.md
//if let Err(error) = fix_path_env::fix() {
// log::warn!("Failed to fix PATH: {error}");
//}
AppBuilder::new().run();
}
-62
View File
@@ -1,62 +0,0 @@
use tauri::{
AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
SystemTrayMenuItem, Wry,
};
use crate::window::window_toggle;
pub const SHOW_LOG_WINDOW: &str = "show_log_window";
pub const CLEAR_STORAGE: &str = "clear_storage";
pub trait AddDefaultSubmenus {
fn add_default_app_submenus(self) -> Self;
}
impl AddDefaultSubmenus for Menu {
fn add_default_app_submenus(self) -> Self {
let submenu = Submenu::new(
"Help",
Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")),
);
self.add_submenu(submenu)
}
}
pub const TRAY_MENU_QUIT: &str = "quit";
pub const TRAY_MENU_SHOW_HIDE: &str = "show-hide";
pub const TRAY_MENU_CONNECTION: &str = "connection";
pub(crate) fn create_tray_menu() -> SystemTray {
let quit = CustomMenuItem::new(TRAY_MENU_QUIT, "Quit");
let hide = CustomMenuItem::new(TRAY_MENU_SHOW_HIDE, "Hide");
let connection = CustomMenuItem::new(TRAY_MENU_CONNECTION, "Connect");
let tray_menu = SystemTrayMenu::new()
.add_item(hide)
.add_item(connection)
.add_native_item(SystemTrayMenuItem::Separator)
.add_item(quit);
SystemTray::new().with_menu(tray_menu)
}
pub(crate) fn tray_menu_event_handler(app: &AppHandle<Wry>, event: SystemTrayEvent) {
match event {
SystemTrayEvent::LeftClick { position, size, .. } => {
println!("Event {position:?} {size:?}");
}
SystemTrayEvent::MenuItemClick { id, .. } => {
println!("Event {id}");
match id.as_str() {
TRAY_MENU_SHOW_HIDE => {
window_toggle(app);
}
TRAY_MENU_QUIT => {
// TODO: add disconnecting first
app.exit(0);
}
_ => {}
}
}
_ => {}
}
}
@@ -3,5 +3,3 @@ pub mod directory;
pub mod export;
pub mod help;
pub mod http;
#[cfg(desktop)]
pub mod window;
@@ -1,7 +0,0 @@
use crate::window::window_hide;
use tauri::{AppHandle, Wry};
#[tauri::command]
pub fn hide_window(app: AppHandle<Wry>) {
window_hide(&app);
}
@@ -1,30 +0,0 @@
use crate::menu::TRAY_MENU_SHOW_HIDE;
use tauri::{AppHandle, Manager};
pub(crate) fn window_hide(app: &AppHandle<tauri::Wry>) {
let window = app.get_window("main").unwrap();
let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE);
if window.is_visible().unwrap() {
window.hide().unwrap();
item_handle.set_title("Show").unwrap();
}
}
pub(crate) fn window_show(app: &AppHandle<tauri::Wry>) {
let window = app.get_window("main").unwrap();
let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE);
if !window.is_visible().unwrap() {
window.show().unwrap();
item_handle.set_title("Hide").unwrap();
window.set_focus().unwrap();
}
}
pub(crate) fn window_toggle(app: &AppHandle<tauri::Wry>) {
let window = app.get_window("main").unwrap();
if window.is_visible().unwrap() {
window_hide(app);
} else {
window_show(app);
}
}
+8 -11
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.9"
"version": "1.1.12"
},
"build": {
"distDir": "../dist",
@@ -14,7 +14,13 @@
"active": true,
"targets": "all",
"identifier": "net.nymtech.connect",
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "Copyright © 2021-2022 Nym Technologies SA",
@@ -37,10 +43,6 @@
"timestampUrl": ""
}
},
"systemTray": {
"iconPath": "icons/tray_icon.png",
"iconAsTemplate": true
},
"updater": {
"active": false
},
@@ -51,11 +53,6 @@
"clipboard": {
"writeText": true
},
"window": {
"startDragging": true,
"close": true,
"minimize": true
},
"notification": {
"all": true
}
@@ -6,12 +6,13 @@ import { CustomTitleBar } from './CustomTitleBar';
export const AppWindowFrame: FCWithChildren = ({ children }) => {
const location = useLocation();
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } =
useClientContext();
// defined functions to be used when moving away from pages
const onBack = () => {
switch (location.pathname) {
case '/menu/settings':
case '/menu/settings/gateway':
return () => {
// when the user moves away from the settings page and the gateway is not valid
// set isActive to false
@@ -19,6 +20,14 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
setUserDefinedGateway((current) => ({ ...current, isActive: false }));
}
};
case '/menu/settings/service-provider':
return () => {
// when the user moves away from the settings page and the sp is not valid
// set isActive to false
if (!userDefinedSPAddress?.address) {
setUserDefinedSPAddress((current) => ({ ...current, isActive: false }));
}
};
default:
return undefined;
}
@@ -33,7 +42,7 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
}}
>
<CustomTitleBar path={location.pathname} onBack={onBack()} />
<Box style={{ padding: '16px', paddingTop: '24px' }}>{children}</Box>
<Box style={{ padding: '16px' }}>{children}</Box>
</Box>
);
};
@@ -38,7 +38,8 @@ const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => {
return <CustomButton Icon={ArrowBack} onClick={handleBack} />;
};
const getTitleIcon = (path: string) => {
const getTitle = (path: string) => {
if (path.includes('settings')) return 'Settings';
if (path !== '/') {
const title = path.split('/').slice(-1);
return (
@@ -60,7 +61,7 @@ export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?:
<Box style={customTitleBarStyles.titlebar}>
{/* set width to keep logo centered */}
<Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon onBack={onBack} />}</Box>
{getTitleIcon(path)}
{path !== '/' && <Box sx={{ width: '40px' }} />}
{getTitle(path)}
<Box sx={{ width: '40px' }} />
</Box>
);
@@ -4,6 +4,10 @@ import { ServiceProvider } from 'src/types/directory';
export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => (
<Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 200, p: 1 }}>
<Typography variant="body2" fontWeight="bold">
Connection info
</Typography>
<Divider />
<Typography variant="caption" fontWeight="bold">
Gateway <Typography variant="caption">{serviceProvider.gateway}</Typography>
</Typography>
@@ -16,6 +20,10 @@ export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: Serv
export const GatwayWarningInfo = () => (
<Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 200, p: 1 }}>
<Typography variant="body2" fontWeight="bold" color="warning.main">
Connection issue
</Typography>
<Divider />
<Typography variant="caption">Try disconnecting and connecting again</Typography>
</Stack>
);
+50 -35
View File
@@ -4,13 +4,14 @@ import { invoke } from '@tauri-apps/api';
import { Error } from 'src/types/error';
import { getVersion } from '@tauri-apps/api/app';
import { useEvents } from 'src/hooks/events';
import { UserDefinedGateway } from 'src/types/gateway';
import { forage } from '@tauri-apps/tauri-forage';
import { UserDefinedGateway, UserDefinedSPAddress } from 'src/types/service-provider';
import { getItemFromStorage, setItemInStorage } from 'src/utils';
import { ConnectionStatusKind, GatewayPerformance } from '../types';
import { ConnectionStatsItem } from '../components/ConnectionStats';
import { ServiceProvider } from '../types/directory';
const FORAGE_KEY = 'nym-connect-user-gateway';
const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway';
const FORAGE_SP_KEY = 'nym-connect-user-sp';
type ModeType = 'light' | 'dark';
@@ -25,16 +26,19 @@ export type TClientContext = {
selectedProvider?: ServiceProvider;
showInfoModal: boolean;
userDefinedGateway?: UserDefinedGateway;
userDefinedSPAddress: UserDefinedSPAddress;
serviceProviders?: ServiceProvider[];
setMode: (mode: ModeType) => void;
clearError: () => void;
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
setConnectedSince: (connectedSince: DateTime | undefined) => void;
setShowInfoModal: (show: boolean) => void;
setRandomSerivceProvider: () => void;
setSerivceProvider: () => void;
startConnecting: () => Promise<void>;
startDisconnecting: () => Promise<void>;
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>;
};
export const ClientContext = createContext({} as TClientContext);
@@ -50,43 +54,39 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
const [appVersion, setAppVersion] = useState<string>();
const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good');
const [showInfoModal, setShowInfoModal] = useState(false);
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({ isActive: false, gateway: '' });
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({
isActive: false,
gateway: undefined,
});
const [userDefinedSPAddress, setUserDefinedSPAddress] = useState<UserDefinedSPAddress>({
isActive: false,
address: undefined,
});
const getAppVersion = async () => {
const version = await getVersion();
return version;
};
const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => {
try {
await forage.setItem({
key: FORAGE_KEY,
value: gateway,
} as any)();
} catch (e) {
console.warn(e);
}
return undefined;
};
useEffect(() => {
setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway });
}, [userDefinedGateway]);
const getUserGatewayFromStorage = async (): Promise<UserDefinedGateway | undefined> => {
try {
const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })();
return gatewayFromStorage;
} catch (e) {
console.warn(e);
}
return undefined;
};
useEffect(() => {
setItemInStorage({ key: FORAGE_SP_KEY, value: userDefinedSPAddress });
}, [userDefinedSPAddress]);
const initialiseApp = async () => {
const services = await invoke('get_services');
const AppVersion = await getAppVersion();
const storedUserDefinedGateway = await getUserGatewayFromStorage();
const storedUserDefinedGateway = await getItemFromStorage({ key: FORAGE_GATEWAY_KEY });
const storedUserDefinedSP = await getItemFromStorage({ key: FORAGE_SP_KEY });
setAppVersion(AppVersion);
setServiceProviders(services as ServiceProvider[]);
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP);
};
useEvents({
@@ -125,27 +125,37 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
}, []);
const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive;
const shouldUseUserSP = !!userDefinedSPAddress.address && userDefinedSPAddress.isActive;
const setServiceProvider = async (newServiceProvider: ServiceProvider) => {
await invoke('set_gateway', {
gateway: newServiceProvider.gateway,
gateway: shouldUseUserGateway ? userDefinedGateway.gateway : newServiceProvider.gateway,
});
await invoke('set_service_provider', {
serviceProvider: shouldUseUserSP ? userDefinedSPAddress.address : newServiceProvider.address,
});
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
};
const getRandomSPFromList = (services: ServiceProvider[]) => {
const randomSelection = services[Math.floor(Math.random() * services.length)];
if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider;
return randomSelection;
};
const setRandomSerivceProvider = async () => {
const buildServiceProvider = async (serviceProvider: ServiceProvider) => {
const sp = { ...serviceProvider };
if (shouldUseUserGateway) sp.gateway = userDefinedGateway.gateway as string;
if (shouldUseUserSP) sp.address = userDefinedSPAddress.address as string;
return sp;
};
const setSerivceProvider = async () => {
if (serviceProviders) {
const randomServiceProvider = getRandomSPFromList(serviceProviders);
await setServiceProvider(randomServiceProvider);
await setUserGatewayInStorage(userDefinedGateway);
setSelectedProvider(randomServiceProvider);
const withUserDefinitions = await buildServiceProvider(randomServiceProvider);
await setServiceProvider(withUserDefinitions);
setSelectedProvider(withUserDefinitions);
}
return undefined;
};
@@ -165,21 +175,25 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
showInfoModal,
setConnectionStats,
selectedProvider,
serviceProviders,
connectedSince,
setConnectedSince,
setRandomSerivceProvider,
setSerivceProvider,
startConnecting,
startDisconnecting,
gatewayPerformance,
setShowInfoModal,
userDefinedSPAddress,
userDefinedGateway,
setUserDefinedGateway,
setUserDefinedSPAddress,
}),
[
mode,
appVersion,
error,
showInfoModal,
serviceProviders,
connectedSince,
connectionStatus,
connectionStats,
@@ -187,6 +201,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
gatewayPerformance,
selectedProvider,
userDefinedGateway,
userDefinedSPAddress,
],
);
@@ -10,6 +10,7 @@ const mockValues: TClientContext = {
gatewayPerformance: 'Good',
showInfoModal: false,
userDefinedGateway: { isActive: false, gateway: '' },
userDefinedSPAddress: { isActive: false, address: '' },
setShowInfoModal: () => {},
setMode: () => {},
clearError: () => {},
@@ -18,8 +19,9 @@ const mockValues: TClientContext = {
setConnectionStatus: () => {},
startConnecting: async () => {},
startDisconnecting: async () => {},
setRandomSerivceProvider: () => {},
setSerivceProvider: () => {},
setUserDefinedGateway: () => {},
setUserDefinedSPAddress: () => {},
};
export const MockProvider: FCWithChildren<{
@@ -29,7 +29,7 @@ export const ConnectionPage = () => {
// eslint-disable-next-line default-case
switch (currentStatus) {
case 'disconnected':
await context.setRandomSerivceProvider();
await context.setSerivceProvider();
await context.startConnecting();
context.setConnectedSince(DateTime.now());
context.setShowInfoModal(true);
+1 -1
View File
@@ -9,7 +9,7 @@ const appsSchema = {
export const CompatibleApps = () => (
<Box>
<Typography fontWeight={600} sx={{ mb: 3 }}>
<Typography fontWeight={600} color="grey.600" sx={{ mb: 3 }}>
Supported apps
</Typography>
<Typography color="nym.highlight" fontWeight={400} sx={{ mb: 2 }}>
@@ -5,8 +5,8 @@ import { useClientContext } from 'src/context/main';
import { ConnectionStatusKind } from 'src/types';
import { AppVersion } from 'src/components/AppVersion';
export const Settings = () => {
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
export const GatewaySettings = () => {
const { userDefinedGateway, setUserDefinedGateway, connectionStatus } = useClientContext();
const [gatewayKey, setGatewayKey] = useState<string | undefined>(userDefinedGateway?.gateway);
const handleIsValidGatewayKey = (isValid: boolean) => {
@@ -20,17 +20,14 @@ export const Settings = () => {
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.warn('HANERE***');
setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked }));
};
const { connectionStatus } = useClientContext();
return (
<Box height="100%">
<Stack justifyContent="space-between" height="100%">
<Box>
<Typography fontWeight="bold" variant="body2" mb={1} fontSize="14px">
<Box mt={3}>
<Typography fontWeight="bold" variant="body2" mb={1}>
Select your Gateway
</Typography>
<Typography color="grey.300" variant="body2" mb={3}>
@@ -61,6 +58,7 @@ export const Settings = () => {
onValidate={handleIsValidGatewayKey}
sx={{ mt: 3 }}
disabled={connectionStatus === 'connected' || !userDefinedGateway?.isActive}
autoFocus
/>
)}
</FormControl>
@@ -0,0 +1,101 @@
import React, { ChangeEvent } from 'react';
import {
Autocomplete,
Box,
FormControl,
FormControlLabel,
FormHelperText,
Stack,
Switch,
TextField,
Typography,
} from '@mui/material';
import { AppVersion } from 'src/components/AppVersion';
import { ConnectionStatusKind } from 'src/types';
import { useClientContext } from 'src/context/main';
export const ServiceProviderSettings = () => {
const { connectionStatus, serviceProviders, userDefinedSPAddress, setUserDefinedSPAddress } = useClientContext();
const toggleOnOff = (e: ChangeEvent<HTMLInputElement>) => {
setUserDefinedSPAddress((current) => ({ ...current, isActive: e.target.checked }));
};
const handleSelectFromList = (value: string | null) => {
setUserDefinedSPAddress((current) => ({ ...current, address: value ?? undefined }));
};
const getSPDescription = (spAddress: string) => {
const match = serviceProviders?.find((sp) => sp.address === spAddress);
if (match) return match.description;
return 'N/A';
};
const validateInput = (value: string) => {
setUserDefinedSPAddress((current) => ({ ...current, address: !value.length ? undefined : value }));
};
return (
<Box height="100%">
<Stack justifyContent="space-between" height="100%">
<Box mt={3}>
<Typography fontWeight="bold" variant="body2" mb={1}>
Select your Service Provider
</Typography>
<Typography color="grey.300" variant="body2" mb={3}>
Pick a service provider from the list or enter your own
</Typography>
<FormControl fullWidth>
<FormControlLabel
control={
<Switch
checked={userDefinedSPAddress.isActive}
onChange={toggleOnOff}
disabled={connectionStatus === ConnectionStatusKind.connected}
size="small"
sx={{ ml: 1 }}
/>
}
label={userDefinedSPAddress.isActive ? 'On' : 'Off'}
/>
{connectionStatus === ConnectionStatusKind.connected && (
<FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText>
)}
{userDefinedSPAddress.isActive && serviceProviders && (
<Autocomplete
clearOnEscape
disabled={connectionStatus === 'connected'}
sx={{ mt: 3 }}
options={serviceProviders.map((sp) => sp.address)}
freeSolo
value={userDefinedSPAddress.address || ''}
onChange={(e, value) => handleSelectFromList(value)}
size="small"
renderInput={(params) => (
<TextField
autoFocus
{...params}
placeholder="Service provider"
onChange={(e) => validateInput(e.target.value)}
/>
)}
ListboxProps={{ style: { background: 'unset', fontSize: '14px' } }}
/>
)}
</FormControl>
{userDefinedSPAddress.address && userDefinedSPAddress.isActive && (
<Box sx={{ mt: 3 }}>
<Typography variant="body2">Name of Service Provider</Typography>
<Typography variant="body2" sx={{ mt: 1 }} color="grey.400">
{getSPDescription(userDefinedSPAddress.address)}
</Typography>
</Box>
)}
</Box>
<AppVersion />
</Stack>
</Box>
);
};
@@ -0,0 +1,26 @@
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Link, List, ListItem, ListItemButton, ListItemText, Stack } from '@mui/material';
import { AppVersion } from 'src/components/AppVersion';
const menuSchema = [
{ title: 'Select your gateway', path: 'gateway' },
{ title: 'Select a service provider', path: 'service-provider' },
];
export const SettingsMenu = () => (
<Stack justifyContent="space-between" height="100%">
<List sx={{ mt: 3 }} dense disablePadding>
{menuSchema.map((item) => (
<Link component={RouterLink} to={item.path} underline="none" color="white" key={item.title}>
<ListItem disablePadding>
<ListItemButton>
<ListItemText>{item.title}</ListItemText>
</ListItemButton>
</ListItem>
</Link>
))}
</List>
<AppVersion />
</Stack>
);
+8 -2
View File
@@ -4,7 +4,9 @@ import { ConnectionPage } from 'src/pages/connection';
import { Menu } from 'src/pages/menu';
import { CompatibleApps } from 'src/pages/menu/Apps';
import { HelpGuide } from 'src/pages/menu/Guide';
import { Settings } from 'src/pages/menu/Settings';
import { SettingsMenu } from 'src/pages/menu/settings';
import { GatewaySettings } from 'src/pages/menu/settings/GatewaySettings';
import { ServiceProviderSettings } from 'src/pages/menu/settings/ServiceProviderSettings';
export const AppRoutes = () => (
<Routes>
@@ -13,7 +15,11 @@ export const AppRoutes = () => (
<Route index element={<Menu />} />
<Route path="apps" element={<CompatibleApps />} />
<Route path="guide" element={<HelpGuide />} />
<Route path="settings" element={<Settings />} />
<Route path="settings">
<Route index element={<SettingsMenu />} />
<Route path="gateway" element={<GatewaySettings />} />
<Route path="service-provider" element={<ServiceProviderSettings />} />
</Route>
</Route>
</Routes>
);
@@ -0,0 +1,9 @@
export interface UserDefinedGateway {
isActive: boolean;
gateway?: string;
}
export interface UserDefinedSPAddress {
isActive: boolean;
address?: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface StorageKeyValue<T> {
key: string;
value: T;
}
+29
View File
@@ -1,5 +1,8 @@
import { useEffect, useRef } from 'react';
import { EventName, listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api';
import { forage } from '@tauri-apps/tauri-forage';
import { StorageKeyValue } from 'src/types/storage';
export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) => {
const unlisten = useRef<UnlistenFn>();
@@ -17,3 +20,29 @@ export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) =
};
}, []);
};
export const toggleLogViewer = async () => {
await invoke('help_log_toggle_window');
};
export async function setItemInStorage<T>({ key, value }: StorageKeyValue<T>) {
try {
await forage.setItem({
key,
value,
} as any)();
} catch (e) {
console.warn(e);
}
return undefined;
}
export const getItemFromStorage = async ({ key }: Pick<StorageKeyValue<undefined>, 'key'>) => {
try {
const gatewayFromStorage = await forage.getItem({ key })();
return gatewayFromStorage;
} catch (e) {
console.warn(e);
}
return undefined;
};
+60 -198
View File
@@ -181,15 +181,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -240,22 +231,23 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.0.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.9.7"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
[[package]]
name = "bitflags"
@@ -476,7 +468,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -485,7 +477,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -569,15 +561,6 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -869,7 +852,7 @@ dependencies = [
"phf 0.8.0",
"proc-macro2",
"quote",
"smallvec 1.10.0",
"smallvec",
"syn",
]
@@ -1431,12 +1414,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -1762,7 +1739,7 @@ dependencies = [
"gobject-sys",
"libc",
"once_cell",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2248,7 +2225,7 @@ version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"hashbrown",
"serde",
]
@@ -2508,7 +2485,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"scopeguard",
]
@@ -2581,12 +2558,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -2599,7 +2570,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
"autocfg",
]
[[package]]
@@ -2729,7 +2700,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-traits",
]
@@ -2739,7 +2710,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-integer",
"num-traits",
]
@@ -2750,7 +2721,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"libm",
]
@@ -2878,6 +2849,20 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-crypto"
version = "0.2.0"
dependencies = [
"bs58",
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
]
[[package]]
name = "nym-dkg"
version = "0.1.0"
@@ -2899,20 +2884,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-crypto"
version = "0.1.0"
dependencies = [
"bs58",
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
]
[[package]]
name = "nym-execute"
version = "0.1.0"
@@ -2941,7 +2912,7 @@ dependencies = [
"nym-contracts-common",
"schemars",
"serde",
"serde_json",
"serde-json-wasm",
"serde_repr",
"thiserror",
"time",
@@ -2984,7 +2955,7 @@ dependencies = [
[[package]]
name = "nym-sphinx-types"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"sphinx-packet",
]
@@ -3110,7 +3081,6 @@ dependencies = [
"nym-wallet-types",
"once_cell",
"pretty_env_logger",
"rand 0.6.5",
"rand_chacha 0.2.2",
"reqwest",
"serde",
@@ -3258,7 +3228,7 @@ version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"cc",
"libc",
"pkg-config",
@@ -3330,7 +3300,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -3790,25 +3760,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg 0.1.2",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
@@ -3819,8 +3770,8 @@ dependencies = [
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"rand_pcg 0.2.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -3834,16 +3785,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -3864,21 +3805,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -3907,15 +3833,6 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -3925,50 +3842,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
@@ -3978,15 +3851,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "raw-window-handle"
version = "0.5.0"
@@ -3996,15 +3860,6 @@ dependencies = [
"cty",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -4350,7 +4205,7 @@ dependencies = [
"phf_codegen",
"precomputed-hash",
"servo_arc",
"smallvec 1.10.0",
"smallvec",
"thin-slice",
]
@@ -4605,16 +4460,7 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
"autocfg",
]
[[package]]
@@ -5281,13 +5127,28 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"bytes",
"libc",
"memchr",
@@ -5434,7 +5295,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -5517,11 +5378,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
@@ -6228,6 +6089,7 @@ version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
dependencies = [
"serde",
"zeroize_derive",
]
@@ -10,7 +10,7 @@ aes-gcm = "0.9"
anyhow = "1.0"
argon2 = "0.4"
base64 = "0.13"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
clap = { version = "4.0", features = ["derive"] }
log = "0.4"
pretty_env_logger = "0.4"
+1 -1
View File
@@ -11,7 +11,7 @@ serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
nym-config = { path = "../../common/config" }
+2 -3
View File
@@ -20,7 +20,7 @@ tauri-macros = "=1.2.1"
[dependencies]
async-trait = "0.1.64"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize", "rand"] }
cfg-if = "1.0.0"
colored = "2.0"
dirs = "4.0"
@@ -32,7 +32,6 @@ itertools = "0.10"
log = { version = "0.4", features = ["serde"] }
once_cell = "1.7.2"
pretty_env_logger = "0.4"
rand = "0.6.5"
reqwest = {version = "0.11.9", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
@@ -50,7 +49,7 @@ k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
aes-gcm = "0.9.4"
argon2 = { version = "0.3.2", features = ["std"] }
base64 = "0.13"
zeroize = "1.4.3"
zeroize = { version = "1.5", features = ["zeroize_derive", "serde"] }
cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
+1
View File
@@ -63,6 +63,7 @@ fn main() {
mixnet::bond::unbond_mixnode,
mixnet::bond::update_mixnode_cost_params,
mixnet::bond::update_mixnode_config,
mixnet::bond::update_gateway_config,
mixnet::bond::get_number_of_mixnode_delegators,
mixnet::bond::get_mix_node_description,
mixnet::bond::get_mixnode_avg_uptime,
@@ -2,15 +2,14 @@ use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER};
use crate::error::BackendError;
use crate::network_config;
use crate::state::{WalletAccountIds, WalletState};
use crate::utils::{SensitiveStringWrapper, ZeroizeMnemonicWrapper};
use crate::wallet_storage::{self, UserPassword, DEFAULT_LOGIN_ID};
use bip39::rand::{self, seq::SliceRandom};
use bip39::{Language, Mnemonic};
use cosmrs::bip32::DerivationPath;
use itertools::Itertools;
use nym_config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
use nym_types::account::{Account, AccountEntry, Balance};
use nym_wallet_types::network::Network as WalletNetwork;
use rand::seq::SliceRandom;
use std::collections::HashMap;
use strum::IntoEnumIterator;
use url::Url;
@@ -19,10 +18,9 @@ use validator_client::{nyxd::SigningNyxdClient, Client};
#[tauri::command]
pub async fn connect_with_mnemonic(
mnemonic: SensitiveStringWrapper,
mnemonic: Mnemonic,
state: tauri::State<'_, WalletState>,
) -> Result<Account, BackendError> {
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
_connect_with_mnemonic(mnemonic, state).await
}
@@ -48,13 +46,13 @@ pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result<Balance
}
#[tauri::command]
pub fn create_new_mnemonic() -> SensitiveStringWrapper {
random_mnemonic().into_string()
pub fn create_new_mnemonic() -> Mnemonic {
random_mnemonic()
}
#[tauri::command]
pub fn validate_mnemonic(mnemonic: SensitiveStringWrapper) -> bool {
ZeroizeMnemonicWrapper::try_from_string(mnemonic).is_ok()
pub fn validate_mnemonic(_mnemonic: Mnemonic) -> bool {
true
}
#[tauri::command]
@@ -82,15 +80,13 @@ pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendE
Ok(())
}
fn random_mnemonic() -> ZeroizeMnemonicWrapper {
fn random_mnemonic() -> Mnemonic {
let mut rng = rand::thread_rng();
Mnemonic::generate_in_with(&mut rng, Language::English, 24)
.unwrap()
.into()
Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap()
}
async fn _connect_with_mnemonic(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
state: tauri::State<'_, WalletState>,
) -> Result<Account, BackendError> {
{
@@ -141,7 +137,7 @@ async fn _connect_with_mnemonic(
&default_nyxd_urls,
&default_api_urls,
&config,
mnemonic.as_ref(),
&mnemonic,
)?;
// Set the default account
@@ -298,16 +294,12 @@ pub fn does_password_file_exist() -> Result<bool, BackendError> {
}
#[tauri::command]
pub fn create_password(
mnemonic: SensitiveStringWrapper,
password: UserPassword,
) -> Result<(), BackendError> {
pub fn create_password(mnemonic: Mnemonic, password: UserPassword) -> Result<(), BackendError> {
if does_password_file_exist()? {
return Err(BackendError::WalletFileAlreadyExists);
}
log::info!("Creating password");
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
// Currently we only support a single, default, login id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
@@ -330,7 +322,7 @@ pub async fn sign_in_with_password(
set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone())
.await?;
_connect_with_mnemonic(mnemonic.into(), state).await
_connect_with_mnemonic(mnemonic, state).await
}
fn extract_first_mnemonic(
@@ -370,7 +362,7 @@ pub async fn sign_in_with_password_and_account_id(
set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone())
.await?;
_connect_with_mnemonic(mnemonic.into(), state).await
_connect_with_mnemonic(mnemonic, state).await
}
fn extract_mnemonic(
@@ -404,13 +396,12 @@ pub fn archive_wallet_file() -> Result<(), BackendError> {
#[tauri::command]
pub async fn add_account_for_password(
mnemonic: SensitiveStringWrapper,
mnemonic: Mnemonic,
password: UserPassword,
account_id: &str,
state: tauri::State<'_, WalletState>,
) -> Result<AccountEntry, BackendError> {
log::info!("Adding account for the current password: {account_id}");
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
// Currently we only support a single, default, login id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
@@ -427,13 +418,7 @@ pub async fn add_account_for_password(
let address = {
let state = state.read().await;
let network: NymNetworkDetails = state.current_network().into();
// safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately
// passed to `DirectSecp256k1HdWallet` which will zeroize it on drop
derive_address(
mnemonic.into_cloned_inner(),
&network.chain_details.bech32_account_prefix,
)?
.to_string()
derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string()
};
// Re-read all the acccounts from the wallet to reset the state, rather than updating it
@@ -553,19 +538,19 @@ pub async fn list_accounts(
pub fn show_mnemonic_for_account_in_password(
account_id: String,
password: UserPassword,
) -> Result<SensitiveStringWrapper, BackendError> {
) -> Result<Mnemonic, BackendError> {
log::info!("Getting mnemonic for: {account_id}");
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
let account_id = wallet_storage::AccountId::new(account_id);
let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?;
Ok(mnemonic.into_string())
Ok(mnemonic)
}
fn _show_mnemonic_for_account_in_password(
login_id: &wallet_storage::LoginId,
account_id: &wallet_storage::AccountId,
password: &wallet_storage::UserPassword,
) -> Result<ZeroizeMnemonicWrapper, BackendError> {
) -> Result<Mnemonic, BackendError> {
let stored_account = wallet_storage::load_existing_login(login_id, password)?;
let mnemonic = match stored_account {
wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(),
@@ -575,7 +560,7 @@ fn _show_mnemonic_for_account_in_password(
.mnemonic()
.clone(),
};
Ok(mnemonic.into())
Ok(mnemonic)
}
#[cfg(test)]
@@ -590,8 +575,8 @@ mod tests {
use super::*;
// This decryptes a stored wallet file using the same procedure as when signing in. Most tests
// related to the encryped wallet storage is in `wallet_storage`.
// This decrypts a stored wallet file using the same procedure as when signing in. Most tests
// related to the encrypted wallet storage is in `wallet_storage`.
#[test]
fn decrypt_stored_wallet_for_sign_in() {
const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json";
@@ -8,6 +8,7 @@ use crate::operations::helpers::{
use crate::state::WalletState;
use crate::{nyxd_client, Gateway, MixNode};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::{MixId, MixNodeConfigUpdate};
use nym_types::currency::DecCoin;
use nym_types::gateway::GatewayBond;
@@ -227,6 +228,31 @@ pub async fn update_mixnode_config(
)?)
}
#[tauri::command]
pub async fn update_gateway_config(
update: GatewayConfigUpdate,
fee: Option<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
log::info!(
">>> Update gateway config: update = {}, fee {:?}",
update.to_inline_json(),
fee,
);
let res = guard
.current_client()?
.nyxd
.update_gateway_config(update, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res, fee_amount,
)?)
}
#[tauri::command]
pub async fn get_mixnode_avg_uptime(
state: tauri::State<'_, WalletState>,
+1 -94
View File
@@ -4,16 +4,14 @@
use crate::error::BackendError;
use crate::nyxd_client;
use crate::state::WalletState;
use bip39::Mnemonic;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{IdentityKey, MixId, Percent};
use nym_types::currency::DecCoin;
use nym_types::mixnode::MixNodeCostParams;
use nym_wallet_types::app::AppEnv;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use validator_client::nyxd::traits::MixnetQueryClient;
use validator_client::nyxd::{tx, Coin, CosmosCoin, Gas, GasPrice};
use zeroize::Zeroize;
fn get_env_as_option(key: &str) -> Option<String> {
match ::std::env::var(key) {
@@ -194,94 +192,3 @@ pub async fn get_old_and_incorrect_hardcoded_fee(
log::info!("hardcoded fee for {:?} is {:?}", operation, coin);
guard.attempt_convert_to_display_dec_coin(coin)
}
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct SensitiveStringWrapper(String);
impl<'de> Deserialize<'de> for SensitiveStringWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(SensitiveStringWrapper(String::deserialize(deserializer)?))
}
}
impl Serialize for SensitiveStringWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// unfortunately this serialized value will live on...
self.0.serialize(serializer)
}
}
impl From<String> for SensitiveStringWrapper {
fn from(value: String) -> Self {
SensitiveStringWrapper(value)
}
}
impl AsRef<str> for SensitiveStringWrapper {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
#[derive(Clone)]
// can't do it natively until https://github.com/rust-bitcoin/rust-bip39/pull/32 gets merged and released...
pub(crate) struct ZeroizeMnemonicWrapper(bip39::Mnemonic);
impl From<bip39::Mnemonic> for ZeroizeMnemonicWrapper {
fn from(value: Mnemonic) -> Self {
ZeroizeMnemonicWrapper(value)
}
}
impl Zeroize for ZeroizeMnemonicWrapper {
fn zeroize(&mut self) {
// overwrite the mnemonic value with a completely random one
// (a poor man's zeroize until bip39 crate does it properly...)
self.0 = Mnemonic::generate(self.0.word_count()).unwrap();
}
}
impl Drop for ZeroizeMnemonicWrapper {
fn drop(&mut self) {
self.zeroize()
}
}
impl AsRef<bip39::Mnemonic> for ZeroizeMnemonicWrapper {
fn as_ref(&self) -> &Mnemonic {
&self.0
}
}
impl ZeroizeMnemonicWrapper {
pub(crate) fn into_string(self) -> SensitiveStringWrapper {
SensitiveStringWrapper(self.0.to_string())
}
pub(crate) fn try_from_string(string: SensitiveStringWrapper) -> Result<Self, bip39::Error> {
let res = string.as_ref().parse()?;
Ok(ZeroizeMnemonicWrapper(res))
}
// special care must be taken when calling this method as the mnemonic will no longer get zeroized!
pub(crate) fn into_cloned_inner(self) -> bip39::Mnemonic {
self.0.clone()
}
#[cfg(test)]
pub(crate) fn unchecked_clone_inner(&self) -> bip39::Mnemonic {
self.0.clone()
}
#[cfg(test)]
pub(crate) fn generate_random() -> Self {
ZeroizeMnemonicWrapper(Mnemonic::generate(24).unwrap())
}
}
@@ -14,16 +14,14 @@
// In the future we might want to simplify by dropping the support for a single account entry,
// instead treating as muliple accounts with one entry.
use serde::{Deserialize, Serialize};
use validator_client::nyxd::bip32::DerivationPath;
use zeroize::Zeroize;
use crate::error::BackendError;
use crate::utils::ZeroizeMnemonicWrapper;
use super::encryption::EncryptedData;
use super::password::{AccountId, LoginId};
use super::UserPassword;
use crate::error::BackendError;
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use validator_client::nyxd::bip32::DerivationPath;
use zeroize::{Zeroize, ZeroizeOnDrop};
const CURRENT_WALLET_FILE_VERSION: u32 = 1;
@@ -153,9 +151,8 @@ impl EncryptedLogin {
/// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where
/// each has an inner id. Future proofed for having private key backed accounts.
#[derive(Serialize, Deserialize, Debug, Zeroize)]
#[derive(Serialize, Deserialize, Debug, Zeroize, ZeroizeOnDrop)]
#[serde(untagged)]
#[zeroize(drop)]
pub(crate) enum StoredLogin {
Mnemonic(MnemonicAccount),
// PrivateKey(PrivateKeyAccount)
@@ -193,8 +190,7 @@ impl StoredLogin {
}
/// Multiple stored accounts, each entry having an id and a data field.
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[zeroize(drop)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct MultipleAccounts {
accounts: Vec<WalletAccount>,
}
@@ -238,19 +234,17 @@ impl MultipleAccounts {
pub(crate) fn add(
&mut self,
id: AccountId,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
) -> Result<(), BackendError> {
if self.get_account(&id).is_some() {
Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin)
} else if self.get_account_with_mnemonic(mnemonic.as_ref()).is_some() {
} else if self.get_account_with_mnemonic(&mnemonic).is_some() {
Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin)
} else {
self.accounts.push(WalletAccount::new(
id,
// safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately
// passed to `MnemonicAccount` which will zeroize it on drop
MnemonicAccount::new(mnemonic.into_cloned_inner(), hd_path),
MnemonicAccount::new(mnemonic, hd_path),
));
Ok(())
}
@@ -272,8 +266,7 @@ impl From<Vec<WalletAccount>> for MultipleAccounts {
}
/// An entry in the list of stored accounts
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[zeroize(drop)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct WalletAccount {
id: AccountId,
account: AccountData,
@@ -307,19 +300,20 @@ impl WalletAccount {
/// An account usually is a mnemonic account, but in the future it might be backed by a private
/// key.
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
#[serde(untagged)]
#[zeroize(drop)]
enum AccountData {
Mnemonic(MnemonicAccount),
// PrivateKey(PrivateKeyAccount)
}
/// An account backed by a unique mnemonic.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct MnemonicAccount {
mnemonic: bip39::Mnemonic,
#[serde(with = "display_hd_path")]
// there's nothing secret about our derivation path
#[zeroize(skip)]
hd_path: DerivationPath,
}
@@ -338,27 +332,6 @@ impl MnemonicAccount {
}
}
impl Zeroize for MnemonicAccount {
fn zeroize(&mut self) {
// in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it)
// and the memory would have been filled with zeroes.
//
// we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing
// of overwriting it with a fresh mnemonic that was never used before
//
// note: this function can only fail on an invalid word count, which clearly is not the case here
self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap();
// further note: we don't really care about the hd_path, there's nothing secret about it.
}
}
impl Drop for MnemonicAccount {
fn drop(&mut self) {
self.zeroize()
}
}
mod display_hd_path {
use serde::{Deserialize, Deserializer, Serializer};
use validator_client::nyxd::bip32::DerivationPath;
+107 -191
View File
@@ -14,7 +14,7 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor
use crate::error::BackendError;
use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME};
use crate::utils::ZeroizeMnemonicWrapper;
use bip39::Mnemonic;
use std::ffi::OsString;
use std::fs::{self, create_dir_all, OpenOptions};
use std::path::{Path, PathBuf};
@@ -139,7 +139,7 @@ fn store_login_at_file(
}
pub(crate) fn store_login_with_multiple_accounts(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
password: &UserPassword,
@@ -154,7 +154,7 @@ pub(crate) fn store_login_with_multiple_accounts(
fn store_login_with_multiple_accounts_at_file(
filepath: &Path,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
password: &UserPassword,
@@ -185,7 +185,7 @@ fn store_login_with_multiple_accounts_at_file(
/// account in the list of accounts associated with the encrypted entry. The inner id for this
/// entry will be set to the same as the outer, unencrypted, id.
pub(crate) fn append_account_to_login(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
inner_id: AccountId,
@@ -201,7 +201,7 @@ pub(crate) fn append_account_to_login(
fn append_account_to_login_at_file(
filepath: &Path,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
inner_id: AccountId,
@@ -422,19 +422,12 @@ mod tests {
fn store_single_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap();
let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap();
assert_eq!(stored_wallet.len(), 1);
@@ -450,7 +443,7 @@ mod tests {
fn store_single_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -478,7 +471,7 @@ mod tests {
fn store_twice_for_the_same_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -486,7 +479,7 @@ mod tests {
// Store the first login
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -495,13 +488,7 @@ mod tests {
// and storing the same id again fails
assert!(matches!(
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
),
store_login_at_file(&wallet_file, account1, hd_path, id1, &password,),
Err(BackendError::WalletLoginIdAlreadyExists),
));
}
@@ -510,7 +497,7 @@ mod tests {
fn store_twice_for_the_same_id_fails_with_multiple() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -542,20 +529,13 @@ mod tests {
fn load_with_wrong_password_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap();
// Trying to load it with wrong password now fails
assert!(matches!(
@@ -568,7 +548,7 @@ mod tests {
fn load_with_wrong_password_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -594,20 +574,13 @@ mod tests {
fn load_with_wrong_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
let id2 = LoginId::new("second".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap();
// Trying to load with the wrong id
assert!(matches!(
@@ -620,7 +593,7 @@ mod tests {
fn load_with_wrong_id_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -640,14 +613,14 @@ mod tests {
fn store_and_load_a_single_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -656,7 +629,7 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&hd_path, acc.hd_path());
}
@@ -664,7 +637,7 @@ mod tests {
fn store_and_load_a_single_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let acc1 = ZeroizeMnemonicWrapper::generate_random();
let acc1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -685,7 +658,7 @@ mod tests {
.get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into())
.unwrap();
assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME);
assert_eq!(account.mnemonic(), acc1.as_ref());
assert_eq!(account.mnemonic(), &acc1);
assert_eq!(account.hd_path(), &hd_path);
}
@@ -693,8 +666,8 @@ mod tests {
fn store_a_second_login_with_a_different_password_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -703,7 +676,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
account1,
cosmos_hd_path.clone(),
id1,
&password,
@@ -712,13 +685,7 @@ mod tests {
// Can't store a second login if you use different password
assert!(matches!(
store_login_at_file(
&wallet_file,
account2.into_cloned_inner(),
cosmos_hd_path,
id2,
&bad_password
),
store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password),
Err(BackendError::WalletDifferentPasswordDetected),
));
}
@@ -727,8 +694,8 @@ mod tests {
fn store_a_second_login_with_a_different_password_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -761,8 +728,8 @@ mod tests {
fn store_two_mnemonic_accounts_gives_different_salts_and_iv() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -770,14 +737,7 @@ mod tests {
let id2 = LoginId::new("second".to_string());
// Store the first account
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap();
let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap();
let encrypted_blob = &stored_wallet
@@ -790,14 +750,7 @@ mod tests {
let original_salt = encrypted_blob.salt().to_vec();
// Add an extra account
store_login_at_file(
&wallet_file,
account2.into_cloned_inner(),
different_hd_path,
id2,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap();
let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap();
assert_eq!(loaded_accounts.len(), 2);
@@ -815,8 +768,8 @@ mod tests {
fn store_two_mnemonic_accounts_using_two_logins() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -826,7 +779,7 @@ mod tests {
// Store the first account
store_login_at_file(
&wallet,
account1.unchecked_clone_inner(),
account1.clone(),
cosmos_hd_path.clone(),
id1.clone(),
&password,
@@ -835,13 +788,13 @@ mod tests {
let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let acc = login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&cosmos_hd_path, acc.hd_path());
// Add an extra account
store_login_at_file(
&wallet,
account2.unchecked_clone_inner(),
account2.clone(),
different_hd_path.clone(),
id2.clone(),
&password,
@@ -851,12 +804,12 @@ mod tests {
// first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account2.as_ref(), acc2.mnemonic());
assert_eq!(&account2, acc2.mnemonic());
assert_eq!(&different_hd_path, acc2.hd_path());
}
@@ -864,8 +817,8 @@ mod tests {
fn store_one_mnemonic_account_and_one_multi_account() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -875,7 +828,7 @@ mod tests {
// Store the first account
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -884,7 +837,7 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&hd_path, acc.hd_path());
// Add an extra account
@@ -900,7 +853,7 @@ mod tests {
// first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
@@ -910,7 +863,7 @@ mod tests {
.get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into())
.unwrap();
assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME);
assert_eq!(account.mnemonic(), account2.as_ref());
assert_eq!(account.mnemonic(), &account2);
assert_eq!(account.hd_path(), &different_hd_path);
}
@@ -918,7 +871,7 @@ mod tests {
fn remove_non_existent_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -938,8 +891,8 @@ mod tests {
fn store_and_remove_wallet_login_information() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -949,7 +902,7 @@ mod tests {
// Store two accounts with two different passwords
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
cosmos_hd_path.clone(),
id1.clone(),
&password,
@@ -957,7 +910,7 @@ mod tests {
.unwrap();
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
different_hd_path.clone(),
id2.clone(),
&password,
@@ -967,12 +920,12 @@ mod tests {
// Load and compare
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account2.as_ref(), acc2.mnemonic());
assert_eq!(&account2, acc2.mnemonic());
assert_eq!(&different_hd_path, acc2.hd_path());
// Delete the second account
@@ -981,7 +934,7 @@ mod tests {
// The first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
// And we can't load the second one anymore
@@ -1008,8 +961,8 @@ mod tests {
fn append_account_converts_the_type() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1017,7 +970,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1027,7 +980,7 @@ mod tests {
// Check that it's there as the correct non-multiple type
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc.mnemonic(), account1.as_ref());
assert_eq!(acc.mnemonic(), &account1);
assert_eq!(acc.hd_path(), &hd_path);
append_account_to_login_at_file(
@@ -1044,14 +997,8 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id1.into(),
MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id2,
MnemonicAccount::new(account2.into_cloned_inner(), hd_path),
),
WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())),
WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1061,10 +1008,10 @@ mod tests {
fn append_accounts_to_existing_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account4 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let account4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1074,7 +1021,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1083,7 +1030,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1093,7 +1040,7 @@ mod tests {
// Check that it's there as the correct non-multiple type
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc2.mnemonic(), account2.as_ref());
assert_eq!(acc2.mnemonic(), &account2);
assert_eq!(acc2.hd_path(), &hd_path);
// Add a third and fourth mnenonic grouped together with the second one
@@ -1119,24 +1066,15 @@ mod tests {
// Check that we can load all four
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc1.mnemonic(), account1.as_ref());
assert_eq!(acc1.mnemonic(), &account1);
assert_eq!(acc1.hd_path(), &hd_path);
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id2.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4,
MnemonicAccount::new(account4.into_cloned_inner(), hd_path),
),
WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())),
WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1146,10 +1084,10 @@ mod tests {
fn append_accounts_to_existing_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account4 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let account4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1200,7 +1138,7 @@ mod tests {
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()),
MnemonicAccount::new(account1, hd_path.clone()),
)]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1210,16 +1148,10 @@ mod tests {
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4,
MnemonicAccount::new(account4.into_cloned_inner(), hd_path),
MnemonicAccount::new(account2, hd_path.clone()),
),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())),
WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1229,7 +1161,7 @@ mod tests {
fn append_the_same_mnemonic_twice_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1254,21 +1186,14 @@ mod tests {
fn delete_the_same_account_twice_for_a_login_fails() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
let id2 = AccountId::new("second".to_string());
store_login_at_file(
&wallet,
account1.into_cloned_inner(),
hd_path.clone(),
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap();
append_account_to_login_at_file(
&wallet,
@@ -1292,8 +1217,8 @@ mod tests {
fn delete_the_same_account_twice_for_a_login_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1330,9 +1255,9 @@ mod tests {
fn delete_appended_account_doesnt_affect_others() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1341,7 +1266,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
account1,
hd_path.clone(),
id1.clone(),
&password,
@@ -1350,7 +1275,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1373,14 +1298,8 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id2.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path),
),
WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1390,8 +1309,8 @@ mod tests {
fn remove_all_accounts_for_a_login_removes_the_file_when_empty() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1439,9 +1358,9 @@ mod tests {
fn remove_all_accounts_for_a_login_removes_that_login() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1496,7 +1415,7 @@ mod tests {
let acc3 = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account3.into_cloned_inner(), hd_path),
MnemonicAccount::new(account3, hd_path),
)]
.into();
assert_eq!(acc3, &expected);
@@ -1506,10 +1425,10 @@ mod tests {
fn append_accounts_and_remove_appended_accounts() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let acc1 = ZeroizeMnemonicWrapper::generate_random();
let acc2 = ZeroizeMnemonicWrapper::generate_random();
let acc3 = ZeroizeMnemonicWrapper::generate_random();
let acc4 = ZeroizeMnemonicWrapper::generate_random();
let acc1 = Mnemonic::generate(24).unwrap();
let acc2 = Mnemonic::generate(24).unwrap();
let acc3 = Mnemonic::generate(24).unwrap();
let acc4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1519,7 +1438,7 @@ mod tests {
store_login_at_file(
&wallet,
acc1.unchecked_clone_inner(),
acc1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1528,7 +1447,7 @@ mod tests {
store_login_at_file(
&wallet,
acc2.unchecked_clone_inner(),
acc2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1564,12 +1483,9 @@ mod tests {
let expected = vec![
WalletAccount::new(
id2.clone().into(),
MnemonicAccount::new(acc2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4.clone(),
MnemonicAccount::new(acc4.into_cloned_inner(), hd_path.clone()),
MnemonicAccount::new(acc2, hd_path.clone()),
),
WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1585,7 +1501,7 @@ mod tests {
// The first login is still available
let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let account = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account.mnemonic(), acc1.as_ref());
assert_eq!(account.mnemonic(), &acc1);
assert_eq!(account.hd_path(), &hd_path);
}
@@ -1,13 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt;
use serde::{Deserialize, Deserializer, Serialize};
use zeroize::Zeroize;
use zeroize::{Zeroize, Zeroizing};
// The `LoginId` is the top level id in the wallet file, and is not stored encrypted
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct LoginId(String);
impl LoginId {
@@ -81,26 +80,4 @@ impl fmt::Display for AccountId {
}
// simple wrapper for String that will get zeroized on drop
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct UserPassword(String);
impl UserPassword {
#[cfg(test)]
pub(crate) fn new(inner: String) -> Self {
UserPassword(inner)
}
pub(crate) fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl<'de> Deserialize<'de> for UserPassword {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(UserPassword(String::deserialize(deserializer)?))
}
}
pub type UserPassword = Zeroizing<String>;
@@ -22,12 +22,12 @@ export const MultiAccountWithPwdHowTo = ({ show, handleClose }: { show: boolean;
>
<Stack spacing={2}>
<Warning sx={{ textAlign: 'center' }}>
<Typography fontWeight={600} sx={{ mb: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ mb: 1 }}>
This machine already has a password set on it
</Typography>
<Typography>
<Typography variant="caption">
In order to import or create account(s) you need to log in with your password or create a new one. Creating a
new password will overwrite any old one. Make sure your menonics are all wirtten down before creating a new
new password will overwrite any old one. Make sure your mnemonics are all written down before creating a new
password.
</Typography>
</Warning>
@@ -35,13 +35,14 @@ export const MultiAccountWithPwdHowTo = ({ show, handleClose }: { show: boolean;
{passwordCreationSteps.map((step, index) => (
<Stack key={step} direction="row" spacing={1}>
<Typography fontWeight={600}>{`${index + 1}.`}</Typography>
<Typography>{`${step}`}</Typography>
<Typography variant="body2">{`${step}`}</Typography>
</Stack>
))}
<Link
href="https://nymtech.net/docs/stable/wallet#importing-or-creating-accounts-when-you-have-signed-in-with-mnemonic-but-a-password-already-exists-on-your-machine"
target="_blank"
text="Open Nym docs for this guide in a browser window"
variant="body2"
fontWeight={600}
/>
</Stack>
+14 -3
View File
@@ -1,8 +1,18 @@
import React, { useState } from 'react';
import { Alert as MuiAlert, IconButton } from '@mui/material';
import { Alert as MuiAlert, IconButton, SxProps } from '@mui/material';
import { Close } from '@mui/icons-material';
export const Alert = ({ title, dismissable }: { title: string | React.ReactNode; dismissable?: boolean }) => {
export const Alert = ({
title,
dismissable,
sxAlert,
bgColor,
}: {
title: string | React.ReactNode;
dismissable?: boolean;
sxAlert?: SxProps;
bgColor?: string;
}) => {
const [displayAlert, setDisplayAlert] = useState(true);
const handleDismiss = () => setDisplayAlert(false);
@@ -14,9 +24,10 @@ export const Alert = ({ title, dismissable }: { title: string | React.ReactNode;
sx={{
width: '100%',
borderRadius: 0,
bgcolor: 'background.default',
bgcolor: bgColor || 'background.default',
color: (theme) => theme.palette.nym.nymWallet.text.blue,
'& .MuiAlert-icon': { color: 'nym.nymWallet.text.blue', mr: 1 },
...sxAlert,
}}
action={
dismissable && (
+7 -1
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { Link } from '@nymproject/react/link/Link';
import { Box, Button, Typography } from '@mui/material';
import { NymCard } from '../NymCard';
@@ -18,7 +19,12 @@ export const Bond = ({
justifyContent: 'space-between',
}}
>
<Typography variant="body2">Bond a mixnode or a gateway</Typography>
<Typography variant="body2">
Bond a mix node or a gateway. Learn how to set up and run a node{' '}
<Link href="https://nymtech.net/docs/nodes/setup-guides.html" target="_blank">
here
</Link>
</Typography>
<Box
sx={{
display: 'flex',
@@ -42,6 +42,7 @@ const GatewayInitForm = ({
initialValue={gatewayData?.identityKey}
errorText={errors.identityKey?.message}
onChanged={(value) => setValue('identityKey', value)}
showTickOnValid={false}
/>
<TextField
{...register('sphinxKey')}
@@ -36,6 +36,7 @@ const MixnodeInitForm = ({ mixnodeData, onNext }: { mixnodeData: MixnodeData; on
initialValue={mixnodeData?.identityKey}
errorText={errors.identityKey?.message}
onChanged={(value) => setValue('identityKey', value)}
showTickOnValid={false}
/>
<TextField
{...register('sphinxKey')}
+1 -1
View File
@@ -40,7 +40,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
)}
<AddressTooltip address={address} visible={!showEntireAddress}>
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
<Typography variant="body2" component="span" sx={{ color: 'text.primary', fontWeight: 400 }}>
{showEntireAddress ? address || '' : splice(6, address)}
</Typography>
</AddressTooltip>
@@ -257,6 +257,7 @@ export const DelegateModal: FCWithChildren<{
textFieldProps={{
autoFocus: !initialIdentityKey,
}}
showTickOnValid={false}
/>
</Box>
<Typography
@@ -277,16 +278,9 @@ export const DelegateModal: FCWithChildren<{
autoFocus={Boolean(initialIdentityKey)}
onChanged={handleAmountChanged}
denom={denom}
validationError={errorAmount}
/>
</Box>
<Typography
component="div"
textAlign="left"
variant="caption"
sx={{ color: 'error.main', mx: 2, mt: errorAmount && 1 }}
>
{errorAmount}
</Typography>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Account balance" value={accountBalance?.toUpperCase()} divider fontWeight={600} />
</Box>
@@ -0,0 +1,20 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { MockMainContextProvider } from '../context/mocks/main';
import { NetworkSelector } from './NetworkSelector';
export default {
title: 'Wallet / Network Selector',
component: NetworkSelector,
} as ComponentMeta<typeof NetworkSelector>;
const Template: ComponentStory<typeof NetworkSelector> = () => (
<Box mt={2} height={800}>
<MockMainContextProvider>
<NetworkSelector />
</MockMainContextProvider>
</Box>
);
export const Default = Template.bind({});
+25 -6
View File
@@ -1,6 +1,6 @@
import React, { useState, useContext } from 'react';
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material';
import { ArrowDropDown, CheckSharp } from '@mui/icons-material';
import { Button, List, ListItemButton, ListItemIcon, ListItemText, ListSubheader, Popover, Stack } from '@mui/material';
import { ArrowDropDown, Check } from '@mui/icons-material';
import { Network } from 'src/types';
import { AppContext } from '../context/main';
import { config } from '../config';
@@ -16,10 +16,29 @@ const NetworkItem: FCWithChildren<{ title: string; isSelected: boolean; onSelect
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
<ListItemButton
onClick={onSelect}
sx={{
minWidth: '180px',
'&:hover': {
backgroundColor: isSelected ? 'rgba(251, 110, 78, 0.08) !important' : undefined,
},
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" gap={2} width="100%">
<ListItemText
primaryTypographyProps={{
color: isSelected ? 'primary' : undefined,
}}
primary={title}
/>
{isSelected && (
<ListItemIcon sx={{ justifyContent: 'flex-end' }}>
<Check color="primary" fontSize="small" />
</ListItemIcon>
)}
</Stack>
</ListItemButton>
);
export const NetworkSelector = () => {
@@ -19,7 +19,6 @@ export const ReceiveModal = ({
return (
<SimpleModal
header="Receive"
subHeader="Provide your address to receive tokens"
open
onClose={onClose}
okLabel=""
@@ -128,7 +128,7 @@ export const SendInputModal = ({
<Stack gap={0.5} sx={{ mt: 1 }}>
<ModalListItem label="Account balance" value={balance?.toUpperCase()} divider fontWeight={600} />
<Typography fontSize="smaller" sx={{ color: 'text.primary' }}>
Est. fee for this transaction will be show on the next page
Est. fee for this transaction will be shown on the next page
</Typography>
</Stack>
<FormControlLabel
@@ -139,7 +139,7 @@ export const SendInputModal = ({
{showMore && (
<Stack direction="column" gap={3} mt={2} mb={3}>
<CurrencyFormField
label="Fees"
label="Fee"
onChanged={(v) => onUserFeesChange(v)}
initialValue={userFees?.amount}
fullWidth
+1 -1
View File
@@ -77,7 +77,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
} catch (e) {
Console.error(e as string);
if (/Raw log: out of gas/.test(e as string)) {
setGasError('Out of gas, please increase the amount of fees');
setGasError('Specified fee was too small. Please increase the amount and try again');
} else {
setSendError(true);
}

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