Skip to content
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
50 changes: 47 additions & 3 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "quill"
version = "0.5.4"
authors = ["DFINITY Team"]
edition = "2021"
rust-version = "1.75.0"
rust-version = "1.82.0"
description = "Minimalistic ledger and governance toolkit for cold wallets."
repository = "https://github.com/dfinity/quill"
license = "Apache-2.0"
Expand All @@ -15,6 +15,7 @@ license = "Apache-2.0"
# lived in the ic repo.)
ic-base-types = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
ic-ckbtc-minter = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
ic-nervous-system-clients = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
ic-nns-common = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
ic-nns-constants = { git = "https://github.com/dfinity/ic", rev = "b4b0230aa1ed1c94d3674a9a27d60b724edb5cd4" }
Expand All @@ -34,6 +35,7 @@ ic-agent = { git = "https://github.com/dfinity/agent-rs", rev = "6e11a350112f9b9
ic-identity-hsm = { git = "https://github.com/dfinity/agent-rs", rev = "6e11a350112f9b907c4d590d8217f340e153d898", optional = true }

anyhow = "1.0.34"
askama = "0.14"
base64 = "0.13.0"
bigdecimal = "0.4"
bip32 = "0.5.0"
Expand Down
3 changes: 3 additions & 0 deletions askama.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[general]
dirs = ["src/lib/format/templates"]
whitespace = "minimize"
3 changes: 3 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("cargo::rerun-if-changed=src/lib/format/templates/")
}
146 changes: 70 additions & 76 deletions src/lib/format/ckbtc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use askama::Template;
use candid::Decode;
use ic_ckbtc_minter::{
state::{ReimbursementReason, RetrieveBtcStatus, RetrieveBtcStatusV2},
Expand All @@ -6,87 +7,89 @@ use ic_ckbtc_minter::{
update_balance::{UpdateBalanceError, UtxoStatus},
},
};
use std::fmt::Write;

use crate::lib::{e8s_to_tokens, AnyhowResult};
use crate::lib::{format::filters, AnyhowResult};

pub fn display_update_balance(blob: &[u8]) -> AnyhowResult<String> {
let result = Decode!(blob, Result<Vec<UtxoStatus>, UpdateBalanceError>)?;
let fmt = match result {
Ok(statuses) => {
let mut fmt = String::new();
for status in statuses {
match status {
UtxoStatus::Minted { block_index, minted_amount, utxo } => writeln!(fmt, "{txid}({btc} BTC): Minted {ckbtc} ckBTC at block index {block_index}", txid = utxo.outpoint.txid, btc = e8s_to_tokens(utxo.value.into()), ckbtc = e8s_to_tokens(minted_amount.into()))?,
UtxoStatus::ValueTooSmall(utxo) => writeln!(fmt,"{txid}({btc} BTC): UTXO rejected: too small to cover KYT cost", txid = utxo.outpoint.txid, btc = e8s_to_tokens(utxo.value.into()))?,
UtxoStatus::Tainted(utxo) => writeln!(fmt, "{txid}({btc} BTC): UTXO rejected: the KYT process determined the BTC is tainted", txid = utxo.outpoint.txid, btc = e8s_to_tokens(utxo.value.into()))?,
UtxoStatus::Checked(utxo) => writeln!(fmt, "{txid}({btc} BTC): The deposted BTC cleared the KYT check, but minting ckBTC failed. Retry this command.", txid = utxo.outpoint.txid, btc = e8s_to_tokens(utxo.value.into()))?,
}
use UtxoStatus::*;
#[derive(Template)]
#[template(path = "ckbtc/update_balance.txt")]
struct UpdateBalance {
statuses: Vec<UtxoStatus>,
}
fmt
UpdateBalance { statuses }.render()?
}
Err(e) => match e {
UpdateBalanceError::GenericError { error_message, .. } => {
format!("ckBTC error: {error_message}")
Err(error) => {
use Err::*;
#[derive(Template)]
#[template(path = "ckbtc/update_balance_err.txt")]
struct UpdateBalanceErr {
error: Err,
}
UpdateBalanceError::AlreadyProcessing => {
"ckBTC error: already processing another update_balance call for the same account"
.to_string()
enum Err {
GenericError {
error_message: String,
},
AlreadyProcessing,
NoNewUtxos {
current: u32,
required: u32,
pending: Option<usize>,
suspended: usize,
},
TemporarilyUnavailable(String),
}
UpdateBalanceError::NoNewUtxos {
current_confirmations,
required_confirmations,
pending_utxos,
suspended_utxos,
} => {
let mut fmt = "ckBTC error: no new confirmed UTXOs to process".to_string();
if let Some(pending_utxos) = pending_utxos {
write!(
fmt,
" ({} unconfirmed, needing {} confirmations but having {}, {} ignored)",
pending_utxos.len(),
UpdateBalanceErr {
error: match error {
UpdateBalanceError::AlreadyProcessing => Err::AlreadyProcessing,
UpdateBalanceError::GenericError { error_message, .. } => {
Err::GenericError { error_message }
}
UpdateBalanceError::NoNewUtxos {
current_confirmations,
required_confirmations,
current_confirmations.unwrap_or_default(),
suspended_utxos.unwrap_or_default().len(),
)?;
}
fmt
pending_utxos,
suspended_utxos,
} => Err::NoNewUtxos {
current: current_confirmations.unwrap_or_default(),
required: required_confirmations,
pending: pending_utxos.map(|v| v.len()),
suspended: suspended_utxos.unwrap_or_default().len(),
},
UpdateBalanceError::TemporarilyUnavailable(msg) => {
Err::TemporarilyUnavailable(msg)
}
},
}
UpdateBalanceError::TemporarilyUnavailable(e) => {
format!("ckBTC error: temporarily unavailable: {e}. Try again later.")
}
},
.render()?
}
};
Ok(fmt)
}

pub fn display_retrieve_btc(blob: &[u8]) -> AnyhowResult<String> {
let result = Decode!(blob, Result<RetrieveBtcOk, RetrieveBtcError>)?;
let fmt = match result {
Ok(ok) => format!("Begun retrieval process at block index {}", ok.block_index),
Err(e) => match e {
RetrieveBtcError::GenericError { error_message, .. } => {
format!("ckBTC error: {error_message}")
}
RetrieveBtcError::AmountTooLow(min) => format!(
"ckBTC error: amount too low to withdraw (min: {})",
e8s_to_tokens(min.into())
),
RetrieveBtcError::InsufficientFunds { balance } => format!(
"ckBTC error: the withdrawal account does not have enough ckBTC (balance: {})",
e8s_to_tokens(balance.into())
),
RetrieveBtcError::MalformedAddress(msg) => {
format!("ckBTC error: malformed address: {msg}")
}
RetrieveBtcError::AlreadyProcessing => {
"ckBTC error: already processing another retrieve_btc call for the same account"
.to_string()
Ok(status) => {
#[derive(Template)]
#[template(path = "ckbtc/retrieve_btc.txt")]
struct RetrieveBtc {
status: RetrieveBtcOk,
}
RetrieveBtcError::TemporarilyUnavailable(msg) => {
format!("ckBTC error: temporarily unavailable: {msg}")
RetrieveBtc { status }.render()?
}
Err(error) => {
use RetrieveBtcError::*;
#[derive(Template)]
#[template(path = "ckbtc/retrieve_btc_err.txt")]
struct RetrieveBtcErr {
error: RetrieveBtcError,
}
},
RetrieveBtcErr { error }.render()?
}
};
Ok(fmt)
}
Expand All @@ -102,21 +105,12 @@ pub fn display_retrieve_btc_status_v2(blob: &[u8]) -> AnyhowResult<String> {
}

fn display_retrieve_btc_status_internal(status: RetrieveBtcStatusV2) -> String {
match status {
RetrieveBtcStatusV2::AmountTooLow => "ckBTC error: amount too low to withdraw".to_string(),
RetrieveBtcStatusV2::Unknown => "ckBTC error: request ID invalid or too old".to_string(),
RetrieveBtcStatusV2::Pending => "The BTC transaction is pending in the queue".to_string(),
RetrieveBtcStatusV2::Signing => "The BTC transaction is being signed".to_string(),
RetrieveBtcStatusV2::Sending { txid } => format!("The BTC transaction is being sent (id {txid})"),
RetrieveBtcStatusV2::Submitted { txid } => format!("The BTC transaction has been sent, awaiting confirmations (id {txid})"),
RetrieveBtcStatusV2::Confirmed { txid } => format!("The BTC transaction has been completed (id {txid})"),
RetrieveBtcStatusV2::WillReimburse(task) => match task.reason {
ReimbursementReason::CallFailed => format!("The BTC transaction failed. {amount} ckBTC is being reimbursed to {account}", amount = e8s_to_tokens(task.amount.into()), account = task.account),
ReimbursementReason::TaintedDestination { kyt_provider, kyt_fee } => format!("The KYT process determined that the BTC destination is tainted. {amount} ckBTC is being reimbursed to {account}\nKYT fee: {fee}, provider: {kyt_provider}", amount = e8s_to_tokens(task.amount.into()), fee = e8s_to_tokens(kyt_fee.into()), account = task.account)
}
RetrieveBtcStatusV2::Reimbursed(reimbursed) => match reimbursed.reason {
ReimbursementReason::CallFailed => format!("The BTC transaction failed. {amount} ckBTC has been reimbursed to {account} at block index {index}", amount = reimbursed.amount, account = reimbursed.account, index = reimbursed.mint_block_index),
ReimbursementReason::TaintedDestination { kyt_provider, kyt_fee } => format!("The KYT process determined that the BTC destination is tainted. {amount} ckBTC has been reimbursed to {account} at block index {index}\nKYT fee: {fee}, provider: {kyt_provider}", amount = e8s_to_tokens(reimbursed.amount.into()), account = reimbursed.account, index = reimbursed.mint_block_index, fee = e8s_to_tokens(kyt_fee.into()))
}
use ReimbursementReason::*;
use RetrieveBtcStatusV2::*;
#[derive(Template)]
#[template(path = "ckbtc/retrieve_btc_status.txt")]
struct RetrieveBtcStatus {
status: RetrieveBtcStatusV2,
}
RetrieveBtcStatus { status }.render().unwrap()
}
15 changes: 10 additions & 5 deletions src/lib/format/gtc.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use askama::Template;
use candid::Decode;
use ic_nns_common::pb::v1::NeuronId;
use itertools::Itertools;

use crate::lib::AnyhowResult;

pub fn format_claim_neurons(blob: &[u8]) -> AnyhowResult<String> {
#[derive(Template)]
#[template(path = "claim_neurons.txt")]
struct ClaimNeurons {
ids: Vec<u64>,
}
let result = Decode!(blob, Result<Vec<NeuronId>, String>)?;
let fmt = match result {
Ok(ids) => format!(
"Claimed neurons {}",
ids.iter().map(|id| id.id).format(", ")
),
Ok(ids) => ClaimNeurons {
ids: ids.iter().map(|id| id.id).collect(),
}
.render()?,
Err(e) => format!("NNS error: {e}"),
};
Ok(fmt)
Expand Down
25 changes: 0 additions & 25 deletions src/lib/format/icp_ledger.rs

This file was deleted.

17 changes: 0 additions & 17 deletions src/lib/format/icrc1.rs

This file was deleted.

Loading
Loading