Skip to content

Commit

Permalink
refactor: Major cli code refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
yanliu38 committed Jan 26, 2025
1 parent f7d15c2 commit 058e1b6
Show file tree
Hide file tree
Showing 23 changed files with 1,442 additions and 1,158 deletions.
54 changes: 54 additions & 0 deletions cli/src/commands/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::argparse::AccountArgs;
use crate::ledger::handle_funds_transfer;
use candid::Principal as IcPrincipal;
use dcc_common::{
account_balance_get_as_string, DccIdentity, IcrcCompatibleAccount, TokenAmountE9s,
DC_TOKEN_DECIMALS_DIV,
};
use std::path::PathBuf;

pub async fn handle_account_command(
account_args: AccountArgs,
network_url: &str,
ledger_canister_id: IcPrincipal,
identity: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let identity = identity.expect("Identity must be specified for this command, use --identity");
let dcc_id = DccIdentity::load_from_dir(&PathBuf::from(&identity))?;

println!("Account Principal ID: {}", dcc_id);
println!(
"Account balance: {} DCT",
account_balance_get_as_string(&dcc_id.as_icrc_compatible_account())
);

if let Some(to_principal_string) = &account_args.transfer_to {
let to_icrc1_account = IcrcCompatibleAccount::from(to_principal_string);

let transfer_amount_e9s = match &account_args.amount_dct {
Some(value) => {
(value.parse::<f64>()? * (DC_TOKEN_DECIMALS_DIV as f64)).round() as TokenAmountE9s
}
None => match &account_args.amount_e9s {
Some(value) => value.parse::<TokenAmountE9s>()?,
None => {
panic!("You must specify either --amount-dct or --amount-e9s")
}
},
};

println!(
"{}",
handle_funds_transfer(
network_url,
ledger_canister_id,
&dcc_id,
&to_icrc1_account,
transfer_amount_e9s,
)
.await?
);
}

Ok(())
}
281 changes: 281 additions & 0 deletions cli/src/commands/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
use crate::argparse::ContractCommands;
use crate::contracts::prompt_for_payment_entries;
use crate::utils::prompts::{prompt_bool, prompt_editor, prompt_input};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use dcc_common::{ContractSignReply, ContractSignRequest, DccIdentity};
use decent_cloud::ledger_canister_client::LedgerCanister;
use ledger_map::LedgerMap;
use std::path::PathBuf;

pub async fn handle_contract_command(
contract_args: ContractCommands,
network_url: &str,
ledger_canister_id: candid::Principal,
identity: Option<String>,
ledger_local: LedgerMap,
) -> Result<(), Box<dyn std::error::Error>> {
match contract_args {
ContractCommands::ListOpen(_list_open_args) => {
println!("Listing all open contracts...");
// A user may provide the identity (public key), but doesn't have to
let contracts_open = match identity {
Some(name) => {
let dcc_id = DccIdentity::load_from_dir(&PathBuf::from(&name)).unwrap();
let canister =
LedgerCanister::new_with_dcc_id(network_url, ledger_canister_id, &dcc_id)
.await?;
canister
.contracts_list_pending(&Some(dcc_id.to_bytes_verifying()))
.await
}
None => {
LedgerCanister::new_without_identity(network_url, ledger_canister_id)
.await?
.contracts_list_pending(&None)
.await
}
};
if contracts_open.is_empty() {
println!("No open contracts");
} else {
for open_contract in contracts_open {
println!(
"{}",
serde_json::to_string_pretty(&open_contract).unwrap_or_default()
);
}
}
}
ContractCommands::SignRequest(sign_req_args) => {
println!("Request to sign a contract...");
loop {
println!();
let i = sign_req_args.interactive;
let identity = prompt_input("Please enter the identity name", &identity, i, false);
let instance_id = prompt_input(
"Please enter the offering id",
&sign_req_args.offering_id,
i,
false,
);
let requester_ssh_pubkey = prompt_input(
"Please enter your ssh public key, which will be granted access to the contract",
&sign_req_args.requester_ssh_pubkey,
i,
false,
);
let requester_contact = prompt_input(
"Enter your contact information (this will be public)",
&sign_req_args.requester_contact,
i,
true,
);
let provider_pubkey_pem =
sign_req_args
.provider_pubkey_pem
.clone()
.unwrap_or_else(|| {
prompt_editor(
"# Enter the provider's public key below, as a PEM string",
i,
)
.lines()
.map(|line| {
line.split_once('#')
.map(|line| line.0)
.unwrap_or(line)
.trim()
.to_string()
})
.filter(|line| !line.is_empty())
.collect::<Vec<String>>()
.join("\n")
});
let provider_dcc_id =
match DccIdentity::new_verifying_from_pem(&provider_pubkey_pem) {
Ok(ident) => ident,
Err(e) => {
eprintln!("ERROR: Failed to parse provider pubkey: {}", e);
continue;
}
};
let provider_pubkey_bytes = provider_dcc_id.to_bytes_verifying();

// Find the offering with the given id, from the provider
let offerings = dcc_common::offerings::do_get_matching_offerings(
&ledger_local,
&format!("instance_types.id = \"{instance_id}\""),
)
.into_iter()
.filter(|o| o.0.to_bytes_verifying() == provider_pubkey_bytes)
.collect::<Vec<(DccIdentity, np_offering::Offering)>>();

let offering = match offerings.len() {
0 => {
eprintln!(
"ERROR: No offering found for the provider {provider_dcc_id} and id: {instance_id}"
);
continue;
}
1 => &offerings[0].1,
_ => {
eprintln!("ERROR: Provider {provider_dcc_id} has multiple offerings with id: {instance_id}");
continue;
}
};

let payment_entries = prompt_for_payment_entries(
&sign_req_args.payment_entries_json,
offering,
&instance_id,
);

let payment_amount_e9s = payment_entries.iter().map(|e| e.amount_e9s).sum();

let memo = prompt_input(
"Please enter a memo for the contract (this will be public)",
&sign_req_args.memo,
i,
true,
);

let dcc_id = DccIdentity::load_from_dir(&PathBuf::from(&identity))?;

let requester_pubkey_bytes = dcc_id.to_bytes_verifying();
let req = ContractSignRequest::new(
&requester_pubkey_bytes,
requester_ssh_pubkey,
requester_contact,
&provider_pubkey_bytes,
instance_id.clone(),
None,
None,
None,
payment_amount_e9s,
payment_entries,
None,
memo.clone(),
);
println!("The following contract sign request will be sent:");
println!("{}", serde_json::to_string_pretty(&req)?);
if dialoguer::Confirm::new()
.with_prompt("Is this correct? If yes, press enter to send.")
.default(false)
.show_default(true)
.interact()
.unwrap()
{
let payload_bytes = borsh::to_vec(&req).unwrap();
let payload_sig_bytes = dcc_id.sign(&payload_bytes)?.to_bytes();
let canister =
LedgerCanister::new_with_dcc_id(network_url, ledger_canister_id, &dcc_id)
.await?;

match canister
.contract_sign_request(
&requester_pubkey_bytes,
&payload_bytes,
&payload_sig_bytes,
)
.await
{
Ok(response) => {
println!("Contract sign request successful: {}", response);
break;
}
Err(e) => {
println!("Contract sign request failed: {}", e);
if dialoguer::Confirm::new()
.with_prompt("Do you want to retry?")
.default(true)
.show_default(true)
.interact()
.unwrap()
{
continue;
} else {
break;
}
}
}
} else {
println!("Contract sign request canceled.");
break;
}
}
}
ContractCommands::SignReply(sign_reply_args) => {
println!("Reply to a contract-sign request...");
loop {
let i = sign_reply_args.interactive;
let identity = prompt_input("Please enter the identity name", &identity, i, false);
let contract_id = prompt_input(
"Please enter the contract id, as a base64 encoded string",
&sign_reply_args.contract_id,
i,
false,
);
let accept = prompt_bool(
"Do you accept the contract?",
sign_reply_args.sign_accept,
i,
);
let response_text = prompt_input(
"Please enter a response text for the contract (this will be public)",
&sign_reply_args.response_text,
i,
true,
);
let response_details = prompt_input(
"Please enter a response details for the contract (this will be public)",
&sign_reply_args.response_details,
i,
true,
);

let dcc_id = DccIdentity::load_from_dir(&PathBuf::from(&identity)).unwrap();
let provider_pubkey_bytes = dcc_id.to_bytes_verifying();
let canister =
LedgerCanister::new_with_dcc_id(network_url, ledger_canister_id, &dcc_id)
.await?;

let contracts_open = canister
.contracts_list_pending(&Some(provider_pubkey_bytes.clone()))
.await;
let open_contract = contracts_open
.iter()
.find(|c| c.contract_id_base64 == contract_id)
.expect("Provided contract id not found");

let contract_id_bytes = BASE64.decode(contract_id.as_bytes()).unwrap();

let reply = ContractSignReply::new(
open_contract.contract_req.requester_pubkey_bytes().to_vec(),
open_contract.contract_req.request_memo(),
contract_id_bytes,
accept,
&response_text,
&response_details,
);

let payload_bytes = borsh::to_vec(&reply).unwrap();
let signature = dcc_id.sign(&payload_bytes)?.to_vec();

match canister
.contract_sign_reply(&provider_pubkey_bytes, &payload_bytes, &signature)
.await
{
Ok(response) => {
println!("Contract sign reply sent successfully: {}", response);
break;
}
Err(e) => {
println!("Error sending contract sign reply: {:?}", e);
}
}
}
}
}
Ok(())
}
Loading

0 comments on commit 058e1b6

Please sign in to comment.