Compare commits

...

8 Commits

Author SHA1 Message Date
Gala 9b1f4560ab Merge branch 'fix/selection-chance-buckets' of github.com:nymtech/nym into fix/selection-chance-buckets 2022-10-26 16:37:22 +02:00
Gala 84d68efdc2 Merge branch 'release/v1.1.0' into fix/selection-chance-buckets 2022-10-26 16:36:59 +02:00
Drazen Urch f4f98027a0 Add per account pledge caps (#1687)
* Add per account pledge caps

* Address PR comments

* Update CHANGELOG

* No cap if no locked

* Fail account creation if taking account already exists

* Delegated free should be counted from vesting period start
2022-10-26 10:51:40 +02:00
Gala 4016d6b5a6 update storybook 2022-09-21 11:10:16 +02:00
Gala 8cb8297681 storybook update 2022-09-21 10:47:15 +02:00
Jon Häggblad 7998bd20a7 Lock file update 2022-09-21 08:49:25 +02:00
Jon Häggblad e9e53c8dff Fixup after rebase 2022-09-21 08:48:00 +02:00
Jon Häggblad a048cb9867 Update to latest set of selection chance buckets 2022-09-21 08:40:06 +02:00
35 changed files with 371 additions and 185 deletions
+3
View File
@@ -15,6 +15,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671]) - native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673]) - wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint - validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 100_000 NYM
### Fixed ### Fixed
@@ -26,6 +27,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591]) - socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585]) - wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669]) - gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- moved `Percent` struct to to `contracts-common`, change affects explorer-api
[#1541]: https://github.com/nymtech/nym/pull/1541 [#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558 [#1558]: https://github.com/nymtech/nym/pull/1558
@@ -39,6 +41,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1669]: https://github.com/nymtech/nym/pull/1669 [#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671 [#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673 [#1673]: https://github.com/nymtech/nym/pull/1673
[#1687]: https://github.com/nymtech/nym/pull/1687
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2) ## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+7
View File
@@ -737,7 +737,9 @@ name = "contracts-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]
@@ -1584,6 +1586,7 @@ dependencies = [
"chrono", "chrono",
"clap 3.2.8", "clap 3.2.8",
"dotenv", "dotenv",
"contracts-common",
"humantime-serde", "humantime-serde",
"isocountry", "isocountry",
"itertools", "itertools",
@@ -3349,6 +3352,7 @@ dependencies = [
"coconut-interface", "coconut-interface",
"config", "config",
"console-subscriber", "console-subscriber",
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"credential-storage", "credential-storage",
"credentials", "credentials",
@@ -6474,6 +6478,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract" name = "vesting-contract"
version = "1.1.0" version = "1.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"mixnet-contract-common", "mixnet-contract-common",
@@ -6487,7 +6492,9 @@ dependencies = [
name = "vesting-contract-common" name = "vesting-contract-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
@@ -15,14 +15,12 @@ use cosmrs::rpc::query::Query;
use cosmrs::rpc::Error as TendermintRpcError; use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl; use cosmrs::rpc::HttpClientUrl;
use cosmrs::tx::Msg; use cosmrs::tx::Msg;
use cosmwasm_std::Uint128;
use execute::execute; use execute::execute;
use network_defaults::{ChainDetails, NymNetworkDetails}; use network_defaults::{ChainDetails, NymNetworkDetails};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryInto; use std::convert::TryInto;
use std::time::SystemTime; use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg; use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
use vesting_contract_common::QueryMsg as VestingQueryMsg;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
@@ -47,6 +45,7 @@ pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
pub use signing_client::Client as SigningNymdClient; pub use signing_client::Client as SigningNymdClient;
pub use traits::{VestingQueryClient, VestingSigningClient}; pub use traits::{VestingQueryClient, VestingSigningClient};
use vesting_contract_common::PledgeCap;
pub mod coin; pub mod coin;
pub mod cosmwasm_client; pub mod cosmwasm_client;
@@ -482,16 +481,6 @@ impl<C> NymdClient<C> {
self.client.get_total_supply().await self.client.get_total_supply().await
} }
pub async fn vesting_get_locked_pledge_cap(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = VestingQueryMsg::GetLockedPledgeCap {};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NymdError> pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NymdError>
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
@@ -737,12 +726,16 @@ impl<C> NymdClient<C> {
#[execute("vesting")] #[execute("vesting")]
fn _vesting_update_locked_pledge_cap( fn _vesting_update_locked_pledge_cap(
&self, &self,
amount: Uint128, address: String,
cap: PledgeCap,
fee: Option<Fee>, fee: Option<Fee>,
) -> (VestingExecuteMsg, Option<Fee>) ) -> (VestingExecuteMsg, Option<Fee>)
where where
C: SigningCosmWasmClient + Sync, C: SigningCosmWasmClient + Sync,
{ {
(VestingExecuteMsg::UpdateLockedPledgeCap { amount }, fee) (
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap },
fee,
)
} }
} }
@@ -9,6 +9,7 @@ use async_trait::async_trait;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixId, MixNode}; use mixnet_contract_common::{Gateway, MixId, MixNode};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification}; use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
use vesting_contract_common::PledgeCap;
#[async_trait] #[async_trait]
pub trait VestingSigningClient { pub trait VestingSigningClient {
@@ -105,6 +106,7 @@ pub trait VestingSigningClient {
staking_address: Option<String>, staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>, vesting_spec: Option<VestingSpecification>,
amount: Coin, amount: Coin,
cap: Option<PledgeCap>,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>; ) -> Result<ExecuteResult, NymdError>;
} }
@@ -382,6 +384,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
staking_address: Option<String>, staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>, vesting_spec: Option<VestingSpecification>,
amount: Coin, amount: Coin,
cap: Option<PledgeCap>,
fee: Option<Fee>, fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> { ) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
@@ -389,6 +392,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
owner_address: owner_address.to_string(), owner_address: owner_address.to_string(),
staking_address, staking_address,
vesting_spec, vesting_spec,
cap,
}; };
self.client self.client
.execute( .execute(
@@ -12,6 +12,7 @@ use validator_client::nymd::AccountId;
use validator_client::nymd::VestingSigningClient; use validator_client::nymd::VestingSigningClient;
use validator_client::nymd::{CosmosCoin, Denom}; use validator_client::nymd::{CosmosCoin, Denom};
use vesting_contract_common::messages::VestingSpecification; use vesting_contract_common::messages::VestingSpecification;
use vesting_contract_common::PledgeCap;
use crate::context::SigningClient; use crate::context::SigningClient;
@@ -34,6 +35,12 @@ pub struct Args {
#[clap(long)] #[clap(long)]
pub staking_address: Option<String>, pub staking_address: Option<String>,
#[clap(
long,
help = "Pledge cap as either absolute uNYM value or percentage, floats need to be in the 0.0 to 1.0 range and will be parsed as percentages, integers will be parsed as uNYM"
)]
pub pledge_cap: Option<PledgeCap>,
} }
pub async fn create(args: Args, client: SigningClient, network_details: &NymNetworkDetails) { pub async fn create(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
@@ -55,6 +62,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
args.staking_address, args.staking_address,
Some(vesting), Some(vesting),
coin.into(), coin.into(),
args.pledge_cap,
None, None,
) )
.await .await
@@ -9,3 +9,5 @@ edition = "2021"
[dependencies] [dependencies]
cosmwasm-std = "1.0.0" cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
thiserror = "1"
@@ -1,7 +1,120 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize}; use cosmwasm_std::Decimal;
use cosmwasm_std::Uint128;
use schemars::JsonSchema;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::Mul;
use std::str::FromStr;
use thiserror::Error;
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
#[derive(Error, Debug)]
pub enum ContractsCommonError {
#[error("Provided percent value ({0}) is greater than 100%")]
InvalidPercent(Decimal),
#[error("{source}")]
StdErr {
#[from]
source: cosmwasm_std::StdError,
},
}
/// Percent represents a value between 0 and 100%
/// (i.e. between 0.0 and 1.0)
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
)]
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
impl Percent {
pub fn new(value: Decimal) -> Result<Self, ContractsCommonError> {
if value > Decimal::one() {
Err(ContractsCommonError::InvalidPercent(value))
} else {
Ok(Percent(value))
}
}
pub fn is_zero(&self) -> bool {
self.0 == Decimal::zero()
}
pub fn from_percentage_value(value: u64) -> Result<Self, ContractsCommonError> {
Percent::new(Decimal::percent(value))
}
pub fn value(&self) -> Decimal {
self.0
}
pub fn round_to_integer(&self) -> u8 {
let hundred = Decimal::from_ratio(100u32, 1u32);
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
truncate_decimal(hundred * self.0).u128() as u8
}
}
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
write!(f, "{}%", adjusted)
}
}
impl FromStr for Percent {
type Err = ContractsCommonError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Percent::new(Decimal::from_str(s)?)
}
}
impl Mul<Decimal> for Percent {
type Output = Decimal;
fn mul(self, rhs: Decimal) -> Self::Output {
self.0 * rhs
}
}
impl Mul<Percent> for Decimal {
type Output = Decimal;
fn mul(self, rhs: Percent) -> Self::Output {
rhs * self
}
}
impl Mul<Uint128> for Percent {
type Output = Uint128;
fn mul(self, rhs: Uint128) -> Self::Output {
self.0 * rhs
}
}
// implement custom Deserialize because we want to validate Percent has the correct range
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let v = Decimal::deserialize(deserializer)?;
if v > Decimal::one() {
Err(D::Error::custom(
"provided decimal percent is larger than 100%",
))
} else {
Ok(v)
}
}
// TODO: there's no reason this couldn't be used for proper binaries, but in that case // TODO: there's no reason this couldn't be used for proper binaries, but in that case
// perhaps the struct should get renamed and moved to a "more" common crate // perhaps the struct should get renamed and moved to a "more" common crate
@@ -13,9 +13,6 @@ pub enum MixnetContractError {
source: cosmwasm_std::StdError, source: cosmwasm_std::StdError,
}, },
#[error("Provided percent value is greater than 100%")]
InvalidPercent,
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")] #[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
OverflowDecimalSubtraction { OverflowDecimalSubtraction {
minuend: Decimal, minuend: Decimal,
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use contracts_common::truncate_decimal;
use cosmwasm_std::{Coin, Decimal, Uint128}; use cosmwasm_std::{Coin, Decimal, Uint128};
/// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would /// Truncates all decimal points so that the reward would fit in a `Coin` and so that we would
@@ -17,7 +18,3 @@ pub fn truncate_reward(reward: Decimal, denom: impl Into<String>) -> Coin {
pub fn truncate_reward_amount(reward: Decimal) -> Uint128 { pub fn truncate_reward_amount(reward: Decimal) -> Uint128 {
truncate_decimal(reward) truncate_decimal(reward)
} }
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -2,15 +2,12 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError; use crate::error::MixnetContractError;
use crate::rewarding::helpers::truncate_decimal;
use crate::{Layer, RewardedSetNodeStatus}; use crate::{Layer, RewardedSetNodeStatus};
use cosmwasm_std::{Addr, Uint128}; use cosmwasm_std::Addr;
use cosmwasm_std::{Coin, Decimal}; use cosmwasm_std::Coin;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::de::Error; use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Index;
use std::fmt::{self, Display, Formatter};
use std::ops::{Index, Mul};
// type aliases for better reasoning about available data // type aliases for better reasoning about available data
pub type IdentityKey = String; pub type IdentityKey = String;
@@ -24,87 +21,6 @@ pub type BlockHeight = u64;
pub type EpochEventId = u32; pub type EpochEventId = u32;
pub type IntervalEventId = u32; pub type IntervalEventId = u32;
/// Percent represents a value between 0 and 100%
/// (i.e. between 0.0 and 1.0)
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Serialize, Deserialize, JsonSchema,
)]
pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
impl Percent {
pub fn new(value: Decimal) -> Result<Self, MixnetContractError> {
if value > Decimal::one() {
Err(MixnetContractError::InvalidPercent)
} else {
Ok(Percent(value))
}
}
pub fn is_zero(&self) -> bool {
self.0 == Decimal::zero()
}
pub fn from_percentage_value(value: u64) -> Result<Self, MixnetContractError> {
Percent::new(Decimal::percent(value))
}
pub fn value(&self) -> Decimal {
self.0
}
pub fn round_to_integer(&self) -> u8 {
let hundred = Decimal::from_ratio(100u32, 1u32);
// we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range
truncate_decimal(hundred * self.0).u128() as u8
}
}
impl Display for Percent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let adjusted = Decimal::from_atomics(100u32, 0).unwrap() * self.0;
write!(f, "{}%", adjusted)
}
}
impl Mul<Decimal> for Percent {
type Output = Decimal;
fn mul(self, rhs: Decimal) -> Self::Output {
self.0 * rhs
}
}
impl Mul<Percent> for Decimal {
type Output = Decimal;
fn mul(self, rhs: Percent) -> Self::Output {
rhs * self
}
}
impl Mul<Uint128> for Percent {
type Output = Uint128;
fn mul(self, rhs: Uint128) -> Self::Output {
self.0 * rhs
}
}
// implement custom Deserialize because we want to validate Percent has the correct range
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: Deserializer<'de>,
{
let v = Decimal::deserialize(deserializer)?;
if v > Decimal::one() {
Err(D::Error::custom(
"provided decimal percent is larger than 100%",
))
} else {
Ok(v)
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
pub struct LayerDistribution { pub struct LayerDistribution {
pub layer1: u64, pub layer1: u64,
@@ -209,7 +125,7 @@ pub struct PagedRewardedSetResponse {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use contracts_common::Percent;
#[test] #[test]
fn percent_serde() { fn percent_serde() {
@@ -6,8 +6,10 @@ edition = "2021"
[dependencies] [dependencies]
cosmwasm-std = "1.0.0" cosmwasm-std = "1.0.0"
mixnet-contract-common = { path = "../mixnet-contract" } mixnet-contract-common = { path = "../mixnet-contract" }
contracts-common = { path = "../contracts-common" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
schemars = "0.8" schemars = "0.8"
log = "0.4"
ts-rs = {version = "6.1.2", optional = true} ts-rs = {version = "6.1.2", optional = true}
[features] [features]
@@ -1,6 +1,10 @@
use std::str::FromStr;
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128}; use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
use log::warn;
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -42,6 +46,46 @@ impl PledgeData {
} }
} }
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum PledgeCap {
Percent(Percent),
Absolute(Uint128), // This has to be in unym
}
impl FromStr for PledgeCap {
type Err = String;
fn from_str(cap: &str) -> Result<Self, Self::Err> {
let cap = cap.replace('_', "").replace(',', ".");
match Percent::from_str(&cap) {
Ok(p) => {
if p.is_zero() {
warn!("Pledge cap set to 0%, are you sure this is right?")
}
Ok(PledgeCap::Percent(p))
}
Err(_) => {
match cap.parse::<u128>() {
Ok(i) => {
if i < 100_000_000_000 {
warn!("PledgeCap set to less then 100_000 NYM, are you sure this is right?");
}
Ok(PledgeCap::Absolute(Uint128::from(i)))
}
Err(_e) => Err(format!("Could not parse {} as Percent or Uint128", cap)),
}
}
}
}
}
impl Default for PledgeCap {
fn default() -> Self {
PledgeCap::Absolute(Uint128::from(100_000_000_000u128))
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct OriginalVestingResponse { pub struct OriginalVestingResponse {
pub amount: Coin, pub amount: Coin,
@@ -98,3 +142,32 @@ pub struct AllDelegationsResponse {
pub delegations: Vec<VestingDelegation>, pub delegations: Vec<VestingDelegation>,
pub start_next_after: Option<(u32, MixId, u64)>, pub start_next_after: Option<(u32, MixId, u64)>,
} }
#[cfg(test)]
mod test {
use contracts_common::Percent;
use cosmwasm_std::Uint128;
use std::str::FromStr;
use crate::PledgeCap;
#[test]
fn test_pledge_cap_from_str() {
assert_eq!(
PledgeCap::from_str("0.1").unwrap(),
PledgeCap::Percent(Percent::from_percentage_value(10).unwrap())
);
assert_eq!(
PledgeCap::from_str("0,1").unwrap(),
PledgeCap::Percent(Percent::from_percentage_value(10).unwrap())
);
assert_eq!(
PledgeCap::from_str("100_000_000_000").unwrap(),
PledgeCap::Absolute(Uint128::new(100_000_000_000))
);
assert_eq!(
PledgeCap::from_str("100000000000").unwrap(),
PledgeCap::Absolute(Uint128::new(100_000_000_000))
);
}
}
@@ -1,4 +1,4 @@
use cosmwasm_std::{Coin, Timestamp, Uint128}; use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::{ use mixnet_contract_common::{
mixnode::{MixNodeConfigUpdate, MixNodeCostParams}, mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, MixId, MixNode, Gateway, MixId, MixNode,
@@ -6,6 +6,8 @@ use mixnet_contract_common::{
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::PledgeCap;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct InitMsg { pub struct InitMsg {
@@ -83,6 +85,7 @@ pub enum ExecuteMsg {
owner_address: String, owner_address: String,
staking_address: Option<String>, staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>, vesting_spec: Option<VestingSpecification>,
cap: Option<PledgeCap>,
}, },
WithdrawVestedCoins { WithdrawVestedCoins {
amount: Coin, amount: Coin,
@@ -120,7 +123,8 @@ pub enum ExecuteMsg {
to_address: Option<String>, to_address: Option<String>,
}, },
UpdateLockedPledgeCap { UpdateLockedPledgeCap {
amount: Uint128, address: String,
cap: PledgeCap,
}, },
} }
@@ -201,7 +205,6 @@ pub enum QueryMsg {
GetCurrentVestingPeriod { GetCurrentVestingPeriod {
address: String, address: String,
}, },
GetLockedPledgeCap {},
GetDelegationTimes { GetDelegationTimes {
address: String, address: String,
mix_id: MixId, mix_id: MixId,
+5
View File
@@ -221,7 +221,9 @@ name = "contracts-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]
@@ -1606,6 +1608,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract" name = "vesting-contract"
version = "1.1.0" version = "1.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"mixnet-contract-common", "mixnet-contract-common",
@@ -1619,7 +1622,9 @@ dependencies = [
name = "vesting-contract-common" name = "vesting-contract-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
+1
View File
@@ -15,6 +15,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
cosmwasm-std = { version = "1.0.0 "} cosmwasm-std = { version = "1.0.0 "}
+22 -14
View File
@@ -1,8 +1,8 @@
use crate::errors::ContractError; use crate::errors::ContractError;
use crate::queued_migrations::migrate_to_v2_mixnet_contract; use crate::queued_migrations::migrate_to_v2_mixnet_contract;
use crate::storage::{ use crate::storage::{
account_from_address, locked_pledge_cap, update_locked_pledge_cap, BlockTimestampSecs, ADMIN, account_from_address, BlockTimestampSecs, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS,
DELEGATIONS, MIXNET_CONTRACT_ADDRESS, MIX_DENOM, MIX_DENOM,
}; };
use crate::traits::{ use crate::traits::{
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount, DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
@@ -25,8 +25,8 @@ use vesting_contract_common::messages::{
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification, ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
}; };
use vesting_contract_common::{ use vesting_contract_common::{
AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeData, AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap,
VestingDelegation, PledgeData, VestingDelegation,
}; };
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000); pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
@@ -59,8 +59,8 @@ pub fn execute(
msg: ExecuteMsg, msg: ExecuteMsg,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
match msg { match msg {
ExecuteMsg::UpdateLockedPledgeCap { amount } => { ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
try_update_locked_pledge_cap(amount, info, deps) try_update_locked_pledge_cap(address, cap, info, deps)
} }
ExecuteMsg::TrackReward { amount, address } => { ExecuteMsg::TrackReward { amount, address } => {
try_track_reward(deps, info, amount, &address) try_track_reward(deps, info, amount, &address)
@@ -88,10 +88,12 @@ pub fn execute(
owner_address, owner_address,
staking_address, staking_address,
vesting_spec, vesting_spec,
cap,
} => try_create_periodic_vesting_account( } => try_create_periodic_vesting_account(
&owner_address, &owner_address,
staking_address, staking_address,
vesting_spec, vesting_spec,
cap,
info, info,
env, env,
deps, deps,
@@ -144,14 +146,18 @@ pub fn execute(
/// ///
/// Callable by ADMIN only, see [instantiate]. /// Callable by ADMIN only, see [instantiate].
pub fn try_update_locked_pledge_cap( pub fn try_update_locked_pledge_cap(
amount: Uint128, address: String,
cap: PledgeCap,
info: MessageInfo, info: MessageInfo,
deps: DepsMut, deps: DepsMut,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
if info.sender != ADMIN.load(deps.storage)? { if info.sender != ADMIN.load(deps.storage)? {
return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
} }
update_locked_pledge_cap(amount, deps.storage)?; let mut account = account_from_address(&address, deps.storage, deps.api)?;
account.pledge_cap = Some(cap);
// update_locked_pledge_cap(amount, deps.storage)?;
Ok(Response::default()) Ok(Response::default())
} }
@@ -430,6 +436,7 @@ fn try_create_periodic_vesting_account(
owner_address: &str, owner_address: &str,
staking_address: Option<String>, staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>, vesting_spec: Option<VestingSpecification>,
cap: Option<PledgeCap>,
info: MessageInfo, info: MessageInfo,
env: Env, env: Env,
deps: DepsMut<'_>, deps: DepsMut<'_>,
@@ -437,6 +444,7 @@ fn try_create_periodic_vesting_account(
if info.sender != ADMIN.load(deps.storage)? { if info.sender != ADMIN.load(deps.storage)? {
return Err(ContractError::NotAdmin(info.sender.as_str().to_string())); return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
} }
let mix_denom = MIX_DENOM.load(deps.storage)?; let mix_denom = MIX_DENOM.load(deps.storage)?;
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok(); let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
@@ -452,6 +460,11 @@ fn try_create_periodic_vesting_account(
let owner_address = deps.api.addr_validate(owner_address)?; let owner_address = deps.api.addr_validate(owner_address)?;
let staking_address = if let Some(staking_address) = staking_address { let staking_address = if let Some(staking_address) = staking_address {
let staking_account_exists =
account_from_address(&staking_address, deps.storage, deps.api).is_ok();
if staking_account_exists {
return Err(ContractError::StakingAccountAlreadyExists(staking_address));
}
Some(deps.api.addr_validate(&staking_address)?) Some(deps.api.addr_validate(&staking_address)?)
} else { } else {
None None
@@ -472,6 +485,7 @@ fn try_create_periodic_vesting_account(
coin.clone(), coin.clone(),
start_time, start_time,
periods, periods,
cap,
deps.storage, deps.storage,
)?; )?;
@@ -486,7 +500,6 @@ fn try_create_periodic_vesting_account(
#[entry_point] #[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> { pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg { let query_res = match msg {
QueryMsg::GetLockedPledgeCap {} => to_binary(&get_locked_pledge_cap(deps)),
QueryMsg::LockedCoins { QueryMsg::LockedCoins {
vesting_account_address, vesting_account_address,
block_time, block_time,
@@ -567,11 +580,6 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
Ok(query_res?) Ok(query_res?)
} }
/// Get locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
pub fn get_locked_pledge_cap(deps: Deps<'_>) -> Uint128 {
locked_pledge_cap(deps.storage)
}
/// Get current vesting period for a given [crate::vesting::Account]. /// Get current vesting period for a given [crate::vesting::Account].
pub fn try_get_current_vesting_period( pub fn try_get_current_vesting_period(
address: &str, address: &str,
+2
View File
@@ -44,6 +44,8 @@ pub enum ContractError {
InvalidAddress(String), InvalidAddress(String),
#[error("VESTING ({}): Account already exists: {0}", line!())] #[error("VESTING ({}): Account already exists: {0}", line!())]
AccountAlreadyExists(String), AccountAlreadyExists(String),
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
StakingAccountAlreadyExists(String),
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())] #[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
MinVestingFunds { sent: u128, need: u128 }, MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())] #[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
+1 -16
View File
@@ -1,5 +1,5 @@
use crate::errors::ContractError;
use crate::vesting::Account; use crate::vesting::Account;
use crate::{contract::INITIAL_LOCKED_PLEDGE_CAP, errors::ContractError};
use cosmwasm_std::{Addr, Api, Storage, Uint128}; use cosmwasm_std::{Addr, Api, Storage, Uint128};
use cw_storage_plus::{Item, Map}; use cw_storage_plus::{Item, Map};
use mixnet_contract_common::{IdentityKey, MixId}; use mixnet_contract_common::{IdentityKey, MixId};
@@ -20,21 +20,6 @@ pub const DELEGATIONS: Map<'_, (u32, MixId, BlockTimestampSecs), Uint128> = Map:
pub const ADMIN: Item<'_, String> = Item::new("adm"); pub const ADMIN: Item<'_, String> = Item::new("adm");
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix"); pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
pub const MIX_DENOM: Item<'_, String> = Item::new("den"); pub const MIX_DENOM: Item<'_, String> = Item::new("den");
pub const LOCKED_PLEDGE_CAP: Item<'_, Uint128> = Item::new("lck");
pub fn locked_pledge_cap(storage: &dyn Storage) -> Uint128 {
LOCKED_PLEDGE_CAP
.load(storage)
.unwrap_or(INITIAL_LOCKED_PLEDGE_CAP)
}
pub fn update_locked_pledge_cap(
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
LOCKED_PLEDGE_CAP.save(storage, &amount)?;
Ok(())
}
pub fn save_delegation( pub fn save_delegation(
key: (u32, MixId, BlockTimestampSecs), key: (u32, MixId, BlockTimestampSecs),
+27
View File
@@ -2,9 +2,12 @@
pub mod helpers { pub mod helpers {
use crate::contract::instantiate; use crate::contract::instantiate;
use crate::vesting::{populate_vesting_periods, Account}; use crate::vesting::{populate_vesting_periods, Account};
use contracts_common::Percent;
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128}; use cosmwasm_std::{Addr, Coin, Empty, Env, MemoryStorage, OwnedDeps, Storage, Uint128};
use std::str::FromStr;
use vesting_contract_common::messages::{InitMsg, VestingSpecification}; use vesting_contract_common::messages::{InitMsg, VestingSpecification};
use vesting_contract_common::PledgeCap;
pub const TEST_COIN_DENOM: &str = "unym"; pub const TEST_COIN_DENOM: &str = "unym";
@@ -37,6 +40,7 @@ pub mod helpers {
}, },
start_time_ts, start_time_ts,
periods, periods,
None,
storage, storage,
) )
.unwrap() .unwrap()
@@ -56,6 +60,29 @@ pub mod helpers {
}, },
start_time, start_time,
periods, periods,
Some(PledgeCap::from_str("0.1").unwrap()),
storage,
)
.unwrap()
}
pub fn vesting_account_percent_fixture(storage: &mut dyn Storage, env: &Env) -> Account {
let start_time = env.block.time;
let periods =
populate_vesting_periods(start_time.seconds(), VestingSpecification::default());
Account::new(
Addr::unchecked("owner"),
Some(Addr::unchecked("staking")),
Coin {
amount: Uint128::new(1_000_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
},
start_time,
periods,
Some(PledgeCap::Percent(
Percent::from_percentage_value(10).unwrap(),
)),
storage, storage,
) )
.unwrap() .unwrap()
@@ -1,5 +1,4 @@
use crate::errors::ContractError; use crate::errors::ContractError;
use crate::storage::locked_pledge_cap;
use crate::storage::save_delegation; use crate::storage::save_delegation;
use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::DelegatingAccount; use crate::traits::DelegatingAccount;
@@ -38,8 +37,9 @@ impl DelegatingAccount for Account {
storage: &mut dyn Storage, storage: &mut dyn Storage,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
let current_balance = self.load_balance(storage)?; let current_balance = self.load_balance(storage)?;
let total_pledged_after = self.total_pledged_locked(storage, env)? + coin.amount; let total_pledged_locked = self.total_pledged_locked(storage, env)?;
let locked_pledge_cap = locked_pledge_cap(storage); let total_pledged_after = total_pledged_locked + coin.amount;
let locked_pledge_cap = self.absolute_pledge_cap()?;
if locked_pledge_cap < total_pledged_after { if locked_pledge_cap < total_pledged_after {
return Err(ContractError::LockedPledgeCapReached { return Err(ContractError::LockedPledgeCapReached {
@@ -1,6 +1,5 @@
use super::PledgeData; use super::PledgeData;
use crate::errors::ContractError; use crate::errors::ContractError;
use crate::storage::locked_pledge_cap;
use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::GatewayBondingAccount; use crate::traits::GatewayBondingAccount;
use crate::traits::VestingAccount; use crate::traits::VestingAccount;
@@ -22,8 +21,9 @@ impl GatewayBondingAccount for Account {
storage: &mut dyn Storage, storage: &mut dyn Storage,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
let current_balance = self.load_balance(storage)?; let current_balance = self.load_balance(storage)?;
let total_pledged_after = self.total_pledged_locked(storage, env)? + pledge.amount; let total_pledged_locked = self.total_pledged_locked(storage, env)?;
let locked_pledge_cap = locked_pledge_cap(storage); let total_pledged_after = total_pledged_locked + pledge.amount;
let locked_pledge_cap = self.absolute_pledge_cap()?;
if locked_pledge_cap < total_pledged_after { if locked_pledge_cap < total_pledged_after {
return Err(ContractError::LockedPledgeCapReached { return Err(ContractError::LockedPledgeCapReached {
@@ -3,7 +3,6 @@
use super::Account; use super::Account;
use crate::errors::ContractError; use crate::errors::ContractError;
use crate::storage::locked_pledge_cap;
use crate::storage::MIXNET_CONTRACT_ADDRESS; use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::MixnodeBondingAccount; use crate::traits::MixnodeBondingAccount;
use crate::traits::VestingAccount; use crate::traits::VestingAccount;
@@ -39,8 +38,9 @@ impl MixnodeBondingAccount for Account {
storage: &mut dyn Storage, storage: &mut dyn Storage,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
let current_balance = self.load_balance(storage)?; let current_balance = self.load_balance(storage)?;
let total_pledged_after = self.total_pledged_locked(storage, env)? + pledge.amount; let total_pledged_locked = self.total_pledged_locked(storage, env)?;
let locked_pledge_cap = locked_pledge_cap(storage); let total_pledged_after = total_pledged_locked + pledge.amount;
let locked_pledge_cap = self.absolute_pledge_cap()?;
if locked_pledge_cap < total_pledged_after { if locked_pledge_cap < total_pledged_after {
return Err(ContractError::LockedPledgeCapReached { return Err(ContractError::LockedPledgeCapReached {
+17 -1
View File
@@ -5,12 +5,13 @@ use crate::storage::{
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge, remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY, save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY,
}; };
use crate::traits::VestingAccount;
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
use cw_storage_plus::Bound; use cw_storage_plus::Bound;
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use vesting_contract_common::{Period, PledgeData}; use vesting_contract_common::{Period, PledgeCap, PledgeData};
mod delegating_account; mod delegating_account;
mod gateway_bonding_account; mod gateway_bonding_account;
@@ -31,6 +32,8 @@ pub struct Account {
pub periods: Vec<VestingPeriod>, pub periods: Vec<VestingPeriod>,
pub coin: Coin, pub coin: Coin,
storage_key: u32, storage_key: u32,
#[serde(default)]
pub pledge_cap: Option<PledgeCap>,
} }
impl Account { impl Account {
@@ -40,6 +43,7 @@ impl Account {
coin: Coin, coin: Coin,
start_time: Timestamp, start_time: Timestamp,
periods: Vec<VestingPeriod>, periods: Vec<VestingPeriod>,
pledge_cap: Option<PledgeCap>,
storage: &mut dyn Storage, storage: &mut dyn Storage,
) -> Result<Self, ContractError> { ) -> Result<Self, ContractError> {
let storage_key = generate_storage_key(storage)?; let storage_key = generate_storage_key(storage)?;
@@ -51,12 +55,24 @@ impl Account {
periods, periods,
coin, coin,
storage_key, storage_key,
pledge_cap,
}; };
save_account(&account, storage)?; save_account(&account, storage)?;
account.save_balance(amount, storage)?; account.save_balance(amount, storage)?;
Ok(account) Ok(account)
} }
pub fn pledge_cap(&self) -> PledgeCap {
self.pledge_cap.clone().unwrap_or_default()
}
pub fn absolute_pledge_cap(&self) -> Result<Uint128, ContractError> {
match self.pledge_cap() {
PledgeCap::Absolute(cap) => Ok(cap),
PledgeCap::Percent(p) => Ok(p * self.get_original_vesting().amount.amount),
}
}
pub fn coin(&self) -> Coin { pub fn coin(&self) -> Coin {
self.coin.clone() self.coin.clone()
} }
@@ -123,12 +123,13 @@ impl VestingAccount for Account {
storage: &dyn Storage, storage: &dyn Storage,
) -> Result<Coin, ContractError> { ) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time); let block_time = block_time.unwrap_or(env.block.time);
let period = self.get_current_vesting_period(block_time);
let withdrawn = self.load_withdrawn(storage)?; let withdrawn = self.load_withdrawn(storage)?;
let max_available = self let max_available = self
.get_vested_coins(Some(block_time), env, storage)? .get_vested_coins(Some(block_time), env, storage)?
.amount .amount
.saturating_sub(withdrawn); .saturating_sub(withdrawn);
let period = self.get_current_vesting_period(block_time);
let start_time = match period { let start_time = match period {
Period::Before => 0, Period::Before => 0,
Period::After => u64::MAX, Period::After => u64::MAX,
@@ -155,15 +156,8 @@ impl VestingAccount for Account {
let block_time = block_time.unwrap_or(env.block.time); let block_time = block_time.unwrap_or(env.block.time);
let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?; let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?;
let period = self.get_current_vesting_period(block_time);
let start_time = match period {
Period::Before => 0,
Period::After => u64::MAX,
Period::In(idx) => self.periods[idx as usize].start_time,
};
let delegations_before_start_time = let delegations_before_start_time =
self.total_delegations_at_timestamp(storage, start_time)?; self.total_delegations_at_timestamp(storage, block_time.seconds())?;
let amount = delegations_before_start_time - delegated_free.amount; let amount = delegations_before_start_time - delegated_free.amount;
+36 -2
View File
@@ -38,6 +38,7 @@ pub fn populate_vesting_periods(
mod tests { mod tests {
use crate::contract::*; use crate::contract::*;
use crate::storage::*; use crate::storage::*;
use crate::support::tests::helpers::vesting_account_percent_fixture;
use crate::support::tests::helpers::{ use crate::support::tests::helpers::{
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM, init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
}; };
@@ -51,6 +52,7 @@ mod tests {
use mixnet_contract_common::{Gateway, MixNode, Percent}; use mixnet_contract_common::{Gateway, MixNode, Percent};
use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification}; use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification};
use vesting_contract_common::Period; use vesting_contract_common::Period;
use vesting_contract_common::PledgeCap;
#[test] #[test]
fn test_account_creation() { fn test_account_creation() {
@@ -61,6 +63,7 @@ mod tests {
owner_address: "owner".to_string(), owner_address: "owner".to_string(),
staking_address: Some("staking".to_string()), staking_address: Some("staking".to_string()),
vesting_spec: None, vesting_spec: None,
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
}; };
// Try creating an account when not admin // Try creating an account when not admin
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
@@ -389,12 +392,38 @@ mod tests {
assert_eq!(locked_coins.amount, Uint128::new(660_000_000_000)); assert_eq!(locked_coins.amount, Uint128::new(660_000_000_000));
} }
#[test]
fn test_percent_cap() {
let mut deps = init_contract();
let env = mock_env();
let account = vesting_account_percent_fixture(&mut deps.storage, &env);
assert_eq!(
account.absolute_pledge_cap().unwrap(),
Uint128::new(100_000_000_000)
)
}
#[test] #[test]
fn test_delegations() { fn test_delegations() {
let mut deps = init_contract(); let mut deps = init_contract();
let env = mock_env(); let env = mock_env();
let account = vesting_account_new_fixture(&mut deps.storage, &env); // let account = vesting_account_new_fixture(&mut deps.storage, &env);
let msg = ExecuteMsg::CreateAccount {
owner_address: "owner".to_string(),
staking_address: Some("staking".to_string()),
vesting_spec: None,
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
};
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
let account = load_account(&Addr::unchecked("owner"), &deps.storage)
.unwrap()
.unwrap();
// Try delegating too much // Try delegating too much
let err = account.try_delegate_to_mixnode( let err = account.try_delegate_to_mixnode(
@@ -434,7 +463,7 @@ mod tests {
let balance = account.load_balance(&deps.storage).unwrap(); let balance = account.load_balance(&deps.storage).unwrap();
assert_eq!(balance, Uint128::new(910000000000)); assert_eq!(balance, Uint128::new(910000000000));
// Try delegating too much again // Try delegating too much againcalca
let err = account.try_delegate_to_mixnode( let err = account.try_delegate_to_mixnode(
1, 1,
Coin { Coin {
@@ -449,6 +478,10 @@ mod tests {
let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap(); let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap();
assert_eq!(Uint128::new(90_000_000_000), total_delegations); assert_eq!(Uint128::new(90_000_000_000), total_delegations);
let account = load_account(&Addr::unchecked("owner"), &deps.storage)
.unwrap()
.unwrap();
// Current period -> block_time: None // Current period -> block_time: None
let delegated_free = account let delegated_free = account
.get_delegated_free(None, &env, &deps.storage) .get_delegated_free(None, &env, &deps.storage)
@@ -813,6 +846,7 @@ mod tests {
}, },
Timestamp::from_seconds(account_creation_timestamp), Timestamp::from_seconds(account_creation_timestamp),
periods, periods,
Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
deps.as_mut().storage, deps.as_mut().storage,
) )
.unwrap(); .unwrap();
+1
View File
@@ -27,6 +27,7 @@ maxminddb = "0.23.0"
dotenv = "0.15.0" dotenv = "0.15.0"
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
network-defaults = { path = "../common/network-defaults" } network-defaults = { path = "../common/network-defaults" }
task = { path = "../common/task" } task = { path = "../common/task" }
validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] }
+1 -1
View File
@@ -4,7 +4,7 @@
use crate::client::ThreadsafeValidatorClient; use crate::client::ThreadsafeValidatorClient;
use crate::helpers::best_effort_small_dec_to_f64; use crate::helpers::best_effort_small_dec_to_f64;
use crate::mix_node::models::EconomicDynamicsStats; use crate::mix_node::models::EconomicDynamicsStats;
use mixnet_contract_common::rewarding::helpers::truncate_decimal; use contracts_common::truncate_decimal;
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
pub(crate) async fn retrieve_mixnode_econ_stats( pub(crate) async fn retrieve_mixnode_econ_stats(
+5 -5
View File
@@ -1,6 +1,6 @@
EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1 EXPLORER_API_URL=https://qwerty-network-explorer.qa.nymte.ch/api/v1
VALIDATOR_API_URL=https://qa-validator-api.nymtech.net VALIDATOR_API_URL=https://qwerty-validator-api.qa.nymte.ch
VALIDATOR_URL=https://qa-validator.nymtech.net VALIDATOR_URL=https://qwerty-validator.qa.nymte.ch
BIG_DIPPER_URL=https://qa-blocks.nymtech.net BIG_DIPPER_URL=https://qwerty-blocks.nymtech.net
CURRENCY_DENOM=unym CURRENCY_DENOM=unym
CURRENCY_STAKING_DENOM=unyx CURRENCY_STAKING_DENOM=unyx
@@ -7,15 +7,9 @@ const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamics
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability; const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow // TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (inclusionProbability) { switch (inclusionProbability) {
case 'VeryLow':
return 'Very Low';
case 'VeryHigh':
return 'Very High';
case 'High': case 'High':
case 'Good': case 'Good':
case 'Low': case 'Low':
case 'Moderate':
return inclusionProbability;
default: default:
return '-'; return '-';
} }
@@ -19,16 +19,12 @@ const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
return theme.palette.warning.main; return theme.palette.warning.main;
} }
if (field === 'selectionChance') { if (field === 'selectionChance') {
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (fieldValue) { switch (fieldValue) {
case 'High': case 'High':
case 'VeryHigh':
return theme.palette.nym.networkExplorer.selectionChance.overModerate; return theme.palette.nym.networkExplorer.selectionChance.overModerate;
case 'Good': case 'Good':
case 'Moderate':
return theme.palette.nym.networkExplorer.selectionChance.moderate; return theme.palette.nym.networkExplorer.selectionChance.moderate;
case 'Low': case 'Low':
case 'VeryLow':
return theme.palette.nym.networkExplorer.selectionChance.underModerate; return theme.palette.nym.networkExplorer.selectionChance.underModerate;
default: default:
return theme.palette.nym.wallet.fee; return theme.palette.nym.wallet.fee;
+1 -2
View File
@@ -215,8 +215,7 @@ export type UptimeStoryResponse = {
export type MixNodeEconomicDynamicsStatsResponse = { export type MixNodeEconomicDynamicsStatsResponse = {
stake_saturation: number; stake_saturation: number;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow active_set_inclusion_probability: 'High' | 'Good' | 'Low';
active_set_inclusion_probability: 'High' | 'Good' | 'Low' | 'VeryLow' | 'Moderate' | 'VeryHigh';
reserve_set_inclusion_probability: 'High' | 'Good' | 'Low'; reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
estimated_total_node_reward: number; estimated_total_node_reward: number;
estimated_operator_reward: number; estimated_operator_reward: number;
+1 -1
View File
@@ -6810,4 +6810,4 @@ dependencies = [
"byteorder", "byteorder",
"crc32fast", "crc32fast",
"crossbeam-utils", "crossbeam-utils",
] ]
+5
View File
@@ -665,7 +665,9 @@ name = "contracts-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]
@@ -5308,6 +5310,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
name = "vesting-contract" name = "vesting-contract"
version = "1.1.0" version = "1.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
"mixnet-contract-common", "mixnet-contract-common",
@@ -5321,7 +5324,9 @@ dependencies = [
name = "vesting-contract-common" name = "vesting-contract-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
+1
View File
@@ -72,6 +72,7 @@ crypto = { path = "../common/crypto" }
gateway-client = { path = "../common/client-libs/gateway-client" } gateway-client = { path = "../common/client-libs/gateway-client" }
inclusion-probability = { path = "../common/inclusion-probability" } inclusion-probability = { path = "../common/inclusion-probability" }
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
nymcoconut = { path = "../common/nymcoconut", optional = true } nymcoconut = { path = "../common/nymcoconut", optional = true }
nymsphinx = { path = "../common/nymsphinx" } nymsphinx = { path = "../common/nymsphinx" }
+1 -1
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::contract_cache::{Cache, CacheNotification, ValidatorCache}; use crate::contract_cache::{Cache, CacheNotification, ValidatorCache};
use mixnet_contract_common::rewarding::helpers::truncate_decimal; use contracts_common::truncate_decimal;
use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams}; use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams};
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use serde::Serialize; use serde::Serialize;