From e5ae70749249c8c3479579dc87743660354cba28 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 13:24:32 -0800 Subject: [PATCH 1/6] Implement protos for LQT actions --- .../gen/penumbra.core.component.funding.v1.rs | 147 ++++ ...enumbra.core.component.funding.v1.serde.rs | 735 ++++++++++++++++++ .../src/gen/penumbra.core.transaction.v1.rs | 13 +- .../gen/penumbra.core.transaction.v1.serde.rs | 28 + .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 647837 -> 652363 bytes .../core/component/funding/v1/funding.proto | 52 ++ .../core/transaction/v1/transaction.proto | 7 + 7 files changed, 980 insertions(+), 2 deletions(-) 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 54eeb4fe0216e6b34656789922e3da451d94cfda..8dbdfb069e6553a838f135fff791539df202cd49 100644 GIT binary patch delta 16054 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%0Dx)oT_KqHf>>^jDc8v>{*7&inE>|-3q_T`F= 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 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; } } From a88a65050a40f2450291767e26ac2e87739badc4 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 13:27:53 -0800 Subject: [PATCH 2/6] Implement LiquidityTournamentVote proofs This re-uses the strategy of the undelegate claim proofs, which wrap an inner proof. This has the advantage of simplifying the proving interface a bit, at the expense of some one time boilerplate. --- Cargo.lock | 8 ++ crates/core/component/funding/Cargo.toml | 18 ++- crates/core/component/funding/src/lib.rs | 1 + .../funding/src/liquidity_tournament/mod.rs | 1 + .../src/liquidity_tournament/proof/mod.rs | 122 ++++++++++++++++++ .../governance/src/delegator_vote/proof.rs | 12 ++ 6 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 crates/core/component/funding/src/liquidity_tournament/mod.rs create mode 100644 crates/core/component/funding/src/liquidity_tournament/proof/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ff9ae8718c..620f5e38f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5208,19 +5208,27 @@ 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", "serde", "tendermint 0.40.1", "tracing", diff --git a/crates/core/component/funding/Cargo.toml b/crates/core/component/funding/Cargo.toml index 7f6ed07d9d..932e1f6b61 100644 --- a/crates/core/component/funding/Cargo.toml +++ b/crates/core/component/funding/Cargo.toml @@ -21,23 +21,39 @@ 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} serde = {workspace = true, features = ["derive"]} tendermint = {workspace = true} tracing = {workspace = true} diff --git a/crates/core/component/funding/src/lib.rs b/crates/core/component/funding/src/lib.rs index f772fb2bd0..83d154c5d5 100644 --- a/crates/core/component/funding/src/lib.rs +++ b/crates/core/component/funding/src/lib.rs @@ -5,5 +5,6 @@ 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/mod.rs b/crates/core/component/funding/src/liquidity_tournament/mod.rs new file mode 100644 index 0000000000..1e185861fc --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/mod.rs @@ -0,0 +1 @@ +pub mod proof; 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/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 { From 6a209d475adc3122d2de64e65139fa11c8ccef52 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 19:18:01 -0800 Subject: [PATCH 3/6] Add Debug implementation to asset::Denom --- crates/core/asset/src/asset/denom.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From f8c8a1211c8227305d257cda6aab6a841b9453eb Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 15:33:20 -0800 Subject: [PATCH 4/6] Implement rust equivalent of LQT actions --- Cargo.lock | 2 + crates/bin/pcli/src/transaction_view_ext.rs | 1 + crates/core/app/src/action_handler/actions.rs | 3 + crates/core/component/funding/Cargo.toml | 1 + .../src/liquidity_tournament/action/mod.rs | 147 ++++++++++++++++++ .../funding/src/liquidity_tournament/mod.rs | 8 + .../src/liquidity_tournament/view/mod.rs | 91 +++++++++++ crates/core/transaction/Cargo.toml | 2 + crates/core/transaction/src/action.rs | 18 +++ crates/core/transaction/src/gas.rs | 41 +++++ crates/core/transaction/src/is_action.rs | 23 +++ crates/core/transaction/src/transaction.rs | 9 +- .../core/transaction/src/view/action_view.rs | 11 ++ 13 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 crates/core/component/funding/src/liquidity_tournament/action/mod.rs create mode 100644 crates/core/component/funding/src/liquidity_tournament/view/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 620f5e38f2..cad652cb6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5229,6 +5229,7 @@ dependencies = [ "penumbra-sdk-shielded-pool", "penumbra-sdk-stake", "penumbra-sdk-tct", + "penumbra-sdk-txhash", "serde", "tendermint 0.40.1", "tracing", @@ -5899,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/component/funding/Cargo.toml b/crates/core/component/funding/Cargo.toml index 932e1f6b61..fec6e3d9d7 100644 --- a/crates/core/component/funding/Cargo.toml +++ b/crates/core/component/funding/Cargo.toml @@ -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} 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..c460be5086 --- /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_commmitment(&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 index 1e185861fc..5a660f69a1 100644 --- a/crates/core/component/funding/src/liquidity_tournament/mod.rs +++ b/crates/core/component/funding/src/liquidity_tournament/mod.rs @@ -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; 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/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..defede771d 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_commmitment(), } } @@ -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..50eb8aa54f 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_commmitment() + } + + 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()) + } } } } From d299209142451bac43fe48db2f2f440693bd9c28 Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 23:14:33 -0800 Subject: [PATCH 5/6] Implement stateless handler for LQT voting --- .../liquidity_tournament/mod.rs | 76 +++++++++++++++++++ .../funding/src/action_handler/mod.rs | 1 + crates/core/component/funding/src/lib.rs | 2 + 3 files changed, 79 insertions(+) create mode 100644 crates/core/component/funding/src/action_handler/liquidity_tournament/mod.rs create mode 100644 crates/core/component/funding/src/action_handler/mod.rs 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 83d154c5d5..6e11248cf4 100644 --- a/crates/core/component/funding/src/lib.rs +++ b/crates/core/component/funding/src/lib.rs @@ -1,6 +1,8 @@ #![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; From a8d55dc670d829fef34e5bf353ce89ddf5bac194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BAc=C3=A1s=20Meier?= Date: Thu, 30 Jan 2025 06:58:40 -0800 Subject: [PATCH 6/6] Update crates/core/component/funding/src/liquidity_tournament/action/mod.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Erwan Or Signed-off-by: Lúcás Meier --- .../component/funding/src/liquidity_tournament/action/mod.rs | 2 +- crates/core/transaction/src/action.rs | 2 +- crates/core/transaction/src/is_action.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/core/component/funding/src/liquidity_tournament/action/mod.rs b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs index c460be5086..86bf11b355 100644 --- a/crates/core/component/funding/src/liquidity_tournament/action/mod.rs +++ b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs @@ -140,7 +140,7 @@ impl EffectingData for ActionLiquidityTournamentVote { impl ActionLiquidityTournamentVote { /// This action doesn't actually produce or consume value. - pub fn balance_commmitment(&self) -> balance::Commitment { + pub fn balance_commitment(&self) -> balance::Commitment { // This will be the commitment to zero. balance::Commitment::default() } diff --git a/crates/core/transaction/src/action.rs b/crates/core/transaction/src/action.rs index defede771d..63ecfd17bd 100644 --- a/crates/core/transaction/src/action.rs +++ b/crates/core/transaction/src/action.rs @@ -196,7 +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(), + Action::ActionLiquidityTournamentVote(action) => action.balance_commitment(), } } diff --git a/crates/core/transaction/src/is_action.rs b/crates/core/transaction/src/is_action.rs index 50eb8aa54f..141ea5f934 100644 --- a/crates/core/transaction/src/is_action.rs +++ b/crates/core/transaction/src/is_action.rs @@ -504,7 +504,7 @@ impl IsAction for ActionDutchAuctionWithdraw { impl IsAction for ActionLiquidityTournamentVote { fn balance_commitment(&self) -> balance::Commitment { - self.balance_commmitment() + self.balance_commitment() } fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {