Skip to content

Commit

Permalink
Implement parameters for liquidity tournament control (in funding)
Browse files Browse the repository at this point in the history
Closes #5013.

I also added a little utility type for representing percentages. Not
strictly necessary, but I think it's a good move towards type safety.
The motivation here was all of the annoyance of dealing with keeping
track of what u64s were bps or bps^2 or yadda yadda yadda in other parts
of the codebase.
  • Loading branch information
cronokirby committed Jan 29, 2025
1 parent 881cd26 commit f20af27
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 16 deletions.
10 changes: 8 additions & 2 deletions crates/core/app/src/params/change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ impl AppParameters {
fixed_gas_prices: _,
fixed_alt_gas_prices: _,
},
funding_params: FundingParameters {},
funding_params:
FundingParameters {
liquidity_tournament: _,
},
governance_params:
GovernanceParameters {
proposal_voting_blocks: _,
Expand Down Expand Up @@ -171,7 +174,10 @@ impl AppParameters {
fixed_gas_prices: _,
fixed_alt_gas_prices: _,
},
funding_params: FundingParameters {},
funding_params:
FundingParameters {
liquidity_tournament: _,
},
governance_params:
GovernanceParameters {
proposal_voting_blocks,
Expand Down
73 changes: 67 additions & 6 deletions crates/core/component/funding/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,60 @@
use penumbra_sdk_num::Percentage;
use penumbra_sdk_proto::core::component::funding::v1 as pb;
use penumbra_sdk_proto::DomainType;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LiquidityTournamentParameters {
// The fraction of gauge votes that an asset must clear to receive rewards.
pub gauge_threshold: Percentage,
// The maximum number of liquidity positions that can receive rewards.
pub max_positions: u64,
// The maximum number of delegators that can receive rewards.
pub max_delegators: u64,
// The share of rewards that go to delegators, instead of positions.
pub delegator_share: Percentage,
}

impl Default for LiquidityTournamentParameters {
fn default() -> Self {
Self {
gauge_threshold: Percentage::from_percent(100),
max_positions: 0,
max_delegators: 0,
delegator_share: Percentage::zero(),
}
}
}

impl TryFrom<pb::funding_parameters::LiquidityTournament> for LiquidityTournamentParameters {
type Error = anyhow::Error;

fn try_from(proto: pb::funding_parameters::LiquidityTournament) -> Result<Self, Self::Error> {
Ok(Self {
gauge_threshold: Percentage::from_percent(proto.gauge_threshold_percent),
max_positions: proto.max_positions,
max_delegators: proto.max_delegators,
delegator_share: Percentage::from_percent(proto.delegator_share_percent),
})
}
}

impl From<LiquidityTournamentParameters> for pb::funding_parameters::LiquidityTournament {
fn from(value: LiquidityTournamentParameters) -> Self {
Self {
gauge_threshold_percent: value.gauge_threshold.to_percent(),
max_positions: value.max_positions,
max_delegators: value.max_delegators,
delegator_share_percent: value.delegator_share.to_percent(),
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(try_from = "pb::FundingParameters", into = "pb::FundingParameters")]
pub struct FundingParameters {}
pub struct FundingParameters {
pub liquidity_tournament: LiquidityTournamentParameters,
}

impl DomainType for FundingParameters {
type Proto = pb::FundingParameters;
Expand All @@ -13,19 +63,30 @@ impl DomainType for FundingParameters {
impl TryFrom<pb::FundingParameters> for FundingParameters {
type Error = anyhow::Error;

fn try_from(_params: pb::FundingParameters) -> anyhow::Result<Self> {
Ok(FundingParameters {})
fn try_from(proto: pb::FundingParameters) -> anyhow::Result<Self> {
Ok(FundingParameters {
// Explicitly consider missing parameters to *be* the default parameters, for upgrades.
liquidity_tournament: proto
.liquidity_tournament
.map(LiquidityTournamentParameters::try_from)
.transpose()?
.unwrap_or_default(),
})
}
}

impl From<FundingParameters> for pb::FundingParameters {
fn from(_params: FundingParameters) -> Self {
pb::FundingParameters {}
fn from(params: FundingParameters) -> Self {
pb::FundingParameters {
liquidity_tournament: Some(params.liquidity_tournament.into()),
}
}
}

impl Default for FundingParameters {
fn default() -> Self {
Self {}
Self {
liquidity_tournament: LiquidityTournamentParameters::default(),
}
}
}
2 changes: 2 additions & 0 deletions crates/core/num/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod amount;
pub mod fixpoint;
mod percentage;

pub use amount::{Amount, AmountVar};
pub use percentage::Percentage;
36 changes: 36 additions & 0 deletions crates/core/num/src/percentage/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// Represents a percentage value.
///
/// Useful for more robust typesafety, versus just passing around a `u64` which
/// is merely *understood* to only contain values in [0, 100].
///
/// Defaults to 0%.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Percentage(u64);

impl Percentage {
/// 0%
pub const fn zero() -> Self {
Self(0)
}

/// Convert this value into a `u64` in [0, 100];
pub const fn to_percent(self) -> u64 {
self.0
}

/// Given an arbitrary `u64`, produce a percentage, *saturating* at 100.
pub fn from_percent(p: u64) -> Self {
Self(u64::min(p.into(), 100))
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_percentage_operations() {
assert_eq!(Percentage::from_percent(101), Percentage::from_percent(100));
assert_eq!(Percentage::from_percent(48).to_percent(), 48);
}
}
46 changes: 45 additions & 1 deletion crates/proto/src/gen/penumbra.core.component.funding.v1.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
// This file is @generated by prost-build.
/// Funding component configuration data.
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct FundingParameters {}
pub struct FundingParameters {
/// The parameters governing the funding of the liquidity tournament.
#[prost(message, optional, tag = "1")]
pub liquidity_tournament: ::core::option::Option<
funding_parameters::LiquidityTournament,
>,
}
/// Nested message and enum types in `FundingParameters`.
pub mod funding_parameters {
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct LiquidityTournament {
/// The fraction of gauge votes that an asset must pass to get any rewards.
///
/// Takes a value in \[0, 100\].
#[prost(uint64, tag = "1")]
pub gauge_threshold_percent: u64,
/// The maximum number of liquidity positions that can receive rewards.
///
/// This avoids potential DoS vectors with processing a large number of small positions.
#[prost(uint64, tag = "2")]
pub max_positions: u64,
/// The maximum number of delegators that can be rewarded.
///
/// Also avoids potential DoS vectors
#[prost(uint64, tag = "3")]
pub max_delegators: u64,
/// The share of rewards which will go to delegators, opposed with positions.
///
/// Takes a value in \[0, 100\].
#[prost(uint64, tag = "4")]
pub delegator_share_percent: u64,
}
impl ::prost::Name for LiquidityTournament {
const NAME: &'static str = "LiquidityTournament";
const PACKAGE: &'static str = "penumbra.core.component.funding.v1";
fn full_name() -> ::prost::alloc::string::String {
"penumbra.core.component.funding.v1.FundingParameters.LiquidityTournament"
.into()
}
fn type_url() -> ::prost::alloc::string::String {
"/penumbra.core.component.funding.v1.FundingParameters.LiquidityTournament"
.into()
}
}
}
impl ::prost::Name for FundingParameters {
const NAME: &'static str = "FundingParameters";
const PACKAGE: &'static str = "penumbra.core.component.funding.v1";
Expand Down
Loading

0 comments on commit f20af27

Please sign in to comment.