diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 38bd021bc11..ae6ab3b42d9 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -29,7 +29,6 @@ pub mod genesis_config { use hex; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; - use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::ledger::treasury::parameters::TreasuryParams; use namada::types::address::Address; @@ -37,6 +36,7 @@ pub mod genesis_config { use namada::types::key::*; use namada::types::time::Rfc3339String; use namada::types::{storage, token}; + use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -265,24 +265,24 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token (in basis points). // XXX: u64 doesn't work with toml-rs! - pub votes_per_token: u64, + pub votes_per_token: Decimal, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, // Reward for voting on a block. // XXX: u64 doesn't work with toml-rs! - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, // Maximum staking APY // XXX: u64 doesn't work with toml-rs! - pub max_staking_rewards_rate: u64, + pub max_staking_rewards_rate: Decimal, // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_slash_rate: u64, + pub duplicate_vote_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a // light client attack (in basis points). // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_slash_rate: u64, + pub light_client_attack_slash_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -569,20 +569,18 @@ pub mod genesis_config { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, unbonding_len: config.pos_params.unbonding_len, - votes_per_token: BasisPoints::new( - config.pos_params.votes_per_token, - ), + votes_per_token: config.pos_params.votes_per_token, block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, - max_staking_rewards_rate: BasisPoints::new( - config.pos_params.max_staking_rewards_rate, - ), - duplicate_vote_slash_rate: BasisPoints::new( - config.pos_params.duplicate_vote_slash_rate, - ), - light_client_attack_slash_rate: BasisPoints::new( - config.pos_params.light_client_attack_slash_rate, - ), + max_staking_rewards_rate: config + .pos_params + .max_staking_rewards_rate, + duplicate_vote_slash_rate: config + .pos_params + .duplicate_vote_slash_rate, + light_client_attack_slash_rate: config + .pos_params + .light_client_attack_slash_rate, }; let mut genesis = Genesis { diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index cb6b8d6da80..8cd4a8c680a 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,10 +1,11 @@ //! Proof-of-Stake system parameters use borsh::{BorshDeserialize, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; use thiserror::Error; -use crate::types::BasisPoints; - /// Proof-of-Stake system parameters #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct PosParams { @@ -20,20 +21,20 @@ pub struct PosParams { pub unbonding_len: u64, /// Used in validators' voting power calculation. Given in basis points /// (voting power per ten thousand tokens). - pub votes_per_token: BasisPoints, + pub votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, /// Amount of tokens rewarded to each validator that voted on a block /// proposal - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, /// Maximum staking rewards rate per annum - pub max_staking_rewards_rate: BasisPoints, + pub max_staking_rewards_rate: Decimal, /// Portion of validator's stake that should be slashed on a duplicate /// vote. Given in basis points (slashed amount per ten thousand tokens). - pub duplicate_vote_slash_rate: BasisPoints, + pub duplicate_vote_slash_rate: Decimal, /// Portion of validator's stake that should be slashed on a light client /// attack. Given in basis points (slashed amount per ten thousand tokens). - pub light_client_attack_slash_rate: BasisPoints, + pub light_client_attack_slash_rate: Decimal, } impl Default for PosParams { @@ -42,16 +43,17 @@ impl Default for PosParams { max_validator_slots: 128, pipeline_len: 2, unbonding_len: 6, - // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per namnam) - votes_per_token: BasisPoints::new(10000), - block_proposer_reward: 100, - block_vote_reward: 1, + // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per + // namnam) + votes_per_token: dec!(1.0), + block_proposer_reward: dec!(0.0625), + block_vote_reward: dec!(0.05), // staking APY 20% - max_staking_rewards_rate: BasisPoints::new(2000), + max_staking_rewards_rate: dec!(0.2), // slash 5% - duplicate_vote_slash_rate: BasisPoints::new(500), + duplicate_vote_slash_rate: dec!(0.05), // slash 5% - light_client_attack_slash_rate: BasisPoints::new(500), + light_client_attack_slash_rate: dec!(0.05), } } } @@ -65,7 +67,7 @@ pub enum ValidationError { )] TotalVotingPowerTooLarge(u64), #[error("Votes per token cannot be greater than 1, got {0}")] - VotesPerTokenGreaterThanOne(BasisPoints), + VotesPerTokenGreaterThanOne(Decimal), #[error("Pipeline length must be >= 2, got {0}")] PipelineLenTooShort(u64), #[error( @@ -105,8 +107,8 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - let max_total_voting_power = self.max_validator_slots - * (self.votes_per_token * TOKEN_MAX_AMOUNT); + let max_total_voting_power = Decimal::from(self.max_validator_slots) + * self.votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { @@ -121,7 +123,7 @@ impl PosParams { } // Check that there is no more than 1 vote per token - if self.votes_per_token > BasisPoints::new(10_000) { + if self.votes_per_token > dec!(1.0) { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.votes_per_token, )) @@ -174,7 +176,7 @@ pub mod testing { max_validator_slots, pipeline_len, unbonding_len, - votes_per_token: BasisPoints::new(votes_per_token), + votes_per_token: Decimal::from(votes_per_token) / dec!(10_000), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index c809a05956f..e3f84eece66 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -1,6 +1,7 @@ //! PoS rewards use rust_decimal::Decimal; +use rust_decimal_macros::dec; use thiserror::Error; use crate::types::VotingPower; @@ -29,20 +30,20 @@ pub struct PosRewards { /// bing #[derive(Debug, Copy, Clone)] pub struct PosRewardsCalculator { - proposer_param: u64, - signer_param: u64, signing_stake: VotingPower, total_stake: VotingPower, + proposer_param: Decimal, + signer_param: Decimal, pos_rewards: Option, } impl PosRewardsCalculator { /// new pub fn new( - proposer_param: u64, - signer_param: u64, signing_stake: VotingPower, total_stake: VotingPower, + proposer_param: Decimal, + signer_param: Decimal, ) -> Self { Self { proposer_param, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 8148046f9fd..407d00c4dfd 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -6,9 +6,11 @@ use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; use std::num::TryFromIntError; -use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; use crate::epoched::{ Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnbondingLen, @@ -324,7 +326,7 @@ pub struct Slash { /// A type of slashsable event. pub r#type: SlashType, /// A rate is the portion of staked tokens that are slashed. - pub rate: BasisPoints, + pub rate: Decimal, } /// Slashes applied to validator, to punish byzantine behavior by removing @@ -362,23 +364,6 @@ impl From for VoteInfo { } } -/// ‱ (Parts per ten thousand). This can be multiplied by any type that -/// implements [`Into`] or [`Into`]. -#[derive( - Debug, - Clone, - Copy, - BorshDeserialize, - BorshSerialize, - BorshSchema, - PartialOrd, - Ord, - PartialEq, - Eq, - Hash, -)] -pub struct BasisPoints(u64); - /// Derive Tendermint raw hash from the public key pub trait PublicKeyTmRawHash { /// Derive Tendermint raw hash from the public key @@ -737,7 +722,7 @@ where impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. - pub fn get_slash_rate(&self, params: &PosParams) -> BasisPoints { + pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { match self { SlashType::DuplicateVote => params.duplicate_vote_slash_rate, SlashType::LightClientAttack => { @@ -756,35 +741,8 @@ impl Display for SlashType { } } -impl BasisPoints { - /// Initialize basis points from an integer. - pub fn new(value: u64) -> Self { - Self(value) - } -} - -impl Display for BasisPoints { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}‱", self.0) - } -} - -impl Mul for BasisPoints { - type Output = u64; - - fn mul(self, rhs: u64) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 / 10_000 - } } -impl Mul for BasisPoints { - type Output = i128; - - fn mul(self, rhs: i128) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 as i128 / 10_000 - } } #[cfg(test)]