Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cronokirby/lqt vote plan #5078

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/bin/pcli/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ fn pretty_print_transaction_plan(
ActionPlan::ActionDutchAuctionEnd(_) => None,
ActionPlan::ActionDutchAuctionWithdraw(_) => None,
ActionPlan::IbcAction(_) => todo!(),
ActionPlan::ActionLiquidityTournamentVote(_) => None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/core/app/src/action_handler/actions/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl AppActionHandler for ProposalSubmit {
anyhow::bail!("invalid action in Community Pool spend proposal (not allowed to manipulate proposals from within proposals)")
}
ValidatorDefinition(_)
| ActionLiquidityTournamentVote(_)
| IbcAction(_)
| ValidatorVote(_)
| PositionOpen(_)
Expand Down
1 change: 1 addition & 0 deletions crates/core/component/funding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ 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}
rand = {workspace = true}
serde = {workspace = true, features = ["derive"]}
tendermint = {workspace = true}
tracing = {workspace = true}
Expand Down
2 changes: 2 additions & 0 deletions crates/core/component/funding/src/liquidity_tournament/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod action;
mod plan;
mod view;

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

/// The maximum number of allowable bytes in the denom string.
Expand Down
204 changes: 204 additions & 0 deletions crates/core/component/funding/src/liquidity_tournament/plan/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
use anyhow::{anyhow, Context};
use decaf377::{Fq, Fr};
use decaf377_rdsa::{Signature, SpendAuth};
use penumbra_sdk_asset::asset::Denom;
use penumbra_sdk_keys::{Address, FullViewingKey};
use penumbra_sdk_proof_params::DELEGATOR_VOTE_PROOF_PROVING_KEY;
use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType};
use penumbra_sdk_sct::Nullifier;
use penumbra_sdk_shielded_pool::note::Note;
use penumbra_sdk_tct::{self as tct};
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use std::convert::{From, TryFrom};

use super::{
proof::{
LiquidityTournamentVoteProof, LiquidityTournamentVoteProofPrivate,
LiquidityTournamentVoteProofPublic,
},
ActionLiquidityTournamentVote, LiquidityTournamentVoteBody,
};

/// A plan to vote in the liquidity tournament.
///
/// This structure represents the planned vote before it is actually executed,
/// containing the necessary information and blinding factors for the voting proof.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(
try_from = "pb::ActionLiquidityTournamentVotePlan",
into = "pb::ActionLiquidityTournamentVotePlan"
)]
pub struct ActionLiquidityTournamentVotePlan {
/// The asset the user wants to vote for.
pub incentivized: Denom,
/// The address the user wants potential rewards to go to.
pub rewards_recipient: Address,
/// The note containing the staked note used for voting.
pub staked_note: Note,
/// The position of the staked note.
pub staked_note_position: tct::Position,
/// The start position of the tournament.
pub start_position: tct::Position,
/// Randomizer for proof of spend capability.
pub randomizer: Fr,
/// The first blinding factor used for generating the ZK proof.
pub proof_blinding_r: Fq,
/// The second blinding factor used for generating the ZK proof.
pub proof_blinding_s: Fq,
}

impl ActionLiquidityTournamentVotePlan {
/// Create a new [`ActionLiquidityTournamentVotePlan`] that votes using the given positioned [`Note`].
#[allow(clippy::too_many_arguments)]
pub fn new<R: CryptoRng + RngCore>(
rng: &mut R,
incentivized: Denom,
rewards_recipient: Address,
staked_note: Note,
staked_note_position: tct::Position,
start_position: tct::Position,
) -> ActionLiquidityTournamentVotePlan {
ActionLiquidityTournamentVotePlan {
incentivized,
rewards_recipient,
staked_note,
staked_note_position,
start_position,
randomizer: Fr::rand(rng),
proof_blinding_r: Fq::rand(rng),
proof_blinding_s: Fq::rand(rng),
}
}

pub fn to_body(&self, fvk: &FullViewingKey) -> LiquidityTournamentVoteBody {
let commitment = self.staked_note.commit();

let nk = fvk.nullifier_key();
let nullifier = Nullifier::derive(nk, self.staked_note_position, &commitment);
let ak = fvk.spend_verification_key();
let rk = ak.randomize(&self.randomizer);
let value = self.staked_note.value();

LiquidityTournamentVoteBody {
incentivized: self.incentivized.clone(),
rewards_recipient: self.rewards_recipient.clone(),
start_position: self.start_position,
value,
nullifier,
rk,
}
}

/// Convert this plan into an action.
///
/// * `fvk`: [`FullViewingKey`], in order to derive keys.
/// * `auth_sig`: [`Signature<SpendAuth>`], as the signature for the transaction.
/// * `auth_path`: [`tct::Proof`], witnessing the inclusion of the spent note.
pub fn to_action(
self,
fvk: &FullViewingKey,
auth_sig: Signature<SpendAuth>,
auth_path: tct::Proof,
) -> ActionLiquidityTournamentVote {
let commitment = self.staked_note.commit();

let nk = fvk.nullifier_key();
let nullifier = Nullifier::derive(nk, self.staked_note_position, &commitment);
let ak = fvk.spend_verification_key();
let rk = ak.randomize(&self.randomizer);
let value = self.staked_note.value();

let public = LiquidityTournamentVoteProofPublic {
anchor: auth_path.root(),
value,
nullifier,
rk,
start_position: self.start_position,
};
let private = LiquidityTournamentVoteProofPrivate {
state_commitment_proof: auth_path,
note: self.staked_note.clone(),
spend_auth_randomizer: self.randomizer,
ak: *ak,
nk: *nk,
};
let proof = LiquidityTournamentVoteProof::prove(
self.proof_blinding_r,
self.proof_blinding_s,
&DELEGATOR_VOTE_PROOF_PROVING_KEY,
public,
private,
)
.expect("can generate ZK LQT voting proof");

ActionLiquidityTournamentVote {
body: self.to_body(fvk),
auth_sig,
proof,
}
}
}

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

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

fn try_from(proto: pb::ActionLiquidityTournamentVotePlan) -> Result<Self, Self::Error> {
let proof_blinding_r_bytes: [u8; 32] = proto
.proof_blinding_r
.try_into()
.map_err(|_| anyhow::anyhow!("malformed r in `DelegatorVotePlan`"))?;
let proof_blinding_s_bytes: [u8; 32] = proto
.proof_blinding_s
.try_into()
.map_err(|_| anyhow::anyhow!("malformed s in `DelegatorVotePlan`"))?;
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()?,
staked_note: proto
.staked_note
.ok_or_else(|| anyhow!("missing `staked_note`"))?
.try_into()?,
staked_note_position: proto.staked_note_position.into(),
start_position: proto.start_position.into(),
randomizer: Fr::from_bytes_checked(
proto
.randomizer
.as_slice()
.try_into()
.map_err(|_| anyhow::anyhow!("invalid randomizer"))?,
)
.map_err(|_| anyhow!("randomizer malformed"))?,
proof_blinding_r: Fq::from_bytes_checked(&proof_blinding_r_bytes)
.map_err(|_| anyhow!("proof_blinding_r malformed"))?,
proof_blinding_s: Fq::from_bytes_checked(&proof_blinding_s_bytes)
.map_err(|_| anyhow!("proof_blinding_s malformed"))?,
})
.with_context(|| format!("while parsing {}", std::any::type_name::<Self>()))
}
}

impl From<ActionLiquidityTournamentVotePlan> for pb::ActionLiquidityTournamentVotePlan {
fn from(value: ActionLiquidityTournamentVotePlan) -> Self {
Self {
incentivized: Some(value.incentivized.into()),
rewards_recipient: Some(value.rewards_recipient.into()),
staked_note: Some(value.staked_note.into()),
staked_note_position: value.staked_note_position.into(),
start_position: value.start_position.into(),
randomizer: value.randomizer.to_bytes().to_vec(),
proof_blinding_r: value.proof_blinding_r.to_bytes().to_vec(),
proof_blinding_s: value.proof_blinding_s.to_bytes().to_vec(),
}
}
}
1 change: 1 addition & 0 deletions crates/core/transaction/src/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ impl GasCost for ActionPlan {
ActionPlan::CommunityPoolOutput(d) => d.gas_cost(),
ActionPlan::CommunityPoolDeposit(dd) => dd.gas_cost(),
ActionPlan::Ics20Withdrawal(w) => w.gas_cost(),
ActionPlan::ActionLiquidityTournamentVote(_) => liquidity_tournament_vote_gas_cost(),
}
}
}
Expand Down
31 changes: 30 additions & 1 deletion crates/core/transaction/src/plan/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionEnd;
use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionSchedule;
use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan;
use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend};
use penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVotePlan;
use penumbra_sdk_txhash::{EffectHash, EffectingData};

use penumbra_sdk_dex::{
Expand Down Expand Up @@ -82,6 +83,8 @@ pub enum ActionPlan {
ActionDutchAuctionSchedule(ActionDutchAuctionSchedule),
ActionDutchAuctionEnd(ActionDutchAuctionEnd),
ActionDutchAuctionWithdraw(ActionDutchAuctionWithdrawPlan),

ActionLiquidityTournamentVote(ActionLiquidityTournamentVotePlan),
}

impl ActionPlan {
Expand Down Expand Up @@ -165,6 +168,18 @@ impl ActionPlan {
ActionDutchAuctionWithdraw(plan) => {
Action::ActionDutchAuctionWithdraw(plan.to_action())
}
ActionLiquidityTournamentVote(plan) => {
let note_commitment = plan.staked_note.commit();
let auth_path = witness_data
.state_commitment_proofs
.get(&note_commitment)
.context(format!("could not get proof for {note_commitment:?}"))?;
Action::ActionLiquidityTournamentVote(plan.to_action(
fvk,
[0; 64].into(),
auth_path.clone(),
))
}
})
}

Expand Down Expand Up @@ -195,6 +210,7 @@ impl ActionPlan {
ActionPlan::ActionDutchAuctionSchedule(_) => 53,
ActionPlan::ActionDutchAuctionEnd(_) => 54,
ActionPlan::ActionDutchAuctionWithdraw(_) => 55,
ActionPlan::ActionLiquidityTournamentVote(_) => 70,
}
}

Expand Down Expand Up @@ -225,7 +241,10 @@ impl ActionPlan {
ActionDutchAuctionWithdraw(action) => action.balance(),

// None of these contribute to transaction balance:
IbcAction(_) | ValidatorDefinition(_) | ValidatorVote(_) => Balance::default(),
IbcAction(_)
| ValidatorDefinition(_)
| ValidatorVote(_)
| ActionLiquidityTournamentVote(_) => Balance::default(),
}
}

Expand Down Expand Up @@ -257,6 +276,7 @@ impl ActionPlan {
ActionDutchAuctionSchedule(_) => Fr::zero(),
ActionDutchAuctionEnd(_) => Fr::zero(),
ActionDutchAuctionWithdraw(_) => Fr::zero(),
ActionLiquidityTournamentVote(_) => Fr::zero(),
}
}

Expand Down Expand Up @@ -289,6 +309,7 @@ impl ActionPlan {
ActionDutchAuctionSchedule(plan) => plan.effect_hash(),
ActionDutchAuctionEnd(plan) => plan.effect_hash(),
ActionDutchAuctionWithdraw(plan) => plan.to_action().effect_hash(),
ActionLiquidityTournamentVote(plan) => plan.to_body(fvk).effect_hash(),
}
}
}
Expand Down Expand Up @@ -532,6 +553,11 @@ impl From<ActionPlan> for pb_t::ActionPlan {
inner.into(),
)),
},
ActionPlan::ActionLiquidityTournamentVote(inner) => pb_t::ActionPlan {
action: Some(
pb_t::action_plan::Action::ActionLiquidityTournamentVotePlan(inner.into()),
),
},
}
}
}
Expand Down Expand Up @@ -617,6 +643,9 @@ impl TryFrom<pb_t::ActionPlan> for ActionPlan {
pb_t::action_plan::Action::Ics20Withdrawal(inner) => {
Ok(ActionPlan::Ics20Withdrawal(inner.try_into()?))
}
pb_t::action_plan::Action::ActionLiquidityTournamentVotePlan(_) => {
unimplemented!()
}
}
}
}
1 change: 1 addition & 0 deletions crates/core/transaction/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ mod test {
ActionPlan::ActionDutchAuctionEnd(_) => None,
ActionPlan::ActionDutchAuctionWithdraw(_) => None,
ActionPlan::IbcAction(_) => todo!(),
ActionPlan::ActionLiquidityTournamentVote(_) => todo!(),
}
}

Expand Down
Loading
Loading