diff --git a/Cargo.lock b/Cargo.lock index ff9ae8718c..cad652cb6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5208,19 +5208,28 @@ name = "penumbra-sdk-funding" version = "1.0.0" dependencies = [ "anyhow", + "ark-groth16", "async-trait", + "base64 0.21.7", "cnidarium", "cnidarium-component", + "decaf377", + "decaf377-rdsa", "futures", "metrics 0.24.1", "penumbra-sdk-asset", "penumbra-sdk-community-pool", "penumbra-sdk-distributions", + "penumbra-sdk-governance", + "penumbra-sdk-keys", "penumbra-sdk-num", + "penumbra-sdk-proof-params", "penumbra-sdk-proto", "penumbra-sdk-sct", "penumbra-sdk-shielded-pool", "penumbra-sdk-stake", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "serde", "tendermint 0.40.1", "tracing", @@ -5891,6 +5900,7 @@ dependencies = [ "penumbra-sdk-community-pool", "penumbra-sdk-dex", "penumbra-sdk-fee", + "penumbra-sdk-funding", "penumbra-sdk-governance", "penumbra-sdk-ibc", "penumbra-sdk-keys", diff --git a/crates/bin/pcli/src/transaction_view_ext.rs b/crates/bin/pcli/src/transaction_view_ext.rs index a1b925a2e9..d17e31f893 100644 --- a/crates/bin/pcli/src/transaction_view_ext.rs +++ b/crates/bin/pcli/src/transaction_view_ext.rs @@ -458,6 +458,7 @@ impl TransactionViewExt for TransactionView { action = format!("{} -> [{}]", x.action.auction_id, inside); ["Dutch Auction Withdraw", &action] } + penumbra_sdk_transaction::ActionView::ActionLiquidityTournamentVote(_) => todo!(), }; actions_table.add_row(row); diff --git a/crates/core/app/src/action_handler/actions.rs b/crates/core/app/src/action_handler/actions.rs index 402932b12f..93f33355e0 100644 --- a/crates/core/app/src/action_handler/actions.rs +++ b/crates/core/app/src/action_handler/actions.rs @@ -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!(), } } @@ -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!(), } } @@ -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!(), } } } diff --git a/crates/core/asset/src/asset/denom.rs b/crates/core/asset/src/asset/denom.rs index fd2f77fd8d..972329404a 100644 --- a/crates/core/asset/src/asset/denom.rs +++ b/crates/core/asset/src/asset/denom.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// /// Each denomination has a unique [`asset::Id`] and base unit, and may also /// have other display units. -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(try_from = "pb::Denom", into = "pb::Denom")] pub struct Denom { pub denom: String, diff --git a/crates/core/component/funding/Cargo.toml b/crates/core/component/funding/Cargo.toml index 7f6ed07d9d..fec6e3d9d7 100644 --- a/crates/core/component/funding/Cargo.toml +++ b/crates/core/component/funding/Cargo.toml @@ -21,23 +21,40 @@ component = [ "futures" ] default = ["component"] +parallel = [ + "ark-groth16/parallel", + "decaf377/parallel", + "decaf377-rdsa/parallel", + "penumbra-sdk-tct/parallel", + "penumbra-sdk-shielded-pool/parallel", + "penumbra-sdk-governance/parallel" +] docsrs = [] [dependencies] anyhow = {workspace = true} +ark-groth16 = {workspace = true, default-features = false} async-trait = {workspace = true} +base64 = {workspace = true} cnidarium = {workspace = true, optional = true, default-features = true} cnidarium-component = {workspace = true, optional = true, default-features = true} +decaf377 = {workspace = true} +decaf377-rdsa = {workspace = true} futures = {workspace = true, optional = true} metrics = {workspace = true, optional = true} penumbra-sdk-asset = {workspace = true, default-features = true} penumbra-sdk-community-pool = {workspace = true, default-features = false} penumbra-sdk-distributions = {workspace = true, default-features = false} +penumbra-sdk-governance = {workspace = true, default-features = false} +penumbra-sdk-keys = {workspace = true, default-features = false} +penumbra-sdk-num = {workspace = true, default-features = false} +penumbra-sdk-proof-params = {workspace = true, default-features = false} penumbra-sdk-proto = {workspace = true, default-features = false} penumbra-sdk-sct = {workspace = true, default-features = false} -penumbra-sdk-num = {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} diff --git a/crates/core/component/funding/src/action_handler/liquidity_tournament/mod.rs b/crates/core/component/funding/src/action_handler/liquidity_tournament/mod.rs new file mode 100644 index 0000000000..0e5bb91054 --- /dev/null +++ b/crates/core/component/funding/src/action_handler/liquidity_tournament/mod.rs @@ -0,0 +1,76 @@ +use anyhow::Context as _; +use async_trait::async_trait; +use cnidarium::StateWrite; +use penumbra_sdk_asset::asset::Denom; +use penumbra_sdk_proof_params::DELEGATOR_VOTE_PROOF_VERIFICATION_KEY; +use penumbra_sdk_txhash::TransactionContext; + +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!( + denom.denom.len() <= LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES, + "denom {} is not <= (MAX OF) {}", + &denom.denom, + LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES + ); + anyhow::ensure!( + denom.denom.starts_with("transfer/"), + "denom {} is not an IBC transfer asset", + &denom.denom + ); + Ok(()) +} + +#[async_trait] +impl ActionHandler for ActionLiquidityTournamentVote { + type CheckStatelessContext = TransactionContext; + + async fn check_stateless(&self, context: TransactionContext) -> anyhow::Result<()> { + let Self { + auth_sig, + proof, + body: + LiquidityTournamentVoteBody { + start_position, + nullifier, + rk, + value, + incentivized, + .. + }, + } = self; + // 1. Is it ok to vote on this denom? + is_valid_denom(incentivized)?; + // 2. Check spend auth signature using provided spend auth key. + rk.verify(context.effect_hash.as_ref(), auth_sig) + .with_context(|| { + format!( + "{} auth signature failed to verify", + std::any::type_name::() + ) + })?; + + // 3. Verify the proof against the provided anchor and start position: + let public = LiquidityTournamentVoteProofPublic { + anchor: context.anchor, + value: *value, + nullifier: *nullifier, + rk: *rk, + start_position: *start_position, + }; + proof + .verify(&DELEGATOR_VOTE_PROOF_VERIFICATION_KEY, public) + .context("a LiquidityTournamentVote proof did not verify")?; + + Ok(()) + } + + async fn check_and_execute(&self, _state: S) -> anyhow::Result<()> { + todo!() + } +} diff --git a/crates/core/component/funding/src/action_handler/mod.rs b/crates/core/component/funding/src/action_handler/mod.rs new file mode 100644 index 0000000000..0a22bdcd17 --- /dev/null +++ b/crates/core/component/funding/src/action_handler/mod.rs @@ -0,0 +1 @@ +pub mod liquidity_tournament; diff --git a/crates/core/component/funding/src/lib.rs b/crates/core/component/funding/src/lib.rs index f772fb2bd0..6e11248cf4 100644 --- a/crates/core/component/funding/src/lib.rs +++ b/crates/core/component/funding/src/lib.rs @@ -1,9 +1,12 @@ #![deny(clippy::unwrap_used)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "component")] +pub mod action_handler; +#[cfg(feature = "component")] pub mod component; pub mod event; pub mod genesis; +pub mod liquidity_tournament; pub mod params; pub use params::FundingParameters; diff --git a/crates/core/component/funding/src/liquidity_tournament/action/mod.rs b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs new file mode 100644 index 0000000000..86bf11b355 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs @@ -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, +} + +impl DomainType for LiquidityTournamentVoteBody { + type Proto = pb::LiquidityTournamentVoteBody; +} + +impl TryFrom for LiquidityTournamentVoteBody { + type Error = anyhow::Error; + + fn try_from(proto: pb::LiquidityTournamentVoteBody) -> Result { + 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::())) + } +} + +impl From 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, + /// A ZK proof tying in the private information for this action. + pub proof: LiquidityTournamentVoteProof, +} + +impl DomainType for ActionLiquidityTournamentVote { + type Proto = pb::ActionLiquidityTournamentVote; +} + +impl TryFrom for ActionLiquidityTournamentVote { + type Error = anyhow::Error; + + fn try_from(value: pb::ActionLiquidityTournamentVote) -> Result { + 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::())) + } +} + +impl From 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_commitment(&self) -> balance::Commitment { + // This will be the commitment to zero. + balance::Commitment::default() + } +} diff --git a/crates/core/component/funding/src/liquidity_tournament/mod.rs b/crates/core/component/funding/src/liquidity_tournament/mod.rs new file mode 100644 index 0000000000..5a660f69a1 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/mod.rs @@ -0,0 +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; diff --git a/crates/core/component/funding/src/liquidity_tournament/proof/mod.rs b/crates/core/component/funding/src/liquidity_tournament/proof/mod.rs new file mode 100644 index 0000000000..f60125b235 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/proof/mod.rs @@ -0,0 +1,122 @@ +use ark_groth16::{PreparedVerifyingKey, ProvingKey}; +use base64::prelude::*; +use decaf377::{Bls12_377, Fq}; +use decaf377_rdsa::{Fr, SpendAuth, VerificationKey}; +use penumbra_sdk_asset::Value; +use penumbra_sdk_governance::{ + DelegatorVoteProof, DelegatorVoteProofPrivate, DelegatorVoteProofPublic, +}; +use penumbra_sdk_keys::keys::NullifierKey; +use penumbra_sdk_proof_params::VerifyingKeyExt as _; +use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType}; +use penumbra_sdk_sct::Nullifier; +use penumbra_sdk_shielded_pool::Note; +use penumbra_sdk_tct as tct; + +#[derive(Clone, Debug)] +pub struct LiquidityTournamentVoteProofPublic { + /// the merkle root of the state commitment tree. + pub anchor: tct::Root, + /// The value of the note being used to vote. + pub value: Value, + /// nullifier of the note to be spent. + pub nullifier: Nullifier, + /// the randomized verification spend key. + pub rk: VerificationKey, + /// the start position of the proposal being voted on. + pub start_position: tct::Position, +} + +impl LiquidityTournamentVoteProofPublic { + fn to_delegator_vote(self) -> DelegatorVoteProofPublic { + DelegatorVoteProofPublic { + anchor: self.anchor, + balance_commitment: self.value.commit(Default::default()), + nullifier: self.nullifier, + rk: self.rk, + start_position: self.start_position, + } + } +} + +#[derive(Clone, Debug)] +pub struct LiquidityTournamentVoteProofPrivate { + /// Inclusion proof for the note commitment. + pub state_commitment_proof: tct::Proof, + /// The note being spent. + pub note: Note, + /// The randomizer used for generating the randomized spend auth key. + pub spend_auth_randomizer: Fr, + /// The spend authorization key. + pub ak: VerificationKey, + /// The nullifier deriving key. + pub nk: NullifierKey, +} + +impl LiquidityTournamentVoteProofPrivate { + fn to_delegator_vote(self) -> DelegatorVoteProofPrivate { + DelegatorVoteProofPrivate { + state_commitment_proof: self.state_commitment_proof, + note: self.note, + v_blinding: Default::default(), + spend_auth_randomizer: self.spend_auth_randomizer, + ak: self.ak, + nk: self.nk, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct LiquidityTournamentVoteProof(DelegatorVoteProof); + +impl LiquidityTournamentVoteProof { + #![allow(clippy::too_many_arguments)] + /// Generate a `LiquidityTournamentVoteProof` given the proving key, + // public inputs, witness data, and the necessary randomness. + pub fn prove( + blinding_r: Fq, + blinding_s: Fq, + pk: &ProvingKey, + public: LiquidityTournamentVoteProofPublic, + private: LiquidityTournamentVoteProofPrivate, + ) -> anyhow::Result { + let proof = DelegatorVoteProof::prove( + blinding_r, + blinding_s, + pk, + public.to_delegator_vote(), + private.to_delegator_vote(), + )?; + Ok(Self(proof)) + } + + /// Called to verify the proof using the provided public inputs. + #[tracing::instrument(level="debug", skip(self, vk), fields(self = ?BASE64_STANDARD.encode(self.clone().encode_to_vec()), vk = ?vk.debug_id()))] + pub fn verify( + &self, + vk: &PreparedVerifyingKey, + public: LiquidityTournamentVoteProofPublic, + ) -> anyhow::Result<()> { + Ok(self.0.verify(vk, public.to_delegator_vote())?) + } +} + +impl DomainType for LiquidityTournamentVoteProof { + type Proto = pb::ZkLiquidityTournamentVoteProof; +} + +impl From for pb::ZkLiquidityTournamentVoteProof { + fn from(proof: LiquidityTournamentVoteProof) -> Self { + pb::ZkLiquidityTournamentVoteProof { + inner: proof.0.to_vec(), + } + } +} + +impl TryFrom for LiquidityTournamentVoteProof { + type Error = anyhow::Error; + + fn try_from(proto: pb::ZkLiquidityTournamentVoteProof) -> Result { + Ok(LiquidityTournamentVoteProof(proto.inner[..].try_into()?)) + } +} diff --git a/crates/core/component/funding/src/liquidity_tournament/view/mod.rs b/crates/core/component/funding/src/liquidity_tournament/view/mod.rs new file mode 100644 index 0000000000..0389535fc7 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/view/mod.rs @@ -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 for ActionLiquidityTournamentVoteView { + type Error = anyhow::Error; + + fn try_from(value: pb::ActionLiquidityTournamentVoteView) -> Result { + let out: Result = 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::())) + } +} + +impl From 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 for ActionLiquidityTournamentVote { + fn from(value: ActionLiquidityTournamentVoteView) -> Self { + match value { + ActionLiquidityTournamentVoteView::Visible { vote, .. } => vote, + ActionLiquidityTournamentVoteView::Opaque { vote } => vote, + } + } +} diff --git a/crates/core/component/governance/src/delegator_vote/proof.rs b/crates/core/component/governance/src/delegator_vote/proof.rs index 9a66e36e99..c3d6205dc6 100644 --- a/crates/core/component/governance/src/delegator_vote/proof.rs +++ b/crates/core/component/governance/src/delegator_vote/proof.rs @@ -394,6 +394,10 @@ impl DelegatorVoteProof { .then_some(()) .ok_or(VerificationError::InvalidProof) } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } } impl DomainType for DelegatorVoteProof { @@ -416,6 +420,14 @@ impl TryFrom for DelegatorVoteProof { } } +impl TryFrom<&[u8]> for DelegatorVoteProof { + type Error = anyhow::Error; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) + } +} + #[cfg(test)] mod tests { diff --git a/crates/core/transaction/Cargo.toml b/crates/core/transaction/Cargo.toml index 2b43b075f5..5a24e0fb09 100644 --- a/crates/core/transaction/Cargo.toml +++ b/crates/core/transaction/Cargo.toml @@ -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", ] @@ -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} diff --git a/crates/core/transaction/src/action.rs b/crates/core/transaction/src/action.rs index 68a5705e8f..63ecfd17bd 100644 --- a/crates/core/transaction/src/action.rs +++ b/crates/core/transaction/src/action.rs @@ -45,6 +45,9 @@ pub enum Action { ActionDutchAuctionSchedule(ActionDutchAuctionSchedule), ActionDutchAuctionEnd(ActionDutchAuctionEnd), ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw), + ActionLiquidityTournamentVote( + penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVote, + ), } impl EffectingData for Action { @@ -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(), } } } @@ -125,6 +129,9 @@ impl Action { Action::ActionDutchAuctionWithdraw(_) => { tracing::info_span!("ActionDutchAuctionWithdraw", ?idx) } + Action::ActionLiquidityTournamentVote(_) => { + tracing::info_span!("ActionLiquidityTournamentVote", ?idx) + } } } @@ -155,6 +162,7 @@ impl Action { Action::ActionDutchAuctionSchedule(_) => 53, Action::ActionDutchAuctionEnd(_) => 54, Action::ActionDutchAuctionWithdraw(_) => 55, + Action::ActionLiquidityTournamentVote(_) => 70, } } } @@ -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_commitment(), } } @@ -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), } } } @@ -300,6 +310,11 @@ impl From 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(), + )), + }, } } } @@ -374,6 +389,9 @@ impl TryFrom for Action { pb::action::Action::ActionDutchAuctionWithdraw(inner) => { Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?)) } + pb::action::Action::ActionLiquidityTournamentVote(inner) => { + Ok(Action::ActionLiquidityTournamentVote(inner.try_into()?)) + } } } } diff --git a/crates/core/transaction/src/gas.rs b/crates/core/transaction/src/gas.rs index a2559ae5e1..a4f3afd192 100644 --- a/crates/core/transaction/src/gas.rs +++ b/crates/core/transaction/src/gas.rs @@ -4,6 +4,9 @@ use penumbra_sdk_auction::auction::dutch::actions::{ use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}; use penumbra_sdk_dex::{PositionClose, PositionOpen, PositionWithdraw, Swap, SwapClaim}; use penumbra_sdk_fee::Gas; +use penumbra_sdk_funding::liquidity_tournament::{ + ActionLiquidityTournamentVote, LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES, +}; use penumbra_sdk_ibc::IbcRelay; use penumbra_sdk_shielded_pool::{Ics20Withdrawal, Output, Spend}; use penumbra_sdk_stake::{ @@ -288,6 +291,35 @@ fn dutch_auction_withdraw_gas_cost() -> Gas { } } +fn liquidity_tournament_vote_gas_cost() -> Gas { + Gas { + block_space: + // LiquidityTournamentVoteBody body = 1; + ( + // asset.v1.Denom incentivized = 1; (restricted to MAX bytes) + LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES as u64 + // keys.v1.Address rewards_recipient = 2; (the larger of the two exclusive fields) + + 223 + // uint64 start_position = 3; + + 8 + // asset.v1.Value value = 4; + + 48 + // sct.v1.Nullifier nullifier = 5; + + 32 + // crypto.decaf377_rdsa.v1.SpendVerificationKey rk = 6; + + 32 + ) + // crypto.decaf377_rdsa.v1.SpendAuthSignature auth_sig = 2; + + 64 + // ZKLiquidityTournamentVoteProof proof = 3; + + ZKPROOF_SIZE, + // For the rest, c.f. `delegator_vote_gas_cost` + compact_block_space: 0, + verification: 1000, + execution: 10, + } +} + impl GasCost for Transaction { fn gas_cost(&self) -> Gas { self.actions().map(GasCost::gas_cost).sum() @@ -376,6 +408,9 @@ impl GasCost for Action { Action::ActionDutchAuctionWithdraw(action_dutch_auction_withdraw) => { action_dutch_auction_withdraw.gas_cost() } + Action::ActionLiquidityTournamentVote(action_liquidity_tournament_vote) => { + action_liquidity_tournament_vote.gas_cost() + } } } } @@ -651,3 +686,9 @@ impl GasCost for ActionDutchAuctionWithdraw { dutch_auction_withdraw_gas_cost() } } + +impl GasCost for ActionLiquidityTournamentVote { + fn gas_cost(&self) -> Gas { + liquidity_tournament_vote_gas_cost() + } +} diff --git a/crates/core/transaction/src/is_action.rs b/crates/core/transaction/src/is_action.rs index b985f56e0a..141ea5f934 100644 --- a/crates/core/transaction/src/is_action.rs +++ b/crates/core/transaction/src/is_action.rs @@ -14,6 +14,9 @@ use penumbra_sdk_dex::{ swap::{Swap, SwapCiphertext, SwapView}, swap_claim::{SwapClaim, SwapClaimView}, }; +use penumbra_sdk_funding::liquidity_tournament::{ + ActionLiquidityTournamentVote, ActionLiquidityTournamentVoteView, +}; use penumbra_sdk_governance::{ DelegatorVote, DelegatorVoteView, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, VotingReceiptToken, @@ -498,3 +501,23 @@ impl IsAction for ActionDutchAuctionWithdraw { ActionView::ActionDutchAuctionWithdraw(view) } } + +impl IsAction for ActionLiquidityTournamentVote { + fn balance_commitment(&self) -> balance::Commitment { + self.balance_commitment() + } + + fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView { + let lqt_vote_view = match txp.spend_nullifiers.get(&self.body.nullifier) { + Some(note) => ActionLiquidityTournamentVoteView::Visible { + vote: self.to_owned(), + note: txp.view_note(note.to_owned()), + }, + None => ActionLiquidityTournamentVoteView::Opaque { + vote: self.to_owned(), + }, + }; + + ActionView::ActionLiquidityTournamentVote(lqt_vote_view) + } +} diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index 1307a1a428..d7154e9c72 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -272,10 +272,11 @@ impl Transaction { | Action::Ics20Withdrawal(_) | Action::CommunityPoolSpend(_) | Action::CommunityPoolOutput(_) - | Action::CommunityPoolDeposit(_) => {} - Action::ActionDutchAuctionSchedule(_) => {} - Action::ActionDutchAuctionEnd(_) => {} - Action::ActionDutchAuctionWithdraw(_) => {} + | Action::CommunityPoolDeposit(_) + | Action::ActionDutchAuctionSchedule(_) + | Action::ActionDutchAuctionEnd(_) + | Action::ActionDutchAuctionWithdraw(_) + | Action::ActionLiquidityTournamentVote(_) => {} } } diff --git a/crates/core/transaction/src/view/action_view.rs b/crates/core/transaction/src/view/action_view.rs index d06f98d2ed..17a3f45bc0 100644 --- a/crates/core/transaction/src/view/action_view.rs +++ b/crates/core/transaction/src/view/action_view.rs @@ -8,6 +8,7 @@ use penumbra_sdk_dex::{ swap::SwapView, swap_claim::SwapClaimView, }; +use penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVoteView; use penumbra_sdk_governance::{ ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, }; @@ -53,6 +54,7 @@ pub enum ActionView { ActionDutchAuctionSchedule(ActionDutchAuctionScheduleView), ActionDutchAuctionEnd(ActionDutchAuctionEnd), ActionDutchAuctionWithdraw(ActionDutchAuctionWithdrawView), + ActionLiquidityTournamentVote(ActionLiquidityTournamentVoteView), } impl DomainType for ActionView { @@ -102,6 +104,9 @@ impl TryFrom for ActionView { AV::ActionDutchAuctionWithdraw(x) => { ActionView::ActionDutchAuctionWithdraw(x.try_into()?) } + AV::ActionLiquidityTournamentVote(x) => { + ActionView::ActionLiquidityTournamentVote(x.try_into()?) + } }, ) } @@ -140,6 +145,9 @@ impl From for pbt::ActionView { ActionView::ActionDutchAuctionWithdraw(x) => { AV::ActionDutchAuctionWithdraw(x.into()) } + ActionView::ActionLiquidityTournamentVote(x) => { + AV::ActionLiquidityTournamentVote(x.into()) + } }), } } @@ -176,6 +184,9 @@ impl From for Action { ActionView::ActionDutchAuctionWithdraw(x) => { Action::ActionDutchAuctionWithdraw(x.into()) } + ActionView::ActionLiquidityTournamentVote(x) => { + Action::ActionLiquidityTournamentVote(x.into()) + } } } } diff --git a/crates/proto/src/gen/penumbra.core.component.funding.v1.rs b/crates/proto/src/gen/penumbra.core.component.funding.v1.rs index c5d098a8bd..31fca58c81 100644 --- a/crates/proto/src/gen/penumbra.core.component.funding.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.funding.v1.rs @@ -98,3 +98,150 @@ impl ::prost::Name for EventFundingStreamReward { "/penumbra.core.component.funding.v1.EventFundingStreamReward".into() } } +/// An action for voting in a liquidity tournament. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActionLiquidityTournamentVote { + /// The effectful data signalling user intent, and the validity of this intent. + #[prost(message, optional, tag = "1")] + pub body: ::core::option::Option, + /// An authorization from the user over this body. + #[prost(message, optional, tag = "2")] + pub auth_sig: ::core::option::Option< + super::super::super::super::crypto::decaf377_rdsa::v1::SpendAuthSignature, + >, + /// A ZK proof that it was correctly constructed from private user state. + #[prost(message, optional, tag = "3")] + pub proof: ::core::option::Option, +} +impl ::prost::Name for ActionLiquidityTournamentVote { + const NAME: &'static str = "ActionLiquidityTournamentVote"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.ActionLiquidityTournamentVote".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.ActionLiquidityTournamentVote".into() + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LiquidityTournamentVoteBody { + /// Which asset should be incentivized. + #[prost(message, optional, tag = "1")] + pub incentivized: ::core::option::Option, + /// Where to send any rewards for participating in the tournament. + #[prost(message, optional, tag = "2")] + pub rewards_recipient: ::core::option::Option< + super::super::super::keys::v1::Address, + >, + /// The start position of the tournament + #[prost(uint64, tag = "3")] + pub start_position: u64, + /// The value being voted with. + /// + /// This should be some amount of a validator's delegation token. + #[prost(message, optional, tag = "4")] + pub value: ::core::option::Option, + /// The nullifier associated with the note being spent. + #[prost(message, optional, tag = "5")] + pub nullifier: ::core::option::Option, + /// A randomized verification key with which to check the auth signature. + #[prost(message, optional, tag = "6")] + pub rk: ::core::option::Option< + super::super::super::super::crypto::decaf377_rdsa::v1::SpendVerificationKey, + >, +} +impl ::prost::Name for LiquidityTournamentVoteBody { + const NAME: &'static str = "LiquidityTournamentVoteBody"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.LiquidityTournamentVoteBody".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.LiquidityTournamentVoteBody".into() + } +} +/// A proof of the validity of a liquidity vote, wrt private state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ZkLiquidityTournamentVoteProof { + #[prost(bytes = "vec", tag = "1")] + pub inner: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for ZkLiquidityTournamentVoteProof { + const NAME: &'static str = "ZKLiquidityTournamentVoteProof"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.ZKLiquidityTournamentVoteProof".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.ZKLiquidityTournamentVoteProof".into() + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActionLiquidityTournamentVoteView { + #[prost( + oneof = "action_liquidity_tournament_vote_view::LiquidityTournamentVote", + tags = "1, 2" + )] + pub liquidity_tournament_vote: ::core::option::Option< + action_liquidity_tournament_vote_view::LiquidityTournamentVote, + >, +} +/// Nested message and enum types in `ActionLiquidityTournamentVoteView`. +pub mod action_liquidity_tournament_vote_view { + /// If we initiated the vote, we should know the note that we spent. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Visible { + #[prost(message, optional, tag = "1")] + pub vote: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub note: ::core::option::Option< + super::super::super::shielded_pool::v1::NoteView, + >, + } + impl ::prost::Name for Visible { + const NAME: &'static str = "Visible"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Visible" + .into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Visible" + .into() + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Opaque { + #[prost(message, optional, tag = "1")] + pub vote: ::core::option::Option, + } + impl ::prost::Name for Opaque { + const NAME: &'static str = "Opaque"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Opaque" + .into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Opaque" + .into() + } + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum LiquidityTournamentVote { + #[prost(message, tag = "1")] + Visible(Visible), + #[prost(message, tag = "2")] + Opaque(Opaque), + } +} +impl ::prost::Name for ActionLiquidityTournamentVoteView { + const NAME: &'static str = "ActionLiquidityTournamentVoteView"; + const PACKAGE: &'static str = "penumbra.core.component.funding.v1"; + fn full_name() -> ::prost::alloc::string::String { + "penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView".into() + } + fn type_url() -> ::prost::alloc::string::String { + "/penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView".into() + } +} diff --git a/crates/proto/src/gen/penumbra.core.component.funding.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.funding.v1.serde.rs index ec4c17bc14..8b8b19991b 100644 --- a/crates/proto/src/gen/penumbra.core.component.funding.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.funding.v1.serde.rs @@ -1,3 +1,453 @@ +impl serde::Serialize for ActionLiquidityTournamentVote { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.body.is_some() { + len += 1; + } + if self.auth_sig.is_some() { + len += 1; + } + if self.proof.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVote", len)?; + if let Some(v) = self.body.as_ref() { + struct_ser.serialize_field("body", v)?; + } + if let Some(v) = self.auth_sig.as_ref() { + struct_ser.serialize_field("authSig", v)?; + } + if let Some(v) = self.proof.as_ref() { + struct_ser.serialize_field("proof", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ActionLiquidityTournamentVote { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "body", + "auth_sig", + "authSig", + "proof", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Body, + AuthSig, + Proof, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "body" => Ok(GeneratedField::Body), + "authSig" | "auth_sig" => Ok(GeneratedField::AuthSig), + "proof" => Ok(GeneratedField::Proof), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ActionLiquidityTournamentVote; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.ActionLiquidityTournamentVote") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut body__ = None; + let mut auth_sig__ = None; + let mut proof__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Body => { + if body__.is_some() { + return Err(serde::de::Error::duplicate_field("body")); + } + body__ = map_.next_value()?; + } + GeneratedField::AuthSig => { + if auth_sig__.is_some() { + return Err(serde::de::Error::duplicate_field("authSig")); + } + auth_sig__ = map_.next_value()?; + } + GeneratedField::Proof => { + if proof__.is_some() { + return Err(serde::de::Error::duplicate_field("proof")); + } + proof__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ActionLiquidityTournamentVote { + body: body__, + auth_sig: auth_sig__, + proof: proof__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVote", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ActionLiquidityTournamentVoteView { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.liquidity_tournament_vote.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView", len)?; + if let Some(v) = self.liquidity_tournament_vote.as_ref() { + match v { + action_liquidity_tournament_vote_view::LiquidityTournamentVote::Visible(v) => { + struct_ser.serialize_field("visible", v)?; + } + action_liquidity_tournament_vote_view::LiquidityTournamentVote::Opaque(v) => { + struct_ser.serialize_field("opaque", v)?; + } + } + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ActionLiquidityTournamentVoteView { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "visible", + "opaque", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Visible, + Opaque, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "visible" => Ok(GeneratedField::Visible), + "opaque" => Ok(GeneratedField::Opaque), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ActionLiquidityTournamentVoteView; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut liquidity_tournament_vote__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Visible => { + if liquidity_tournament_vote__.is_some() { + return Err(serde::de::Error::duplicate_field("visible")); + } + liquidity_tournament_vote__ = map_.next_value::<::std::option::Option<_>>()?.map(action_liquidity_tournament_vote_view::LiquidityTournamentVote::Visible) +; + } + GeneratedField::Opaque => { + if liquidity_tournament_vote__.is_some() { + return Err(serde::de::Error::duplicate_field("opaque")); + } + liquidity_tournament_vote__ = map_.next_value::<::std::option::Option<_>>()?.map(action_liquidity_tournament_vote_view::LiquidityTournamentVote::Opaque) +; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ActionLiquidityTournamentVoteView { + liquidity_tournament_vote: liquidity_tournament_vote__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for action_liquidity_tournament_vote_view::Opaque { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.vote.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Opaque", len)?; + if let Some(v) = self.vote.as_ref() { + struct_ser.serialize_field("vote", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for action_liquidity_tournament_vote_view::Opaque { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "vote", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Vote, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "vote" => Ok(GeneratedField::Vote), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = action_liquidity_tournament_vote_view::Opaque; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Opaque") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut vote__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Vote => { + if vote__.is_some() { + return Err(serde::de::Error::duplicate_field("vote")); + } + vote__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(action_liquidity_tournament_vote_view::Opaque { + vote: vote__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Opaque", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for action_liquidity_tournament_vote_view::Visible { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.vote.is_some() { + len += 1; + } + if self.note.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Visible", len)?; + if let Some(v) = self.vote.as_ref() { + struct_ser.serialize_field("vote", v)?; + } + if let Some(v) = self.note.as_ref() { + struct_ser.serialize_field("note", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for action_liquidity_tournament_vote_view::Visible { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "vote", + "note", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Vote, + Note, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "vote" => Ok(GeneratedField::Vote), + "note" => Ok(GeneratedField::Note), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = action_liquidity_tournament_vote_view::Visible; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Visible") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut vote__ = None; + let mut note__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Vote => { + if vote__.is_some() { + return Err(serde::de::Error::duplicate_field("vote")); + } + vote__ = map_.next_value()?; + } + GeneratedField::Note => { + if note__.is_some() { + return Err(serde::de::Error::duplicate_field("note")); + } + note__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(action_liquidity_tournament_vote_view::Visible { + vote: vote__, + note: note__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.ActionLiquidityTournamentVoteView.Visible", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for EventFundingStreamReward { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -491,3 +941,288 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { deserializer.deserialize_struct("penumbra.core.component.funding.v1.GenesisContent", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for LiquidityTournamentVoteBody { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.incentivized.is_some() { + len += 1; + } + if self.rewards_recipient.is_some() { + len += 1; + } + if self.start_position != 0 { + len += 1; + } + if self.value.is_some() { + len += 1; + } + if self.nullifier.is_some() { + len += 1; + } + if self.rk.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.LiquidityTournamentVoteBody", len)?; + if let Some(v) = self.incentivized.as_ref() { + struct_ser.serialize_field("incentivized", v)?; + } + if let Some(v) = self.rewards_recipient.as_ref() { + struct_ser.serialize_field("rewardsRecipient", v)?; + } + if self.start_position != 0 { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("startPosition", ToString::to_string(&self.start_position).as_str())?; + } + if let Some(v) = self.value.as_ref() { + struct_ser.serialize_field("value", v)?; + } + if let Some(v) = self.nullifier.as_ref() { + struct_ser.serialize_field("nullifier", v)?; + } + if let Some(v) = self.rk.as_ref() { + struct_ser.serialize_field("rk", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for LiquidityTournamentVoteBody { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "incentivized", + "rewards_recipient", + "rewardsRecipient", + "start_position", + "startPosition", + "value", + "nullifier", + "rk", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Incentivized, + RewardsRecipient, + StartPosition, + Value, + Nullifier, + Rk, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "incentivized" => Ok(GeneratedField::Incentivized), + "rewardsRecipient" | "rewards_recipient" => Ok(GeneratedField::RewardsRecipient), + "startPosition" | "start_position" => Ok(GeneratedField::StartPosition), + "value" => Ok(GeneratedField::Value), + "nullifier" => Ok(GeneratedField::Nullifier), + "rk" => Ok(GeneratedField::Rk), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = LiquidityTournamentVoteBody; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.LiquidityTournamentVoteBody") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut incentivized__ = None; + let mut rewards_recipient__ = None; + let mut start_position__ = None; + let mut value__ = None; + let mut nullifier__ = None; + let mut rk__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Incentivized => { + if incentivized__.is_some() { + return Err(serde::de::Error::duplicate_field("incentivized")); + } + incentivized__ = map_.next_value()?; + } + GeneratedField::RewardsRecipient => { + if rewards_recipient__.is_some() { + return Err(serde::de::Error::duplicate_field("rewardsRecipient")); + } + rewards_recipient__ = map_.next_value()?; + } + GeneratedField::StartPosition => { + if start_position__.is_some() { + return Err(serde::de::Error::duplicate_field("startPosition")); + } + start_position__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::Value => { + if value__.is_some() { + return Err(serde::de::Error::duplicate_field("value")); + } + value__ = map_.next_value()?; + } + GeneratedField::Nullifier => { + if nullifier__.is_some() { + return Err(serde::de::Error::duplicate_field("nullifier")); + } + nullifier__ = map_.next_value()?; + } + GeneratedField::Rk => { + if rk__.is_some() { + return Err(serde::de::Error::duplicate_field("rk")); + } + rk__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(LiquidityTournamentVoteBody { + incentivized: incentivized__, + rewards_recipient: rewards_recipient__, + start_position: start_position__.unwrap_or_default(), + value: value__, + nullifier: nullifier__, + rk: rk__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.LiquidityTournamentVoteBody", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for ZkLiquidityTournamentVoteProof { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.inner.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.funding.v1.ZKLiquidityTournamentVoteProof", len)?; + if !self.inner.is_empty() { + #[allow(clippy::needless_borrow)] + #[allow(clippy::needless_borrows_for_generic_args)] + struct_ser.serialize_field("inner", pbjson::private::base64::encode(&self.inner).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ZkLiquidityTournamentVoteProof { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "inner", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Inner, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "inner" => Ok(GeneratedField::Inner), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ZkLiquidityTournamentVoteProof; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.funding.v1.ZKLiquidityTournamentVoteProof") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut inner__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Inner => { + if inner__.is_some() { + return Err(serde::de::Error::duplicate_field("inner")); + } + inner__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ZkLiquidityTournamentVoteProof { + inner: inner__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.funding.v1.ZKLiquidityTournamentVoteProof", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.rs index 2295d26332..ed60879050 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.rs @@ -139,7 +139,7 @@ impl ::prost::Name for DetectionData { pub struct Action { #[prost( oneof = "action::Action", - tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52, 53, 54, 55, 200" + tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52, 53, 54, 55, 70, 200" )] pub action: ::core::option::Option, } @@ -221,6 +221,11 @@ pub mod action { ActionDutchAuctionWithdraw( super::super::super::component::auction::v1::ActionDutchAuctionWithdraw, ), + /// Funding + #[prost(message, tag = "70")] + ActionLiquidityTournamentVote( + super::super::super::component::funding::v1::ActionLiquidityTournamentVote, + ), #[prost(message, tag = "200")] Ics20Withdrawal(super::super::super::component::ibc::v1::Ics20Withdrawal), } @@ -462,7 +467,7 @@ impl ::prost::Name for TransactionBodyView { pub struct ActionView { #[prost( oneof = "action_view::ActionView", - tags = "1, 2, 3, 4, 21, 16, 17, 18, 19, 20, 22, 30, 31, 32, 34, 41, 42, 50, 51, 52, 53, 54, 55, 43, 200" + tags = "1, 2, 3, 4, 21, 16, 17, 18, 19, 20, 22, 30, 31, 32, 34, 41, 42, 50, 51, 52, 53, 54, 55, 43, 70, 200" )] pub action_view: ::core::option::Option, } @@ -546,6 +551,10 @@ pub mod action_view { /// of the transaction. is that fine? #[prost(message, tag = "43")] UndelegateClaim(super::super::super::component::stake::v1::UndelegateClaim), + #[prost(message, tag = "70")] + ActionLiquidityTournamentVote( + super::super::super::component::funding::v1::ActionLiquidityTournamentVoteView, + ), #[prost(message, tag = "200")] Ics20Withdrawal(super::super::super::component::ibc::v1::Ics20Withdrawal), } diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs index ba9d7347a8..1b09283f15 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs @@ -84,6 +84,9 @@ impl serde::Serialize for Action { action::Action::ActionDutchAuctionWithdraw(v) => { struct_ser.serialize_field("actionDutchAuctionWithdraw", v)?; } + action::Action::ActionLiquidityTournamentVote(v) => { + struct_ser.serialize_field("actionLiquidityTournamentVote", v)?; + } action::Action::Ics20Withdrawal(v) => { struct_ser.serialize_field("ics20Withdrawal", v)?; } @@ -142,6 +145,8 @@ impl<'de> serde::Deserialize<'de> for Action { "actionDutchAuctionEnd", "action_dutch_auction_withdraw", "actionDutchAuctionWithdraw", + "action_liquidity_tournament_vote", + "actionLiquidityTournamentVote", "ics20_withdrawal", "ics20Withdrawal", ]; @@ -172,6 +177,7 @@ impl<'de> serde::Deserialize<'de> for Action { ActionDutchAuctionSchedule, ActionDutchAuctionEnd, ActionDutchAuctionWithdraw, + ActionLiquidityTournamentVote, Ics20Withdrawal, __SkipField__, } @@ -219,6 +225,7 @@ impl<'de> serde::Deserialize<'de> for Action { "actionDutchAuctionSchedule" | "action_dutch_auction_schedule" => Ok(GeneratedField::ActionDutchAuctionSchedule), "actionDutchAuctionEnd" | "action_dutch_auction_end" => Ok(GeneratedField::ActionDutchAuctionEnd), "actionDutchAuctionWithdraw" | "action_dutch_auction_withdraw" => Ok(GeneratedField::ActionDutchAuctionWithdraw), + "actionLiquidityTournamentVote" | "action_liquidity_tournament_vote" => Ok(GeneratedField::ActionLiquidityTournamentVote), "ics20Withdrawal" | "ics20_withdrawal" => Ok(GeneratedField::Ics20Withdrawal), _ => Ok(GeneratedField::__SkipField__), } @@ -408,6 +415,13 @@ impl<'de> serde::Deserialize<'de> for Action { return Err(serde::de::Error::duplicate_field("actionDutchAuctionWithdraw")); } action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::ActionDutchAuctionWithdraw) +; + } + GeneratedField::ActionLiquidityTournamentVote => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionLiquidityTournamentVote")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::ActionLiquidityTournamentVote) ; } GeneratedField::Ics20Withdrawal => { @@ -948,6 +962,9 @@ impl serde::Serialize for ActionView { action_view::ActionView::UndelegateClaim(v) => { struct_ser.serialize_field("undelegateClaim", v)?; } + action_view::ActionView::ActionLiquidityTournamentVote(v) => { + struct_ser.serialize_field("actionLiquidityTournamentVote", v)?; + } action_view::ActionView::Ics20Withdrawal(v) => { struct_ser.serialize_field("ics20Withdrawal", v)?; } @@ -1006,6 +1023,8 @@ impl<'de> serde::Deserialize<'de> for ActionView { "actionDutchAuctionWithdraw", "undelegate_claim", "undelegateClaim", + "action_liquidity_tournament_vote", + "actionLiquidityTournamentVote", "ics20_withdrawal", "ics20Withdrawal", ]; @@ -1036,6 +1055,7 @@ impl<'de> serde::Deserialize<'de> for ActionView { ActionDutchAuctionEnd, ActionDutchAuctionWithdraw, UndelegateClaim, + ActionLiquidityTournamentVote, Ics20Withdrawal, __SkipField__, } @@ -1083,6 +1103,7 @@ impl<'de> serde::Deserialize<'de> for ActionView { "actionDutchAuctionEnd" | "action_dutch_auction_end" => Ok(GeneratedField::ActionDutchAuctionEnd), "actionDutchAuctionWithdraw" | "action_dutch_auction_withdraw" => Ok(GeneratedField::ActionDutchAuctionWithdraw), "undelegateClaim" | "undelegate_claim" => Ok(GeneratedField::UndelegateClaim), + "actionLiquidityTournamentVote" | "action_liquidity_tournament_vote" => Ok(GeneratedField::ActionLiquidityTournamentVote), "ics20Withdrawal" | "ics20_withdrawal" => Ok(GeneratedField::Ics20Withdrawal), _ => Ok(GeneratedField::__SkipField__), } @@ -1272,6 +1293,13 @@ impl<'de> serde::Deserialize<'de> for ActionView { return Err(serde::de::Error::duplicate_field("undelegateClaim")); } action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::UndelegateClaim) +; + } + GeneratedField::ActionLiquidityTournamentVote => { + if action_view__.is_some() { + return Err(serde::de::Error::duplicate_field("actionLiquidityTournamentVote")); + } + action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::ActionLiquidityTournamentVote) ; } GeneratedField::Ics20Withdrawal => { diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 54eeb4fe02..8dbdfb069e 100644 Binary files a/crates/proto/src/gen/proto_descriptor.bin.no_lfs and b/crates/proto/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/proto/penumbra/penumbra/core/component/funding/v1/funding.proto b/proto/penumbra/penumbra/core/component/funding/v1/funding.proto index ab5a5aae72..b27e93becb 100644 --- a/proto/penumbra/penumbra/core/component/funding/v1/funding.proto +++ b/proto/penumbra/penumbra/core/component/funding/v1/funding.proto @@ -1,7 +1,12 @@ syntax = "proto3"; package penumbra.core.component.funding.v1; +import "penumbra/core/asset/v1/asset.proto"; +import "penumbra/core/component/sct/v1/sct.proto"; +import "penumbra/core/component/shielded_pool/v1/shielded_pool.proto"; +import "penumbra/core/keys/v1/keys.proto"; import "penumbra/core/num/v1/num.proto"; +import "penumbra/crypto/decaf377_rdsa/v1/decaf377_rdsa.proto"; // Funding component configuration data. message FundingParameters { @@ -45,3 +50,50 @@ message EventFundingStreamReward { // The amount of the reward, in staking tokens. num.v1.Amount reward_amount = 3; } + +// An action for voting in a liquidity tournament. +message ActionLiquidityTournamentVote { + // The effectful data signalling user intent, and the validity of this intent. + LiquidityTournamentVoteBody body = 1; + // An authorization from the user over this body. + crypto.decaf377_rdsa.v1.SpendAuthSignature auth_sig = 2; + // A ZK proof that it was correctly constructed from private user state. + ZKLiquidityTournamentVoteProof proof = 3; +} + +message LiquidityTournamentVoteBody { + // Which asset should be incentivized. + asset.v1.Denom incentivized = 1; + // Where to send any rewards for participating in the tournament. + keys.v1.Address rewards_recipient = 2; + // The start position of the tournament + uint64 start_position = 3; + // The value being voted with. + // + // This should be some amount of a validator's delegation token. + asset.v1.Value value = 4; + // The nullifier associated with the note being spent. + sct.v1.Nullifier nullifier = 5; + // A randomized verification key with which to check the auth signature. + crypto.decaf377_rdsa.v1.SpendVerificationKey rk = 6; +} + +// A proof of the validity of a liquidity vote, wrt private state. +message ZKLiquidityTournamentVoteProof { + bytes inner = 1; +} + +message ActionLiquidityTournamentVoteView { + // If we initiated the vote, we should know the note that we spent. + message Visible { + ActionLiquidityTournamentVote vote = 1; + shielded_pool.v1.NoteView note = 2; + } + message Opaque { + ActionLiquidityTournamentVote vote = 1; + } + oneof liquidity_tournament_vote { + Visible visible = 1; + Opaque opaque = 2; + } +} diff --git a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto index beffedd441..9a4a195dc0 100644 --- a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto +++ b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto @@ -7,6 +7,7 @@ import "penumbra/core/component/auction/v1/auction.proto"; import "penumbra/core/component/dex/v1/dex.proto"; import "penumbra/core/component/fee/v1/fee.proto"; import "penumbra/core/component/governance/v1/governance.proto"; +import "penumbra/core/component/funding/v1/funding.proto"; import "penumbra/core/component/ibc/v1/ibc.proto"; import "penumbra/core/component/sct/v1/sct.proto"; import "penumbra/core/component/shielded_pool/v1/shielded_pool.proto"; @@ -112,6 +113,9 @@ message Action { component.auction.v1.ActionDutchAuctionEnd action_dutch_auction_end = 54; component.auction.v1.ActionDutchAuctionWithdraw action_dutch_auction_withdraw = 55; + // Funding + component.funding.v1.ActionLiquidityTournamentVote action_liquidity_tournament_vote = 70; + component.ibc.v1.Ics20Withdrawal ics20_withdrawal = 200; } } @@ -241,6 +245,9 @@ message ActionView { // balance commitment, and can only infer the value from looking at the rest // of the transaction. is that fine? component.stake.v1.UndelegateClaim undelegate_claim = 43; + + component.funding.v1.ActionLiquidityTournamentVoteView action_liquidity_tournament_vote = 70; + component.ibc.v1.Ics20Withdrawal ics20_withdrawal = 200; } }