Skip to content

Commit

Permalink
Implement rust equivalent of LQT actions
Browse files Browse the repository at this point in the history
  • Loading branch information
cronokirby committed Jan 30, 2025
1 parent 6a209d4 commit e42c010
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/core/app/src/action_handler/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl AppActionHandler for Action {
Action::ActionDutchAuctionSchedule(action) => action.check_stateless(()).await,
Action::ActionDutchAuctionEnd(action) => action.check_stateless(()).await,
Action::ActionDutchAuctionWithdraw(action) => action.check_stateless(()).await,
Action::ActionLiquidityTournamentVote(_action) => todo!(),
}
}

Expand Down Expand Up @@ -97,6 +98,7 @@ impl AppActionHandler for Action {
Action::ActionDutchAuctionSchedule(action) => action.check_historical(state).await,
Action::ActionDutchAuctionEnd(action) => action.check_historical(state).await,
Action::ActionDutchAuctionWithdraw(action) => action.check_historical(state).await,
Action::ActionLiquidityTournamentVote(_action) => todo!(),
}
}

Expand Down Expand Up @@ -138,6 +140,7 @@ impl AppActionHandler for Action {
Action::ActionDutchAuctionSchedule(action) => action.check_and_execute(state).await,
Action::ActionDutchAuctionEnd(action) => action.check_and_execute(state).await,
Action::ActionDutchAuctionWithdraw(action) => action.check_and_execute(state).await,
Action::ActionLiquidityTournamentVote(_action) => todo!(),
}
}
}
1 change: 1 addition & 0 deletions crates/core/component/funding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ penumbra-sdk-sct = {workspace = true, default-features = false}
penumbra-sdk-shielded-pool = {workspace = true, default-features = false}
penumbra-sdk-stake = {workspace = true, default-features = false}
penumbra-sdk-tct = {workspace = true, default-features = false}
penumbra-sdk-txhash = {workspace = true, default-features = false}
serde = {workspace = true, features = ["derive"]}
tendermint = {workspace = true}
tracing = {workspace = true}
Expand Down
147 changes: 147 additions & 0 deletions crates/core/component/funding/src/liquidity_tournament/action/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use anyhow::{anyhow, Context};
use decaf377_rdsa::{Signature, SpendAuth, VerificationKey};
use penumbra_sdk_asset::{asset::Denom, balance, Value};
use penumbra_sdk_keys::Address;
use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType};
use penumbra_sdk_sct::Nullifier;
use penumbra_sdk_tct::Position;
use penumbra_sdk_txhash::{EffectHash, EffectingData};

use super::proof::LiquidityTournamentVoteProof;

/// The internal body of an LQT vote, containing the intended vote and other validity information.
///
/// c.f. [`penumbra_sdk_governance::delegator_vote::action::DelegatorVoteBody`], which is similar.
#[derive(Clone, Debug)]
pub struct LiquidityTournamentVoteBody {
/// Which asset is being incentivized.
///
/// We use the base denom to allow filtering particular asset sources (i.e. IBC transfers)a.
pub incentivized: Denom,
/// The address that will receive any rewards for voting.
pub rewards_recipient: Address,
/// The start position of the tournament.
///
/// This is included to allow stateless verification, but should match the epoch of the LQT.
pub start_position: Position,
/// The value being used to vote with.
///
/// This should be the delegation tokens for a specific validator.
pub value: Value,
/// The nullifier of the note being spent.
pub nullifier: Nullifier,
/// The key that must be used to vote.
pub rk: VerificationKey<SpendAuth>,
}

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

impl TryFrom<pb::LiquidityTournamentVoteBody> for LiquidityTournamentVoteBody {
type Error = anyhow::Error;

fn try_from(proto: pb::LiquidityTournamentVoteBody) -> Result<Self, Self::Error> {
Result::<_, Self::Error>::Ok(Self {
incentivized: proto
.incentivized
.ok_or_else(|| anyhow!("missing `incentivized`"))?
.try_into()?,
rewards_recipient: proto
.rewards_recipient
.ok_or_else(|| anyhow!("missing `rewards_recipient`"))?
.try_into()?,
start_position: proto.start_position.into(),
value: proto
.value
.ok_or_else(|| anyhow!("missing `value`"))?
.try_into()?,
nullifier: proto
.nullifier
.ok_or_else(|| anyhow!("missing `nullifier`"))?
.try_into()?,
rk: proto
.rk
.ok_or_else(|| anyhow!("missing `rk`"))?
.try_into()?,
})
.with_context(|| format!("while parsing {}", std::any::type_name::<Self>()))
}
}

impl From<LiquidityTournamentVoteBody> for pb::LiquidityTournamentVoteBody {
fn from(value: LiquidityTournamentVoteBody) -> Self {
Self {
incentivized: Some(value.incentivized.into()),
rewards_recipient: Some(value.rewards_recipient.into()),
start_position: value.start_position.into(),
value: Some(value.value.into()),
nullifier: Some(value.nullifier.into()),
rk: Some(value.rk.into()),
}
}
}

/// The action used to vote in the liquidity tournament.
///
/// This vote is towards a particular asset whose liquidity should be incentivized,
/// and is weighted by the amount of delegation tokens being expended.
#[derive(Clone, Debug)]
pub struct ActionLiquidityTournamentVote {
/// The actual body, containing the vote and other validity information.
pub body: LiquidityTournamentVoteBody,
/// An authorization over the body.
pub auth_sig: Signature<SpendAuth>,
/// A ZK proof tying in the private information for this action.
pub proof: LiquidityTournamentVoteProof,
}

impl DomainType for ActionLiquidityTournamentVote {
type Proto = pb::ActionLiquidityTournamentVote;
}

impl TryFrom<pb::ActionLiquidityTournamentVote> for ActionLiquidityTournamentVote {
type Error = anyhow::Error;

fn try_from(value: pb::ActionLiquidityTournamentVote) -> Result<Self, Self::Error> {
Result::<_, Self::Error>::Ok(Self {
body: value
.body
.ok_or_else(|| anyhow!("missing `body`"))?
.try_into()?,
auth_sig: value
.auth_sig
.ok_or_else(|| anyhow!("missing `auth_sig`"))?
.try_into()?,
proof: value
.proof
.ok_or_else(|| anyhow!("missing `proof`"))?
.try_into()?,
})
.with_context(|| format!("while parsing {}", std::any::type_name::<Self>()))
}
}

impl From<ActionLiquidityTournamentVote> for pb::ActionLiquidityTournamentVote {
fn from(value: ActionLiquidityTournamentVote) -> Self {
Self {
body: Some(value.body.into()),
auth_sig: Some(value.auth_sig.into()),
proof: Some(value.proof.into()),
}
}
}

impl EffectingData for ActionLiquidityTournamentVote {
fn effect_hash(&self) -> EffectHash {
EffectHash::from_proto_effecting_data(&self.to_proto())
}
}

impl ActionLiquidityTournamentVote {
/// This action doesn't actually produce or consume value.
pub fn balance_commmitment(&self) -> balance::Commitment {
// This will be the commitment to zero.
balance::Commitment::default()
}
}
8 changes: 8 additions & 0 deletions crates/core/component/funding/src/liquidity_tournament/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
mod action;
mod view;

pub mod proof;
pub use action::{ActionLiquidityTournamentVote, LiquidityTournamentVoteBody};
pub use view::ActionLiquidityTournamentVoteView;

/// The maximum number of allowable bytes in the denom string.
pub const LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES: usize = 256;
91 changes: 91 additions & 0 deletions crates/core/component/funding/src/liquidity_tournament/view/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use anyhow::{anyhow, Context};
use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType};
use penumbra_sdk_shielded_pool::NoteView;
use serde::{Deserialize, Serialize};

use super::ActionLiquidityTournamentVote;

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(
try_from = "pb::ActionLiquidityTournamentVoteView",
into = "pb::ActionLiquidityTournamentVoteView"
)]
#[allow(clippy::large_enum_variant)]
pub enum ActionLiquidityTournamentVoteView {
Visible {
vote: ActionLiquidityTournamentVote,
note: NoteView,
},
Opaque {
vote: ActionLiquidityTournamentVote,
},
}

impl DomainType for ActionLiquidityTournamentVoteView {
type Proto = pb::ActionLiquidityTournamentVoteView;
}

impl TryFrom<pb::ActionLiquidityTournamentVoteView> for ActionLiquidityTournamentVoteView {
type Error = anyhow::Error;

fn try_from(value: pb::ActionLiquidityTournamentVoteView) -> Result<Self, Self::Error> {
let out: Result<Self, Self::Error> = match value
.liquidity_tournament_vote
.ok_or_else(|| anyhow::anyhow!("missing `liquidity_tournament_vote`"))?
{
pb::action_liquidity_tournament_vote_view::LiquidityTournamentVote::Visible(
visible,
) => Ok(Self::Visible {
vote: visible
.vote
.ok_or_else(|| anyhow!("missing `visible.vote`"))?
.try_into()?,
note: visible
.note
.ok_or_else(|| anyhow!("missing `visible.note`"))?
.try_into()?,
}),
pb::action_liquidity_tournament_vote_view::LiquidityTournamentVote::Opaque(opaque) => {
Ok(Self::Opaque {
vote: opaque
.vote
.ok_or_else(|| anyhow!("missing `opaque.vote`"))?
.try_into()?,
})
}
};
out.with_context(|| format!("while parsing `{}`", std::any::type_name::<Self>()))
}
}

impl From<ActionLiquidityTournamentVoteView> for pb::ActionLiquidityTournamentVoteView {
fn from(value: ActionLiquidityTournamentVoteView) -> Self {
use pb::action_liquidity_tournament_vote_view as pblqtvv;
match value {
ActionLiquidityTournamentVoteView::Visible { vote, note } => Self {
liquidity_tournament_vote: Some(pblqtvv::LiquidityTournamentVote::Visible(
pblqtvv::Visible {
vote: Some(vote.into()),
note: Some(note.into()),
},
)),
},
ActionLiquidityTournamentVoteView::Opaque { vote } => Self {
liquidity_tournament_vote: Some(pblqtvv::LiquidityTournamentVote::Opaque(
pblqtvv::Opaque {
vote: Some(vote.into()),
},
)),
},
}
}
}

impl From<ActionLiquidityTournamentVoteView> for ActionLiquidityTournamentVote {
fn from(value: ActionLiquidityTournamentVoteView) -> Self {
match value {
ActionLiquidityTournamentVoteView::Visible { vote, .. } => vote,
ActionLiquidityTournamentVoteView::Opaque { vote } => vote,
}
}
}
2 changes: 2 additions & 0 deletions crates/core/transaction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ parallel = [
"penumbra-sdk-shielded-pool/parallel",
"penumbra-sdk-auction/parallel",
"penumbra-sdk-dex/parallel",
"penumbra-sdk-funding/parallel",
"penumbra-sdk-governance/parallel",
"penumbra-sdk-stake/parallel",
]
Expand Down Expand Up @@ -45,6 +46,7 @@ penumbra-sdk-community-pool = {workspace = true, default-features = false}
penumbra-sdk-auction = {workspace = true, default-features = false}
penumbra-sdk-dex = {workspace = true, default-features = false}
penumbra-sdk-fee = {workspace = true, default-features = false}
penumbra-sdk-funding = {workspace = true, default-features = false}
penumbra-sdk-governance = {workspace = true, default-features = false}
penumbra-sdk-ibc = {workspace = true, default-features = false}
penumbra-sdk-keys = {workspace = true, default-features = false}
Expand Down
18 changes: 18 additions & 0 deletions crates/core/transaction/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub enum Action {
ActionDutchAuctionSchedule(ActionDutchAuctionSchedule),
ActionDutchAuctionEnd(ActionDutchAuctionEnd),
ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw),
ActionLiquidityTournamentVote(
penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVote,
),
}

impl EffectingData for Action {
Expand Down Expand Up @@ -74,6 +77,7 @@ impl EffectingData for Action {
Action::ActionDutchAuctionSchedule(a) => a.effect_hash(),
Action::ActionDutchAuctionEnd(a) => a.effect_hash(),
Action::ActionDutchAuctionWithdraw(a) => a.effect_hash(),
Action::ActionLiquidityTournamentVote(a) => a.effect_hash(),
}
}
}
Expand Down Expand Up @@ -125,6 +129,9 @@ impl Action {
Action::ActionDutchAuctionWithdraw(_) => {
tracing::info_span!("ActionDutchAuctionWithdraw", ?idx)
}
Action::ActionLiquidityTournamentVote(_) => {
tracing::info_span!("ActionLiquidityTournamentVote", ?idx)
}
}
}

Expand Down Expand Up @@ -155,6 +162,7 @@ impl Action {
Action::ActionDutchAuctionSchedule(_) => 53,
Action::ActionDutchAuctionEnd(_) => 54,
Action::ActionDutchAuctionWithdraw(_) => 55,
Action::ActionLiquidityTournamentVote(_) => 70,
}
}
}
Expand Down Expand Up @@ -188,6 +196,7 @@ impl IsAction for Action {
Action::ActionDutchAuctionSchedule(action) => action.balance_commitment(),
Action::ActionDutchAuctionEnd(action) => action.balance_commitment(),
Action::ActionDutchAuctionWithdraw(action) => action.balance_commitment(),
Action::ActionLiquidityTournamentVote(action) => action.balance_commmitment(),
}
}

Expand Down Expand Up @@ -217,6 +226,7 @@ impl IsAction for Action {
Action::ActionDutchAuctionSchedule(x) => x.view_from_perspective(txp),
Action::ActionDutchAuctionEnd(x) => x.view_from_perspective(txp),
Action::ActionDutchAuctionWithdraw(x) => x.view_from_perspective(txp),
Action::ActionLiquidityTournamentVote(x) => x.view_from_perspective(txp),
}
}
}
Expand Down Expand Up @@ -300,6 +310,11 @@ impl From<Action> for pb::Action {
Action::ActionDutchAuctionWithdraw(inner) => pb::Action {
action: Some(pb::action::Action::ActionDutchAuctionWithdraw(inner.into())),
},
Action::ActionLiquidityTournamentVote(inner) => pb::Action {
action: Some(pb::action::Action::ActionLiquidityTournamentVote(
inner.into(),
)),
},
}
}
}
Expand Down Expand Up @@ -374,6 +389,9 @@ impl TryFrom<pb::Action> for Action {
pb::action::Action::ActionDutchAuctionWithdraw(inner) => {
Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?))
}
pb::action::Action::ActionLiquidityTournamentVote(inner) => {
Ok(Action::ActionLiquidityTournamentVote(inner.try_into()?))
}
}
}
}
Loading

0 comments on commit e42c010

Please sign in to comment.