diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index bb44f82881..d29c0c03fd 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -830,6 +830,7 @@ pub async fn query_protocol_parameters( liveness_threshold, rewards_gain_p, rewards_gain_d, + min_commission_rate, }, max_proposal_period: _, } = query_pos_parameters(context.client()).await; @@ -929,6 +930,12 @@ pub async fn query_protocol_parameters( "", tm_votes_per_token ); + display_line!( + context.io(), + "{:4}Minimum allowable validator commission rate: {}", + "", + min_commission_rate + ); } pub async fn query_bond( diff --git a/crates/apps_lib/src/config/genesis/chain.rs b/crates/apps_lib/src/config/genesis/chain.rs index ac4ad408c4..628b887a3d 100644 --- a/crates/apps_lib/src/config/genesis/chain.rs +++ b/crates/apps_lib/src/config/genesis/chain.rs @@ -400,6 +400,7 @@ impl Finalized { liveness_threshold, rewards_gain_p, rewards_gain_d, + min_commission_rate, } = self.parameters.pos_params.clone(); namada_sdk::proof_of_stake::parameters::PosParams { @@ -420,6 +421,7 @@ impl Finalized { liveness_threshold, rewards_gain_p, rewards_gain_d, + min_commission_rate, }, max_proposal_period: self.parameters.gov_params.max_proposal_period, } diff --git a/crates/apps_lib/src/config/genesis/templates.rs b/crates/apps_lib/src/config/genesis/templates.rs index 5e3aaf881e..7170bac033 100644 --- a/crates/apps_lib/src/config/genesis/templates.rs +++ b/crates/apps_lib/src/config/genesis/templates.rs @@ -415,6 +415,8 @@ pub struct PosParams { pub rewards_gain_p: Dec, /// PoS gain d (read only) pub rewards_gain_d: Dec, + /// Minimum validator commission rate + pub min_commission_rate: Dec, } #[derive( diff --git a/crates/proof_of_stake/src/error.rs b/crates/proof_of_stake/src/error.rs index 40b1b46592..fa6cfbf499 100644 --- a/crates/proof_of_stake/src/error.rs +++ b/crates/proof_of_stake/src/error.rs @@ -91,6 +91,11 @@ pub enum CommissionRateChangeError { LargerThanOne(Dec, Address), #[error("Rate change of {0} is too large for validator {1}")] RateChangeTooLarge(Dec, Address), + #[error( + "Invalid commission rate of {0}; the minimum allowed by the protocol \ + is {1}" + )] + RateBelowMin(Dec, Dec), #[error( "There is no maximum rate change written in storage for validator {0}" )] diff --git a/crates/proof_of_stake/src/lib.rs b/crates/proof_of_stake/src/lib.rs index c93126c44a..10a0948040 100644 --- a/crates/proof_of_stake/src/lib.rs +++ b/crates/proof_of_stake/src/lib.rs @@ -1667,6 +1667,7 @@ where S: StorageRead + StorageWrite, Gov: governance::Read, { + // Check that the rate is a number between 0.0 and 1.0 if new_rate.is_negative() { return Err(CommissionRateChangeError::NegativeRate( new_rate, @@ -1674,7 +1675,6 @@ where ) .into()); } - if new_rate > Dec::one() { return Err(CommissionRateChangeError::LargerThanOne( new_rate, @@ -1690,6 +1690,16 @@ where })?; let params = read_pos_params::(storage)?; + + // Check that the new rate is allowed by the min commission rate + if new_rate < params.min_commission_rate { + return Err(CommissionRateChangeError::RateBelowMin( + new_rate, + params.min_commission_rate, + ) + .into()); + } + let commission_handle = validator_commission_rate_handle(validator); let pipeline_epoch = checked!(current_epoch + params.pipeline_len)?; diff --git a/crates/proof_of_stake/src/parameters.rs b/crates/proof_of_stake/src/parameters.rs index 1e1032dbe5..88286b77eb 100644 --- a/crates/proof_of_stake/src/parameters.rs +++ b/crates/proof_of_stake/src/parameters.rs @@ -80,6 +80,8 @@ pub struct OwnedPosParams { pub rewards_gain_p: Dec, /// PoS gain d (read only) pub rewards_gain_d: Dec, + /// Minimum validator commission rate + pub min_commission_rate: Dec, } impl Default for OwnedPosParams { @@ -108,6 +110,7 @@ impl Default for OwnedPosParams { liveness_threshold: Dec::new(9, 1).expect("Test failed"), rewards_gain_p: Dec::from_str("0.25").expect("Test failed"), rewards_gain_d: Dec::from_str("0.25").expect("Test failed"), + min_commission_rate: Dec::from_str("0.05").expect("Test failed"), } } } diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 9e74fd3cc5..fd98b5fe5c 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -636,6 +636,18 @@ pub async fn build_validator_commission_change( *rate, ))); } + if *rate < params.min_commission_rate { + edisplay_line!( + context.io(), + "New rate is below the minimum allowed by the protocol: {}", + params.min_commission_rate + ); + if !tx_args.force { + return Err(Error::from(TxSubmitError::InvalidCommissionRate( + *rate, + ))); + } + } let pipeline_epoch_minus_one = epoch.unchecked_add(params.pipeline_len - 1); @@ -873,6 +885,19 @@ pub async fn build_validator_metadata_change( ))); } } + if *rate < params.min_commission_rate { + edisplay_line!( + context.io(), + "New rate is below the minimum allowed by the protocol: {}", + params.min_commission_rate + ); + if !tx_args.force { + return Err(Error::from(TxSubmitError::InvalidCommissionRate( + *rate, + ))); + } + } + let pipeline_epoch_minus_one = epoch.unchecked_add(params.pipeline_len - 1); diff --git a/genesis/hardware/parameters.toml b/genesis/hardware/parameters.toml index cda15160aa..ffd3abdad5 100644 --- a/genesis/hardware/parameters.toml +++ b/genesis/hardware/parameters.toml @@ -71,6 +71,8 @@ liveness_threshold = "0.9" rewards_gain_p = "0.25" # The D gain factor in the Proof of Stake rewards controller rewards_gain_d = "0.25" +# Minimum allowable validator commission rate +min_commission_rate = "0.05" # Governance parameters. [gov_params] diff --git a/genesis/localnet/parameters.toml b/genesis/localnet/parameters.toml index 161e4da0ab..f843bdd17f 100644 --- a/genesis/localnet/parameters.toml +++ b/genesis/localnet/parameters.toml @@ -71,6 +71,8 @@ liveness_threshold = "0.9" rewards_gain_p = "0.25" # The D gain factor in the Proof of Stake rewards controller rewards_gain_d = "0.25" +# Minimum allowable validator commission rate +min_commission_rate = "0.05" # Governance parameters. [gov_params] diff --git a/genesis/starter/parameters.toml b/genesis/starter/parameters.toml index 79b53b6b1c..1a09c1c8cc 100644 --- a/genesis/starter/parameters.toml +++ b/genesis/starter/parameters.toml @@ -71,6 +71,8 @@ liveness_threshold = "0.9" rewards_gain_p = "0.25" # The D gain factor in the Proof of Stake rewards controller rewards_gain_d = "0.25" +# Minimum allowable validator commission rate +min_commission_rate = "0.05" # Governance parameters. [gov_params]