Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d916854ba3 | |||
| dbd0e6b4fa | |||
| abf7b57ccb | |||
| ec1988b745 | |||
| 08bb9c0fbc |
Generated
+2
@@ -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]]
|
||||||
|
|||||||
@@ -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,128 @@
|
|||||||
// 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 zero() -> Self {
|
||||||
|
Self(Decimal::zero())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hundred() -> Self {
|
||||||
|
Self(Decimal::one())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -136,4 +136,7 @@ pub enum MixnetContractError {
|
|||||||
|
|
||||||
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
|
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
|
||||||
DuplicateRewardedSetNode { mix_id: MixId },
|
DuplicateRewardedSetNode { mix_id: MixId },
|
||||||
|
|
||||||
|
#[error("Migration from the old contract is currently in progress. The transaction is temporarily disabled until that is finished")]
|
||||||
|
MigrationInProgress,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::reward_params::{
|
|||||||
};
|
};
|
||||||
use crate::{delegation, ContractStateParams, MixId, Percent};
|
use crate::{delegation, ContractStateParams, MixId, Percent};
|
||||||
use crate::{Gateway, IdentityKey, MixNode};
|
use crate::{Gateway, IdentityKey, MixNode};
|
||||||
use cosmwasm_std::Decimal;
|
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -18,6 +18,7 @@ use std::time::Duration;
|
|||||||
pub struct InstantiateMsg {
|
pub struct InstantiateMsg {
|
||||||
pub rewarding_validator_address: String,
|
pub rewarding_validator_address: String,
|
||||||
pub vesting_contract_address: String,
|
pub vesting_contract_address: String,
|
||||||
|
pub v1_mixnet_contract_address: String,
|
||||||
|
|
||||||
pub rewarding_denom: String,
|
pub rewarding_denom: String,
|
||||||
pub epochs_in_interval: u32,
|
pub epochs_in_interval: u32,
|
||||||
@@ -31,6 +32,7 @@ pub struct InitialRewardingParams {
|
|||||||
pub initial_reward_pool: Decimal,
|
pub initial_reward_pool: Decimal,
|
||||||
pub initial_staking_supply: Decimal,
|
pub initial_staking_supply: Decimal,
|
||||||
|
|
||||||
|
pub staking_supply_scale_factor: Percent,
|
||||||
pub sybil_resistance: Percent,
|
pub sybil_resistance: Percent,
|
||||||
pub active_set_work_factor: Decimal,
|
pub active_set_work_factor: Decimal,
|
||||||
pub interval_pool_emission: Percent,
|
pub interval_pool_emission: Percent,
|
||||||
@@ -51,6 +53,7 @@ impl InitialRewardingParams {
|
|||||||
interval: IntervalRewardParams {
|
interval: IntervalRewardParams {
|
||||||
reward_pool: self.initial_reward_pool,
|
reward_pool: self.initial_reward_pool,
|
||||||
staking_supply: self.initial_staking_supply,
|
staking_supply: self.initial_staking_supply,
|
||||||
|
staking_supply_scale_factor: self.staking_supply_scale_factor,
|
||||||
epoch_reward_budget,
|
epoch_reward_budget,
|
||||||
stake_saturation_point,
|
stake_saturation_point,
|
||||||
sybil_resistance: self.sybil_resistance,
|
sybil_resistance: self.sybil_resistance,
|
||||||
@@ -66,6 +69,80 @@ impl InitialRewardingParams {
|
|||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ExecuteMsg {
|
pub enum ExecuteMsg {
|
||||||
|
// special, first-time migration:
|
||||||
|
#[serde(rename = "m1")]
|
||||||
|
SaveOperator {
|
||||||
|
#[serde(rename = "a1")]
|
||||||
|
host: String,
|
||||||
|
|
||||||
|
#[serde(rename = "a2")]
|
||||||
|
mix_port: u16,
|
||||||
|
|
||||||
|
#[serde(rename = "a3")]
|
||||||
|
verloc_port: u16,
|
||||||
|
|
||||||
|
#[serde(rename = "a4")]
|
||||||
|
http_api_port: u16,
|
||||||
|
|
||||||
|
#[serde(rename = "a5")]
|
||||||
|
sphinx_key: String,
|
||||||
|
|
||||||
|
#[serde(rename = "a6")]
|
||||||
|
identity_key: IdentityKey,
|
||||||
|
|
||||||
|
#[serde(rename = "a7")]
|
||||||
|
version: String,
|
||||||
|
|
||||||
|
#[serde(rename = "a8")]
|
||||||
|
pledge_amount: Coin,
|
||||||
|
|
||||||
|
#[serde(rename = "a9")]
|
||||||
|
owner: Addr,
|
||||||
|
|
||||||
|
#[serde(rename = "a10")]
|
||||||
|
block_height: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "a11")]
|
||||||
|
profit_margin_percent: u8,
|
||||||
|
|
||||||
|
#[serde(rename = "a12")]
|
||||||
|
proxy: Option<Addr>,
|
||||||
|
},
|
||||||
|
#[serde(rename = "m2")]
|
||||||
|
SaveDelegation {
|
||||||
|
#[serde(rename = "a1")]
|
||||||
|
owner: Addr,
|
||||||
|
|
||||||
|
#[serde(rename = "a2")]
|
||||||
|
mix_id: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "a3")]
|
||||||
|
amount: Coin,
|
||||||
|
|
||||||
|
#[serde(rename = "a4")]
|
||||||
|
block_height: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "a5")]
|
||||||
|
proxy: Option<Addr>,
|
||||||
|
},
|
||||||
|
#[serde(rename = "m3")]
|
||||||
|
SaveGateway {
|
||||||
|
#[serde(rename = "a1")]
|
||||||
|
pledge_amount: Coin,
|
||||||
|
|
||||||
|
#[serde(rename = "a2")]
|
||||||
|
owner: Addr,
|
||||||
|
|
||||||
|
#[serde(rename = "a3")]
|
||||||
|
block_height: u64,
|
||||||
|
|
||||||
|
#[serde(rename = "a4")]
|
||||||
|
gateway: Gateway,
|
||||||
|
|
||||||
|
#[serde(rename = "a5")]
|
||||||
|
proxy: Option<Addr>,
|
||||||
|
},
|
||||||
|
|
||||||
// state/sys-params-related
|
// state/sys-params-related
|
||||||
UpdateRewardingValidatorAddress {
|
UpdateRewardingValidatorAddress {
|
||||||
address: String,
|
address: String,
|
||||||
@@ -266,6 +343,7 @@ impl ExecuteMsg {
|
|||||||
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
|
||||||
"resolving all pending events".into()
|
"resolving all pending events".into()
|
||||||
}
|
}
|
||||||
|
_ => unimplemented!("migration-specific messages are not supported by this"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ pub struct IntervalRewardParams {
|
|||||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
pub staking_supply: Decimal,
|
pub staking_supply: Decimal,
|
||||||
|
|
||||||
|
/// Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set.
|
||||||
|
/// Also known as `beta`.
|
||||||
|
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||||
|
pub staking_supply_scale_factor: Percent,
|
||||||
|
|
||||||
// computed values
|
// computed values
|
||||||
/// Current value of the computed reward budget per epoch, per node.
|
/// Current value of the computed reward budget per epoch, per node.
|
||||||
/// It is expected to be constant throughout the interval.
|
/// It is expected to be constant throughout the interval.
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ impl Simulator {
|
|||||||
if self.interval.current_interval_id() + 1 == updated.current_interval_id() {
|
if self.interval.current_interval_id() + 1 == updated.current_interval_id() {
|
||||||
let old = self.system_rewarding_params.interval;
|
let old = self.system_rewarding_params.interval;
|
||||||
let reward_pool = old.reward_pool - self.pending_reward_pool_emission;
|
let reward_pool = old.reward_pool - self.pending_reward_pool_emission;
|
||||||
let staking_supply = old.staking_supply + self.pending_reward_pool_emission;
|
let staking_supply = old.staking_supply
|
||||||
|
+ self
|
||||||
|
.system_rewarding_params
|
||||||
|
.interval
|
||||||
|
.staking_supply_scale_factor
|
||||||
|
* self.pending_reward_pool_emission;
|
||||||
let epoch_reward_budget = reward_pool
|
let epoch_reward_budget = reward_pool
|
||||||
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
|
/ Decimal::from_atomics(self.interval.epochs_in_interval(), 0).unwrap()
|
||||||
* old.interval_pool_emission.value();
|
* old.interval_pool_emission.value();
|
||||||
@@ -51,6 +56,7 @@ impl Simulator {
|
|||||||
interval: IntervalRewardParams {
|
interval: IntervalRewardParams {
|
||||||
reward_pool,
|
reward_pool,
|
||||||
staking_supply,
|
staking_supply,
|
||||||
|
staking_supply_scale_factor: old.staking_supply_scale_factor,
|
||||||
epoch_reward_budget,
|
epoch_reward_budget,
|
||||||
stake_saturation_point,
|
stake_saturation_point,
|
||||||
sybil_resistance: old.sybil_resistance,
|
sybil_resistance: old.sybil_resistance,
|
||||||
@@ -209,6 +215,7 @@ mod tests {
|
|||||||
interval: IntervalRewardParams {
|
interval: IntervalRewardParams {
|
||||||
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
||||||
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
||||||
|
staking_supply_scale_factor: Percent::hundred(),
|
||||||
epoch_reward_budget,
|
epoch_reward_budget,
|
||||||
stake_saturation_point,
|
stake_saturation_point,
|
||||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||||
@@ -537,6 +544,7 @@ mod tests {
|
|||||||
interval: IntervalRewardParams {
|
interval: IntervalRewardParams {
|
||||||
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(),
|
reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(),
|
||||||
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(),
|
staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(),
|
||||||
|
staking_supply_scale_factor: Percent::hundred(),
|
||||||
epoch_reward_budget,
|
epoch_reward_budget,
|
||||||
stake_saturation_point,
|
stake_saturation_point,
|
||||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||||
|
|||||||
@@ -24,87 +24,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,
|
||||||
|
|||||||
Generated
+2
@@ -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]]
|
||||||
|
|||||||
+103
-180
@@ -2,18 +2,28 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT};
|
use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT};
|
||||||
|
use crate::delegations;
|
||||||
|
use crate::gateways::storage as gateways_storage;
|
||||||
use crate::interval::storage as interval_storage;
|
use crate::interval::storage as interval_storage;
|
||||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||||
use crate::mixnodes::storage as mixnode_storage;
|
use crate::mixnodes::storage as mixnode_storage;
|
||||||
|
use crate::mixnodes::storage::{assign_layer, next_mixnode_id_counter};
|
||||||
use crate::rewards::storage as rewards_storage;
|
use crate::rewards::storage as rewards_storage;
|
||||||
use cosmwasm_std::{
|
use cosmwasm_std::{
|
||||||
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
coin, entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
use cw_storage_plus::Item;
|
||||||
use mixnet_contract_common::error::MixnetContractError;
|
use mixnet_contract_common::error::MixnetContractError;
|
||||||
use mixnet_contract_common::{
|
use mixnet_contract_common::{
|
||||||
ContractState, ContractStateParams, ExecuteMsg, InstantiateMsg, Interval, MigrateMsg, QueryMsg,
|
ContractState, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, InstantiateMsg,
|
||||||
|
Interval, MigrateMsg, MixNode, MixNodeBond, MixNodeCostParams, MixNodeRewarding, Percent,
|
||||||
|
QueryMsg,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// To be removed once entire contract is unlocked
|
||||||
|
const V1_MIXNET_CONTRACT: Item<'_, String> = Item::new("v1_mixnet_contract");
|
||||||
|
|
||||||
fn default_initial_state(
|
fn default_initial_state(
|
||||||
owner: Addr,
|
owner: Addr,
|
||||||
rewarding_validator_address: Addr,
|
rewarding_validator_address: Addr,
|
||||||
@@ -70,6 +80,8 @@ pub fn instantiate(
|
|||||||
mixnode_storage::initialise_storage(deps.storage)?;
|
mixnode_storage::initialise_storage(deps.storage)?;
|
||||||
rewards_storage::initialise_storage(deps.storage, reward_params)?;
|
rewards_storage::initialise_storage(deps.storage, reward_params)?;
|
||||||
|
|
||||||
|
V1_MIXNET_CONTRACT.save(deps.storage, &msg.v1_mixnet_contract_address)?;
|
||||||
|
|
||||||
Ok(Response::default())
|
Ok(Response::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,194 +93,102 @@ pub fn execute(
|
|||||||
info: MessageInfo,
|
info: MessageInfo,
|
||||||
msg: ExecuteMsg,
|
msg: ExecuteMsg,
|
||||||
) -> Result<Response, MixnetContractError> {
|
) -> Result<Response, MixnetContractError> {
|
||||||
|
// only the old mixnet contract is allowed to request migration-specific commands here
|
||||||
|
if info.sender != V1_MIXNET_CONTRACT.load(deps.storage)? {
|
||||||
|
return Err(MixnetContractError::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
// state/sys-params-related
|
ExecuteMsg::SaveOperator {
|
||||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
host,
|
||||||
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
|
mix_port,
|
||||||
deps, info, address,
|
verloc_port,
|
||||||
)
|
http_api_port,
|
||||||
}
|
sphinx_key,
|
||||||
ExecuteMsg::UpdateContractStateParams { updated_parameters } => {
|
identity_key,
|
||||||
crate::mixnet_contract_settings::transactions::try_update_contract_settings(
|
version,
|
||||||
deps,
|
pledge_amount,
|
||||||
info,
|
|
||||||
updated_parameters,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UpdateActiveSetSize {
|
|
||||||
active_set_size,
|
|
||||||
force_immediately,
|
|
||||||
} => crate::rewards::transactions::try_update_active_set_size(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
info,
|
|
||||||
active_set_size,
|
|
||||||
force_immediately,
|
|
||||||
),
|
|
||||||
ExecuteMsg::UpdateRewardingParams {
|
|
||||||
updated_params,
|
|
||||||
force_immediately,
|
|
||||||
} => crate::rewards::transactions::try_update_rewarding_params(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
info,
|
|
||||||
updated_params,
|
|
||||||
force_immediately,
|
|
||||||
),
|
|
||||||
ExecuteMsg::UpdateIntervalConfig {
|
|
||||||
epochs_in_interval,
|
|
||||||
epoch_duration_secs,
|
|
||||||
force_immediately,
|
|
||||||
} => crate::interval::transactions::try_update_interval_config(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
info,
|
|
||||||
epochs_in_interval,
|
|
||||||
epoch_duration_secs,
|
|
||||||
force_immediately,
|
|
||||||
),
|
|
||||||
ExecuteMsg::AdvanceCurrentEpoch {
|
|
||||||
new_rewarded_set,
|
|
||||||
expected_active_set_size,
|
|
||||||
} => crate::interval::transactions::try_advance_epoch(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
info,
|
|
||||||
new_rewarded_set,
|
|
||||||
expected_active_set_size,
|
|
||||||
),
|
|
||||||
ExecuteMsg::ReconcileEpochEvents { limit } => {
|
|
||||||
crate::interval::transactions::try_reconcile_epoch_events(deps, env, info, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mixnode-related:
|
|
||||||
ExecuteMsg::BondMixnode {
|
|
||||||
mix_node,
|
|
||||||
cost_params,
|
|
||||||
owner_signature,
|
|
||||||
} => crate::mixnodes::transactions::try_add_mixnode(
|
|
||||||
deps,
|
|
||||||
env,
|
|
||||||
info,
|
|
||||||
mix_node,
|
|
||||||
cost_params,
|
|
||||||
owner_signature,
|
|
||||||
),
|
|
||||||
ExecuteMsg::BondMixnodeOnBehalf {
|
|
||||||
mix_node,
|
|
||||||
cost_params,
|
|
||||||
owner,
|
owner,
|
||||||
owner_signature,
|
block_height,
|
||||||
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
|
profit_margin_percent,
|
||||||
deps,
|
proxy,
|
||||||
env,
|
} => {
|
||||||
info,
|
let mixnode = MixNode {
|
||||||
mix_node,
|
host,
|
||||||
cost_params,
|
mix_port,
|
||||||
owner,
|
verloc_port,
|
||||||
owner_signature,
|
http_api_port,
|
||||||
),
|
sphinx_key,
|
||||||
ExecuteMsg::UnbondMixnode {} => {
|
identity_key,
|
||||||
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
|
version,
|
||||||
}
|
};
|
||||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
let layer = assign_layer(deps.storage)?;
|
||||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
|
|
||||||
crate::mixnodes::transactions::try_update_mixnode_cost_params(
|
|
||||||
deps, env, info, new_costs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
|
|
||||||
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
|
|
||||||
deps, env, info, new_costs, owner,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
|
|
||||||
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
|
|
||||||
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
|
|
||||||
deps, info, new_config, owner,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// gateway-related:
|
let cost_params = MixNodeCostParams {
|
||||||
ExecuteMsg::BondGateway {
|
// this value must be valid since we got it from the v1 contract which we trust
|
||||||
gateway,
|
profit_margin_percent: Percent::from_percentage_value(profit_margin_percent as u64)
|
||||||
owner_signature,
|
.unwrap(),
|
||||||
} => crate::gateways::transactions::try_add_gateway(
|
interval_operating_cost: coin(40_000_000, &pledge_amount.denom),
|
||||||
deps,
|
};
|
||||||
env,
|
let node_id = next_mixnode_id_counter(deps.storage)?;
|
||||||
info,
|
let current_epoch =
|
||||||
gateway,
|
interval_storage::current_interval(deps.storage)?.current_epoch_absolute_id();
|
||||||
owner_signature,
|
let mixnode_rewarding =
|
||||||
),
|
MixNodeRewarding::initialise_new(cost_params, &pledge_amount, current_epoch);
|
||||||
ExecuteMsg::BondGatewayOnBehalf {
|
let mixnode_bond = MixNodeBond::new(
|
||||||
gateway,
|
node_id,
|
||||||
owner,
|
owner,
|
||||||
owner_signature,
|
pledge_amount,
|
||||||
} => crate::gateways::transactions::try_add_gateway_on_behalf(
|
layer,
|
||||||
deps,
|
mixnode,
|
||||||
env,
|
proxy,
|
||||||
info,
|
block_height,
|
||||||
gateway,
|
);
|
||||||
owner,
|
|
||||||
owner_signature,
|
|
||||||
),
|
|
||||||
ExecuteMsg::UnbondGateway {} => {
|
|
||||||
crate::gateways::transactions::try_remove_gateway(deps, info)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
|
|
||||||
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
// delegation-related:
|
mixnode_storage::mixnode_bonds().save(deps.storage, node_id, &mixnode_bond)?;
|
||||||
ExecuteMsg::DelegateToMixnode { mix_id } => {
|
rewards_storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mixnode_rewarding)?;
|
||||||
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id)
|
|
||||||
}
|
|
||||||
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
|
|
||||||
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
|
|
||||||
deps, env, info, mix_id, delegate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
|
|
||||||
crate::delegations::transactions::try_remove_delegation_from_mixnode(
|
|
||||||
deps, env, info, mix_id,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
|
|
||||||
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
|
|
||||||
deps, env, info, mix_id, delegate,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reward-related
|
Ok(Response::new())
|
||||||
ExecuteMsg::RewardMixnode {
|
}
|
||||||
|
ExecuteMsg::SaveDelegation {
|
||||||
|
owner,
|
||||||
mix_id,
|
mix_id,
|
||||||
performance,
|
amount,
|
||||||
} => crate::rewards::transactions::try_reward_mixnode(deps, env, info, mix_id, performance),
|
block_height,
|
||||||
|
proxy,
|
||||||
|
} => {
|
||||||
|
let mut mix_rewarding =
|
||||||
|
rewards_storage::MIXNODE_REWARDING.load(deps.storage, mix_id)?;
|
||||||
|
mix_rewarding.add_base_delegation(amount.amount);
|
||||||
|
rewards_storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?;
|
||||||
|
|
||||||
ExecuteMsg::WithdrawOperatorReward {} => {
|
let delegation = Delegation::new(
|
||||||
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
|
owner,
|
||||||
|
mix_id,
|
||||||
|
mix_rewarding.total_unit_reward,
|
||||||
|
amount,
|
||||||
|
block_height,
|
||||||
|
proxy,
|
||||||
|
);
|
||||||
|
|
||||||
|
let storage_key = delegation.storage_key();
|
||||||
|
delegations::storage::delegations().save(deps.storage, storage_key, &delegation)?;
|
||||||
|
Ok(Response::new())
|
||||||
}
|
}
|
||||||
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
|
ExecuteMsg::SaveGateway {
|
||||||
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
|
pledge_amount,
|
||||||
}
|
owner,
|
||||||
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
|
block_height,
|
||||||
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
|
gateway,
|
||||||
}
|
proxy,
|
||||||
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
|
} => {
|
||||||
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
|
let bond = GatewayBond::new(pledge_amount, owner, block_height, gateway, proxy);
|
||||||
deps, info, mix_id, owner,
|
|
||||||
)
|
gateways_storage::gateways().save(deps.storage, bond.identity(), &bond)?;
|
||||||
|
Ok(Response::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
// testing-only
|
_ => Err(MixnetContractError::MigrationInProgress),
|
||||||
#[cfg(feature = "contract-testing")]
|
|
||||||
ExecuteMsg::TestingResolveAllPendingEvents { limit } => {
|
|
||||||
crate::testing::transactions::try_resolve_all_pending_events(deps, env, limit)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,12 +411,14 @@ mod tests {
|
|||||||
let init_msg = InstantiateMsg {
|
let init_msg = InstantiateMsg {
|
||||||
rewarding_validator_address: "foomp123".to_string(),
|
rewarding_validator_address: "foomp123".to_string(),
|
||||||
vesting_contract_address: "bar456".to_string(),
|
vesting_contract_address: "bar456".to_string(),
|
||||||
|
v1_mixnet_contract_address: "whatever".to_string(),
|
||||||
rewarding_denom: "uatom".to_string(),
|
rewarding_denom: "uatom".to_string(),
|
||||||
epochs_in_interval: 1234,
|
epochs_in_interval: 1234,
|
||||||
epoch_duration: Duration::from_secs(4321),
|
epoch_duration: Duration::from_secs(4321),
|
||||||
initial_rewarding_params: InitialRewardingParams {
|
initial_rewarding_params: InitialRewardingParams {
|
||||||
initial_reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
initial_reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
||||||
initial_staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
initial_staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
||||||
|
staking_supply_scale_factor: Percent::hundred(),
|
||||||
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
||||||
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
||||||
interval_pool_emission: Percent::from_percentage_value(1).unwrap(),
|
interval_pool_emission: Percent::from_percentage_value(1).unwrap(),
|
||||||
@@ -535,6 +457,7 @@ mod tests {
|
|||||||
interval: IntervalRewardParams {
|
interval: IntervalRewardParams {
|
||||||
reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
reward_pool: Decimal::from_atomics(100_000_000_000_000u128, 0).unwrap(),
|
||||||
staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
staking_supply: Decimal::from_atomics(123_456_000_000_000u128, 0).unwrap(),
|
||||||
|
staking_supply_scale_factor: Percent::hundred(),
|
||||||
epoch_reward_budget: expected_epoch_reward_budget,
|
epoch_reward_budget: expected_epoch_reward_budget,
|
||||||
stake_saturation_point: expected_stake_saturation_point,
|
stake_saturation_point: expected_stake_saturation_point,
|
||||||
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
sybil_resistance: Percent::from_percentage_value(23).unwrap(),
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ pub(crate) fn apply_reward_pool_changes(
|
|||||||
|
|
||||||
let reward_pool = rewarding_params.interval.reward_pool - pending_pool_change.removed
|
let reward_pool = rewarding_params.interval.reward_pool - pending_pool_change.removed
|
||||||
+ pending_pool_change.added;
|
+ pending_pool_change.added;
|
||||||
let staking_supply = rewarding_params.interval.staking_supply + pending_pool_change.removed;
|
let staking_supply = rewarding_params.interval.staking_supply
|
||||||
|
+ rewarding_params.interval.staking_supply_scale_factor * pending_pool_change.removed;
|
||||||
let epoch_reward_budget = reward_pool
|
let epoch_reward_budget = reward_pool
|
||||||
/ Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap()
|
/ Decimal::from_atomics(interval.epochs_in_interval(), 0).unwrap()
|
||||||
* rewarding_params.interval.interval_pool_emission;
|
* rewarding_params.interval.interval_pool_emission;
|
||||||
|
|||||||
@@ -768,6 +768,7 @@ pub mod test_helpers {
|
|||||||
InitialRewardingParams {
|
InitialRewardingParams {
|
||||||
initial_reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
initial_reward_pool: Decimal::from_atomics(reward_pool, 0).unwrap(), // 250M * 1M (we're expressing it all in base tokens)
|
||||||
initial_staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
initial_staking_supply: Decimal::from_atomics(staking_supply, 0).unwrap(), // 100M * 1M
|
||||||
|
staking_supply_scale_factor: Percent::hundred(),
|
||||||
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
sybil_resistance: Percent::from_percentage_value(30).unwrap(),
|
||||||
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
active_set_work_factor: Decimal::from_atomics(10u32, 0).unwrap(),
|
||||||
interval_pool_emission: Percent::from_percentage_value(2).unwrap(),
|
interval_pool_emission: Percent::from_percentage_value(2).unwrap(),
|
||||||
|
|||||||
Generated
+2
@@ -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]]
|
||||||
|
|||||||
Reference in New Issue
Block a user