Skip to content

Commit

Permalink
funding: add component parameters for LQT (#5020)
Browse files Browse the repository at this point in the history
Closes #5013.

Diverges slightly from the ADR draft in that it creates a nested proto
sub-message specific to the LQT. My rationale here was that this is
nicer than prefixing every relevant field with "liquidity_tournament",
both in the proto definitions, but also in the Rust code, because that
way we can have a default for that entire set of parameters, and then
the code can explicitly mark the absence of that set as to be
interpreted as this default. Also works nicer for the future, in which
we might be using the funding component for other things, each of which
can cleanly have its own set of parameters.

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.

I've added some relevant unit tests.

## Checklist before requesting a review

- [x] I have added guiding text to explain how a reviewer should test
these changes.

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:

  > adds new parameters that can be voted on, so consensus-breaking.

---------

Co-authored-by: Erwan Or <[email protected]>
  • Loading branch information
cronokirby and erwanor authored Jan 29, 2025
1 parent 881cd26 commit 1b1edc2
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 17 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
2 changes: 1 addition & 1 deletion crates/core/component/funding/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Component for Funding {
#[instrument(name = "funding", skip(state, app_state))]
async fn init_chain<S: StateWrite>(mut state: S, app_state: Option<&Self::AppState>) {
match app_state {
None => { /* Checkpoint -- no-op */ }
None => { /* no-op */ }
Some(genesis) => {
state.put_funding_params(genesis.funding_params.clone());
}
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 1b1edc2

Please sign in to comment.