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

lqt: implement voting in pcli #5079

Merged
merged 13 commits into from
Feb 8, 2025
Merged
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.

6 changes: 6 additions & 0 deletions crates/bin/pcli/src/command/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use ibc_types::core::{
client::Height as IbcHeight,
};
use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
use lqt_vote::LqtVoteCmd;
use rand_core::OsRng;
use regex::Regex;

Expand Down Expand Up @@ -82,6 +83,7 @@ use clap::Parser;

mod auction;
mod liquidity_position;
mod lqt_vote;
mod proposal;
mod replicate;

Expand Down Expand Up @@ -311,6 +313,8 @@ pub enum TxCmd {
/// The transaction to be broadcast
transaction: PathBuf,
},
#[clap(display_order = 700)]
LqtVote(LqtVoteCmd),
}

/// Vote on a governance proposal.
Expand Down Expand Up @@ -368,6 +372,7 @@ impl TxCmd {
TxCmd::Auction(_) => false,
TxCmd::Broadcast { .. } => false,
TxCmd::RegisterForwardingAccount { .. } => false,
TxCmd::LqtVote(cmd) => cmd.offline(),
}
}

Expand Down Expand Up @@ -1553,6 +1558,7 @@ impl TxCmd {

println!("Noble response: {:?}", r);
}
TxCmd::LqtVote(cmd) => cmd.exec(app, gas_prices).await?,
}

Ok(())
Expand Down
128 changes: 128 additions & 0 deletions crates/bin/pcli/src/command/tx/lqt_vote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::collections::BTreeMap;

use anyhow::anyhow;
use penumbra_sdk_asset::{
asset::{self, REGISTRY},
Value,
};
use penumbra_sdk_fee::{FeeTier, GasPrices};
use penumbra_sdk_keys::Address;
use penumbra_sdk_num::Amount;
use penumbra_sdk_proto::core::component::sct::v1::{
query_service_client::QueryServiceClient as SctQueryServiceClient, EpochByHeightRequest,
};
use penumbra_sdk_sct::epoch::Epoch;
use penumbra_sdk_view::{Planner, ViewClient};
use rand_core::OsRng;

use crate::App;

async fn fetch_epoch(app: &mut App) -> anyhow::Result<Epoch> {
let mut sct_client = SctQueryServiceClient::new(app.pd_channel().await?);
let latest_sync_height = app.view().status().await?.full_sync_height;
let epoch = sct_client
.epoch_by_height(EpochByHeightRequest {
height: latest_sync_height,
})
.await?
.into_inner()
.epoch
.expect("epoch must be available")
.into();
Ok(epoch)
}

/// Vote in the current round of the liquidity tournament.
///
/// This will plan a transaction which directs all available voting power to a single asset.
#[derive(Debug, clap::Parser)]
pub struct LqtVoteCmd {
/// The denom string for the asset being voted for.
vote: String,
/// If provided, make the rewards recipient a particular address instead.
///
/// This can also be an integer, indicating an ephemeral address of a sub-account.
#[clap(short, long)]
rewards_recipient: Option<String>,
/// The selected fee tier.
#[clap(short, long, default_value_t)]
fee_tier: FeeTier,
}

impl LqtVoteCmd {
pub fn offline(&self) -> bool {
false
}

fn rewards_addr(&self, app: &App) -> anyhow::Result<Address> {
let to_parse = match &self.rewards_recipient {
None => {
return Ok(app
.config
.full_viewing_key
.ephemeral_address(OsRng, Default::default())
.0)
}
Some(x) => x,
};
let maybe_index: Option<u32> = to_parse.parse().ok();
if let Some(i) = maybe_index {
return Ok(app
.config
.full_viewing_key
.ephemeral_address(OsRng, i.into())
.0);
}
to_parse
.parse()
.map_err(|_| anyhow!("failed to parse address '{}'", to_parse))
}

pub async fn exec(&self, app: &mut App, gas_prices: GasPrices) -> anyhow::Result<()> {
let vote_meta = REGISTRY
.parse_denom(&self.vote)
.ok_or_else(|| anyhow!("failed to parse denom: '{}'", &self.vote))?;
let vote_denom = vote_meta.base_denom();

let epoch = fetch_epoch(app).await?;
let voting_notes = app.view().lqt_voting_notes(epoch.index, None).await?;

let mut planner = Planner::new(OsRng);

planner
.set_gas_prices(gas_prices)
.set_fee_tier(self.fee_tier);

// First, tell the planner to make all the necessary votes.
planner.lqt_vote(
u16::try_from(epoch.index)?,
vote_denom,
self.rewards_addr(app)?,
&voting_notes,
);
// We also want to go ahead and do the consolidation thing,
// to reduce the number of votes we need in the next epoch.
// To do so, we need to spend all of these notes, and produce one output per
// delegator token.
let mut totals: BTreeMap<asset::Id, Amount> = Default::default();
for note in voting_notes {
let value = note.note.value();
planner.spend(note.note, note.position);
*totals.entry(value.asset_id).or_insert(Amount::zero()) += value.amount;
}
let change_addr = app
.config
.full_viewing_key
.ephemeral_address(OsRng, Default::default())
.0;
planner.change_address(change_addr.clone());
for (asset_id, amount) in totals {
planner.output(Value { asset_id, amount }, change_addr.clone());
}

let plan = planner.plan(app.view(), Default::default()).await?;
app.build_and_submit_transaction(plan).await?;

Ok(())
}
}
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
2 changes: 2 additions & 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 Expand Up @@ -356,6 +357,7 @@ async fn build_community_pool_transaction(
effect_hash: Some(effect_hash),
spend_auths: Default::default(),
delegator_vote_auths: Default::default(),
lqt_vote_auths: Default::default(),
},
)
}
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
Loading