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

pcli: add balance migration command #4842

Merged
merged 1 commit into from
Sep 5, 2024
Merged
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
18 changes: 12 additions & 6 deletions crates/bin/pcli/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use debug::DebugCmd;
pub use init::InitCmd;
pub use migrate::MigrateCmd;
pub use query::QueryCmd;
pub use threshold::ThresholdCmd;
pub use tx::TxCmd;
Expand All @@ -11,6 +12,7 @@ use self::{ceremony::CeremonyCmd, tx::TxCmdWithOptions};
mod ceremony;
mod debug;
mod init;
mod migrate;
mod query;
mod threshold;
mod tx;
Expand Down Expand Up @@ -53,18 +55,21 @@ pub enum Command {
/// Create and broadcast a transaction.
#[clap(display_order = 400, visible_alias = "tx")]
Transaction(TxCmdWithOptions),
/// Follow the threshold signing protocol.
#[clap(subcommand, display_order = 500)]
Threshold(ThresholdCmd),
/// Migrate your balance to another wallet.
#[clap(subcommand, display_order = 600)]
Migrate(MigrateCmd),
/// Manage a validator.
#[clap(subcommand, display_order = 900)]
Validator(ValidatorCmd),
/// Display information related to diagnosing problems running Penumbra
#[clap(subcommand, display_order = 999)]
Debug(DebugCmd),
/// Contribute to the summoning ceremony.
#[clap(subcommand, display_order = 990)]
Ceremony(CeremonyCmd),
/// Follow the threshold signing protocol.
#[clap(subcommand, display_order = 500)]
Threshold(ThresholdCmd),
/// Display information related to diagnosing problems running Penumbra
#[clap(subcommand, display_order = 999)]
Debug(DebugCmd),
}

impl Command {
Expand All @@ -79,6 +84,7 @@ impl Command {
Command::Debug(cmd) => cmd.offline(),
Command::Ceremony(_) => false,
Command::Threshold(cmd) => cmd.offline(),
Command::Migrate(_) => false,
}
}
}
98 changes: 98 additions & 0 deletions crates/bin/pcli/src/command/migrate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::App;
use anyhow::{Context, Result};
use penumbra_keys::FullViewingKey;
use penumbra_proto::view::v1::GasPricesRequest;
use penumbra_view::ViewClient;
use penumbra_wallet::plan::Planner;
use rand_core::OsRng;
use std::{io::Write, str::FromStr};
use termion::input::TermRead;

#[derive(Debug, clap::Parser)]
pub enum MigrateCmd {
/// Migrate your entire balance to another wallet.
///
/// All assets from all accounts in the source wallet will be sent to the destination wallet.
/// A FullViewingKey must be provided for the destination wallet.
/// All funds will be deposited in the account 0 of the destination wallet,
/// minus any gas prices for the migration transaction.
#[clap(name = "balance")]
Balance,
}

impl MigrateCmd {
#[tracing::instrument(skip(self, app))]
pub async fn exec(&self, app: &mut App) -> Result<()> {
let gas_prices = app
.view
.as_mut()
.context("view service must be initialized")?
.gas_prices(GasPricesRequest {})
.await?
.into_inner()
.gas_prices
.expect("gas prices must be available")
.try_into()?;

print!("Enter FVK: ");
std::io::stdout().flush()?;
let to: String = std::io::stdin().lock().read_line()?.unwrap_or_default();

match self {
MigrateCmd::Balance => {
let source_fvk = app.config.full_viewing_key.clone();

let dest_fvk = to.parse::<FullViewingKey>().map_err(|_| {
anyhow::anyhow!("The provided string is not a valid FullViewingKey.")
})?;

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

let (dest_address, _) = FullViewingKey::payment_address(
&FullViewingKey::from_str(&to[..])?,
Default::default(),
);

planner
.set_gas_prices(gas_prices)
.set_fee_tier(Default::default())
.change_address(dest_address);

// Return all unspent notes from the view service
let notes = app
.view
.as_mut()
.context("view service must be initialized")?
.unspent_notes_by_account_and_asset()
.await?;

for notes in notes.into_values() {
for notes in notes.into_values() {
for note in notes {
planner.spend(note.note, note.position);
}
}
}

let memo = format!("Migrating balance from {} to {}", source_fvk, dest_fvk);
let plan = planner
.memo(memo)
.plan(
app.view
.as_mut()
.context("view service must be initialized")?,
Default::default(),
)
.await
.context("can't build send transaction")?;

if plan.actions.is_empty() {
anyhow::bail!("migration plan contained zero actions: is the source wallet already empty?");
}
app.build_and_submit_transaction(plan).await?;

Result::Ok(())
}
}
}
}
1 change: 1 addition & 0 deletions crates/bin/pcli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async fn main() -> Result<()> {
Command::Query(cmd) => cmd.exec(&mut app).await?,
Command::Ceremony(cmd) => cmd.exec(&mut app).await?,
Command::Threshold(cmd) => cmd.exec(&mut app).await?,
Command::Migrate(cmd) => cmd.exec(&mut app).await?,
}

Ok(())
Expand Down
Loading