Skip to content

Commit

Permalink
Implement check_and_execute (sans nullifier check) for LQT
Browse files Browse the repository at this point in the history
  • Loading branch information
cronokirby committed Jan 31, 2025
1 parent 551c987 commit 6097a52
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 8 deletions.
1 change: 1 addition & 0 deletions crates/core/component/funding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ component = [
"penumbra-sdk-proto/cnidarium",
"penumbra-sdk-community-pool/component",
"penumbra-sdk-distributions/component",
"penumbra-sdk-governance/component",
"penumbra-sdk-sct/component",
"penumbra-sdk-shielded-pool/component",
"penumbra-sdk-stake/component",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use anyhow::Context as _;
use anyhow::{anyhow, Context as _};
use async_trait::async_trait;
use cnidarium::StateWrite;
use penumbra_sdk_asset::asset::Denom;
use cnidarium::{StateRead, StateWrite};
use cnidarium_component::ActionHandler;
use penumbra_sdk_asset::{asset::Denom, Value};
use penumbra_sdk_governance::StateReadExt as _;
use penumbra_sdk_num::Amount;
use penumbra_sdk_proof_params::DELEGATOR_VOTE_PROOF_VERIFICATION_KEY;
use penumbra_sdk_sct::component::clock::EpochRead as _;
use penumbra_sdk_sct::epoch::Epoch;
use penumbra_sdk_stake::component::validator_handler::ValidatorDataRead as _;
use penumbra_sdk_tct::Position;
use penumbra_sdk_txhash::TransactionContext;

use crate::component::liquidity_tournament::votes::StateWriteExt as _;
use crate::liquidity_tournament::{
proof::LiquidityTournamentVoteProofPublic, ActionLiquidityTournamentVote,
LiquidityTournamentVoteBody, LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES,
};
use cnidarium_component::ActionHandler;

fn is_valid_denom(denom: &Denom) -> anyhow::Result<()> {
anyhow::ensure!(
Expand All @@ -26,6 +33,37 @@ fn is_valid_denom(denom: &Denom) -> anyhow::Result<()> {
Ok(())
}

// Check that the start position is early enough to vote in the current epoch.
async fn start_position_good_for_epoch(epoch: Epoch, start: Position) -> anyhow::Result<()> {
anyhow::ensure!(
epoch.index > u64::from(start.epoch()),
"position {start:?} is not before epoch {epoch:?}"
);
Ok(())
}

/// Fetch the unbonded equivalent of some purported delegation token.
///
/// Will fail if (either):
/// - the token is not for a known validator,
/// - the validator does not have any rate data.
async fn unbonded_amount(state: impl StateRead, value: Value) -> anyhow::Result<Amount> {
let validator = state.validator_by_delegation_asset(value.asset_id).await?;
let rate = state
.get_validator_rate(&validator)
.await?
.ok_or_else(|| anyhow!("{} has no rate data", &validator))?;
Ok(rate.unbonded_amount(value.amount))
}

// This isolates the logic for how we should handle out of bounds amounts.
fn voting_power(amount: Amount) -> u64 {
amount
.value()
.try_into()
.expect("someone acquired {amount:?} > u64::MAX worth of delegation tokens!")
}

#[async_trait]
impl ActionHandler for ActionLiquidityTournamentVote {
type CheckStatelessContext = TransactionContext;
Expand Down Expand Up @@ -70,7 +108,28 @@ impl ActionHandler for ActionLiquidityTournamentVote {
Ok(())
}

async fn check_and_execute<S: StateWrite>(&self, _state: S) -> anyhow::Result<()> {
todo!()
async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> anyhow::Result<()> {
// 1. Check that the nullifier hasn't appeared in this round. (TODO)
// 2. Check that the start position can vote in this round.
let current_epoch = state
.get_current_epoch()
.await
.expect("failed to fetch current epoch");
start_position_good_for_epoch(current_epoch, self.body.start_position).await?;
// 3. Tally.
let power = voting_power(unbonded_amount(&state, self.body.value).await?);
let incentivized = self
.body
.incentivized_id()
.ok_or_else(|| anyhow!("{:?} is not a base denom", self.body.incentivized))?;
state
.tally(
current_epoch.index,
incentivized,
power,
&self.body.rewards_recipient,
)
.await?;
Ok(())
}
}
1 change: 1 addition & 0 deletions crates/core/component/funding/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod state_key;
pub mod view;
use ::metrics::{gauge, histogram};
pub use metrics::register_metrics;
pub(crate) mod liquidity_tournament;

/* Component implementation */
use penumbra_sdk_asset::{Value, STAKING_TOKEN_ASSET_ID};
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod nullifier;
pub mod votes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use async_trait::async_trait;
use cnidarium::StateWrite;
use penumbra_sdk_asset::asset;
use penumbra_sdk_keys::Address;

use crate::component::state_key;

#[async_trait]
pub trait StateWriteExt: StateWrite {
// Keeping this as returning a result to not have to touch other code if it changes to return an error.
async fn tally(
&mut self,
epoch: u64,
asset: asset::Id,
power: u64,
voter: &Address,
) -> anyhow::Result<()> {
self.nonverifiable_put_raw(
state_key::lqt::v1::votes::receipt(epoch, asset, power, voter).to_vec(),
Vec::default(),
);
Ok(())
}
}

impl<T: StateWrite + ?Sized> StateWriteExt for T {}
1 change: 0 additions & 1 deletion crates/core/component/funding/src/component/state_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ pub mod lqt {
const POWER_LEN: usize = 8;
const RECEIPT_LEN: usize = PREFIX_LEN + ASSET_LEN + POWER_LEN + ADDRESS_LEN_BYTES;

#[allow(dead_code)] // TODO: remove once used
/// When present, indicates that an address voted for a particular asset, with a given power.
///
/// To get the values ordered by descending voting power, use [`prefix`];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use anyhow::{anyhow, Context};
use decaf377_rdsa::{Signature, SpendAuth, VerificationKey};
use penumbra_sdk_asset::{asset::Denom, balance, Value};
use penumbra_sdk_asset::{
asset::{self, Denom, REGISTRY},
balance, Value,
};
use penumbra_sdk_keys::Address;
use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType};
use penumbra_sdk_sct::Nullifier;
Expand Down Expand Up @@ -34,6 +37,17 @@ pub struct LiquidityTournamentVoteBody {
pub rk: VerificationKey<SpendAuth>,
}

impl LiquidityTournamentVoteBody {
/// Get the asset id that should be incentivized.
///
/// This will return None if the denom is not a base denom.
pub fn incentivized_id(&self) -> Option<asset::Id> {
REGISTRY
.parse_denom(&self.incentivized.denom)
.map(|x| x.id())
}
}

impl DomainType for LiquidityTournamentVoteBody {
type Proto = pb::LiquidityTournamentVoteBody;
}
Expand Down

0 comments on commit 6097a52

Please sign in to comment.