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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Changed
- All command parameters have been moved to the end of the command. E.g. instead of `quill --pem-file $PEM <subcommand>`, it is now `quill <subcommand> --pem-file $PEM`.

## [0.2.17] - 2022-07-13

### Fixed
Expand Down
16 changes: 8 additions & 8 deletions e2e/tests-quill/create_neuron.bash
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ teardown() {

@test "basic create neuron" {
#account is initialized with 10_000 tokens
assert_command quill --insecure-local-dev-mode account-balance 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --yes
assert_command quill account-balance 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --yes --insecure-local-dev-mode
assert_string_match '(record { e8s = 1_000_000_000_000 : nat64 })'

# stake 3 tokens
assert_command bash -c "quill --pem-file $PEM_LOCATION/identity.pem neuron-stake --amount 3 --name myneur > stake.call"
assert_command bash -c "quill neuron-stake --amount 3 --name myneur --pem-file $PEM_LOCATION/identity.pem > stake.call"
assert_file_not_empty stake.call
SEND_OUTPUT="$(quill --insecure-local-dev-mode send stake.call --yes)"
SEND_OUTPUT="$(quill send stake.call --yes --insecure-local-dev-mode)"
assert_command echo "$SEND_OUTPUT" # replay the output so string matches work
echo "$SEND_OUTPUT"
assert_string_match "Method name: claim_or_refresh_neuron_from_account"
Expand All @@ -25,16 +25,16 @@ teardown() {
assert_string_match "record { result = opt variant { NeuronId = record { id =" #fragment of a correct response

# check that staking worked using get-neuron-info
assert_command bash -c "quill --insecure-local-dev-mode get-neuron-info $NEURON_ID --yes"
assert_command bash -c "quill get-neuron-info $NEURON_ID --yes --insecure-local-dev-mode"
assert_string_match 'stake_e8s = 300_000_000'

# increase dissolve delay by 6 months
assert_command bash -c "quill --pem-file $PEM_LOCATION/identity.pem neuron-manage --additional-dissolve-delay-seconds 15778800 $NEURON_ID > more-delay.call"
assert_command bash -c "quill neuron-manage --additional-dissolve-delay-seconds 15778800 --pem-file $PEM_LOCATION/identity.pem $NEURON_ID > more-delay.call"
assert_file_not_empty more-delay.call
assert_command quill --insecure-local-dev-mode send more-delay.call --yes #provides no interesting output on succes. Command not failing is good enough here
assert_command quill send more-delay.call --yes --insecure-local-dev-mode #provides no interesting output on succes. Command not failing is good enough here

# check that increasing dissolve delay worked, this time using list-neurons
assert_command bash -c "quill --pem-file $PEM_LOCATION/identity.pem list-neurons > neuron.call"
assert_command quill --insecure-local-dev-mode send neuron.call --yes
assert_command bash -c "quill list-neurons --pem-file $PEM_LOCATION/identity.pem > neuron.call"
assert_command quill send neuron.call --yes --insecure-local-dev-mode
assert_string_match "dissolve_delay_seconds = 15_778_800"
}
12 changes: 6 additions & 6 deletions src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context};
use bip39::{Language, Mnemonic};
use clap::Parser;
use rand::{rngs::OsRng, RngCore};
use std::path::Path;
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[clap(about, version, author)]
Expand All @@ -14,11 +14,11 @@ pub struct GenerateOpts {

/// File to write the seed phrase to.
#[clap(long, default_value = "seed.txt")]
seed_file: String,
seed_file: PathBuf,

/// File to write the PEM to.
#[clap(long)]
pem_file: Option<String>,
pem_file: Option<PathBuf>,

/// A seed phrase in quotes to use to generate the PEM file.
#[clap(long)]
Expand All @@ -35,11 +35,11 @@ pub struct GenerateOpts {

/// Generate or recover mnemonic seed phrase and/or PEM file.
pub fn exec(opts: GenerateOpts) -> AnyhowResult {
if Path::new(&opts.seed_file).exists() && !opts.overwrite_seed_file {
if opts.seed_file.exists() && !opts.overwrite_seed_file {
return Err(anyhow!("Seed file exists and overwrite is not set."));
}
if let Some(path) = &opts.pem_file {
if Path::new(path).exists() && !opts.overwrite_pem_file {
if path.exists() && !opts.overwrite_pem_file {
return Err(anyhow!("PEM file exists and overwrite is not set."));
}
}
Expand All @@ -64,7 +64,7 @@ pub fn exec(opts: GenerateOpts) -> AnyhowResult {
phrase.push('\n');
std::fs::write(opts.seed_file, phrase)?;
if let Some(path) = opts.pem_file {
std::fs::write(path, pem.clone())?;
std::fs::write(path, &pem)?;
}
let (principal_id, account_id) = crate::commands::public::get_ids(&AuthInfo::PemFile(pem))?;
println!("Principal id: {}", principal_id);
Expand Down
104 changes: 63 additions & 41 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! This module implements the command-line API.

use crate::lib::{AnyhowResult, AuthInfo};
use crate::{get_auth, lib::AnyhowResult, BaseOpts};
use anyhow::Context;
use clap::Parser;
use clap::{Args, Parser};
use std::io::{self, Write};
use tokio::runtime::Runtime;

Expand All @@ -28,66 +28,88 @@ pub use public::get_ids;
#[derive(Parser)]
pub enum Command {
/// Prints the principal id and the account id.
PublicIds(public::PublicOpts),
Send(send::SendOpts),
Transfer(transfer::TransferOpts),
PublicIds(BaseOpts<public::PublicOpts>),
Send(BaseOpts<send::SendOpts>),
Transfer(BaseOpts<transfer::TransferOpts>),
/// Claim seed neurons from the Genesis Token Canister.
ClaimNeurons,
NeuronStake(neuron_stake::StakeOpts),
NeuronManage(neuron_manage::ManageOpts),
ClaimNeurons(BaseOpts<Empty>),
NeuronStake(BaseOpts<neuron_stake::StakeOpts>),
NeuronManage(BaseOpts<neuron_manage::ManageOpts>),
/// Signs the query for all neurons belonging to the signing principal.
ListNeurons(list_neurons::ListNeuronsOpts),
ListProposals(list_proposals::ListProposalsOpts),
GetProposalInfo(get_proposal_info::GetProposalInfoOpts),
GetNeuronInfo(get_neuron_info::GetNeuronInfoOpts),
ListNeurons(BaseOpts<list_neurons::ListNeuronsOpts>),
ListProposals(BaseOpts<list_proposals::ListProposalsOpts>),
GetProposalInfo(BaseOpts<get_proposal_info::GetProposalInfoOpts>),
GetNeuronInfo(BaseOpts<get_neuron_info::GetNeuronInfoOpts>),
/// Queries a ledger account balance.
AccountBalance(account_balance::AccountBalanceOpts),
AccountBalance(BaseOpts<account_balance::AccountBalanceOpts>),
/// Update node provider details
UpdateNodeProvider(update_node_provider::UpdateNodeProviderOpts),
ReplaceNodeProviderId(replace_node_provide_id::ReplaceNodeProviderIdOpts),
UpdateNodeProvider(BaseOpts<update_node_provider::UpdateNodeProviderOpts>),
ReplaceNodeProviderId(BaseOpts<replace_node_provide_id::ReplaceNodeProviderIdOpts>),
/// Generate a mnemonic seed phrase and generate or recover PEM.
Generate(generate::GenerateOpts),
Generate(BaseOpts<generate::GenerateOpts>),
/// Print QR Scanner dapp QR code: scan to start dapp to submit QR results.
ScannerQRCode,
/// Print QR code for data e.g. principal id.
QRCode(qrcode::QRCodeOpts),
QRCode(BaseOpts<qrcode::QRCodeOpts>),
}

pub fn exec(auth: &AuthInfo, qr: bool, fetch_root_key: bool, cmd: Command) -> AnyhowResult {
#[derive(Args)]
pub struct Empty;

pub fn dispatch(cmd: Command) -> AnyhowResult {
let runtime = Runtime::new().expect("Unable to create a runtime");
match cmd {
Command::PublicIds(opts) => public::exec(auth, opts),
Command::Transfer(opts) => transfer::exec(auth, opts).and_then(|out| print_vec(qr, &out)),
Command::PublicIds(opts) => public::exec(&get_auth(opts.global_opts)?, opts.command_opts)?,
Command::Transfer(opts) => {
let qr = opts.global_opts.qr;
let out = transfer::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print_vec(qr, &out)?;
}
Command::NeuronStake(opts) => {
neuron_stake::exec(auth, opts).and_then(|out| print_vec(qr, &out))
let qr = opts.global_opts.qr;
let out = neuron_stake::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print_vec(qr, &out)?;
}
Command::NeuronManage(opts) => {
neuron_manage::exec(auth, opts).and_then(|out| print_vec(qr, &out))
let qr = opts.global_opts.qr;
let out = neuron_manage::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print_vec(qr, &out)?;
}
Command::ListNeurons(opts) => {
list_neurons::exec(auth, opts).and_then(|out| print_vec(qr, &out))
}
Command::ClaimNeurons => claim_neurons::exec(auth).and_then(|out| print_vec(qr, &out)),
Command::ListProposals(opts) => {
runtime.block_on(async { list_proposals::exec(opts, fetch_root_key).await })
let qr = opts.global_opts.qr;
let out = list_neurons::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print_vec(qr, &out)?;
}
Command::GetProposalInfo(opts) => {
runtime.block_on(async { get_proposal_info::exec(opts, fetch_root_key).await })
}
Command::GetNeuronInfo(opts) => {
runtime.block_on(async { get_neuron_info::exec(opts, fetch_root_key).await })
}
Command::AccountBalance(opts) => {
runtime.block_on(async { account_balance::exec(opts, fetch_root_key).await })
Command::ClaimNeurons(opts) => {
let qr = opts.global_opts.qr;
claim_neurons::exec(&get_auth(opts.global_opts)?)
.and_then(|out| print_vec(qr, &out))?;
}
Command::ListProposals(opts) => runtime.block_on(async {
list_proposals::exec(opts.command_opts, opts.global_opts.fetch_root_key).await
})?,
Command::GetProposalInfo(opts) => runtime.block_on(async {
get_proposal_info::exec(opts.command_opts, opts.global_opts.fetch_root_key).await
})?,
Command::GetNeuronInfo(opts) => runtime.block_on(async {
get_neuron_info::exec(opts.command_opts, opts.global_opts.fetch_root_key).await
})?,
Command::AccountBalance(opts) => runtime.block_on(async {
account_balance::exec(opts.command_opts, opts.global_opts.fetch_root_key).await
})?,
Command::UpdateNodeProvider(opts) => {
update_node_provider::exec(auth, opts).and_then(|out| print(&out))
let out = update_node_provider::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print(&out)?;
}
Command::ReplaceNodeProviderId(opts) => {
replace_node_provide_id::exec(auth, opts).and_then(|out| print(&out))
let out =
replace_node_provide_id::exec(&get_auth(opts.global_opts)?, opts.command_opts)?;
print(&out)?;
}
Command::Send(opts) => runtime.block_on(async { send::exec(opts, fetch_root_key).await }),
Command::Generate(opts) => generate::exec(opts),
Command::Send(opts) => runtime.block_on(async {
send::exec(opts.command_opts, opts.global_opts.fetch_root_key).await
})?,
Command::Generate(opts) => generate::exec(opts.command_opts)?,
// QR code for URL: https://p5deo-6aaaa-aaaab-aaaxq-cai.raw.ic0.app/
// Source code: https://github.com/ninegua/ic-qr-scanner
Command::ScannerQRCode => {
Expand All @@ -113,10 +135,10 @@ pub fn exec(auth: &AuthInfo, qr: bool, fetch_root_key: bool, cmd: Command) -> An
█████████████████████████████████████
█████████████████████████████████████"
);
Ok(())
}
Command::QRCode(opts) => qrcode::exec(opts),
Command::QRCode(opts) => qrcode::exec(opts.command_opts)?,
}
Ok(())
}

// Using println! for printing to STDOUT and piping it to other tools leads to
Expand Down
4 changes: 3 additions & 1 deletion src/commands/qrcode.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use crate::lib::{read_from_file, AnyhowResult};
use clap::Parser;
use qrcodegen::{QrCode, QrCodeEcc};
Expand All @@ -6,7 +8,7 @@ use qrcodegen::{QrCode, QrCodeEcc};
pub struct QRCodeOpts {
/// File the contents of which to be output as a QRCode.
#[clap(long)]
file: Option<String>,
file: Option<PathBuf>,

// String to be output as a QRCode.
#[clap(long)]
Expand Down
3 changes: 2 additions & 1 deletion src/commands/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ic_agent::{agent::http_transport::ReqwestHttpReplicaV2Transport, RequestId};
use ic_types::principal::Principal;
use ledger_canister::{ICPTs, Subaccount};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::str::FromStr;

#[derive(
Expand Down Expand Up @@ -49,7 +50,7 @@ pub struct SendArgs {
#[derive(Parser)]
pub struct SendOpts {
/// Path to the signed message
file_name: String,
file_name: PathBuf,

/// Will display the signed message, but not send it.
#[clap(long)]
Expand Down
8 changes: 4 additions & 4 deletions src/lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use simple_asn1::ASN1Block::{
BitString, Explicit, Integer, ObjectIdentifier, OctetString, Sequence,
};
use simple_asn1::{oid, to_der, ASN1Class, BigInt, BigUint};
use std::env::VarError;
use std::path::PathBuf;
use std::{env::VarError, path::Path};

pub const IC_URL: &str = "https://ic0.app";

Expand Down Expand Up @@ -160,13 +160,13 @@ pub fn get_candid_type(idl: String, method_name: &str) -> Option<(TypeEnv, Funct
}

/// Reads from the file path or STDIN and returns the content.
pub fn read_from_file(path: &str) -> AnyhowResult<String> {
pub fn read_from_file(path: impl AsRef<Path>) -> AnyhowResult<String> {
use std::io::Read;
let path = path.as_ref();
let mut content = String::new();
if path == "-" {
if path == Path::new("-") {
std::io::stdin().read_to_string(&mut content)?;
} else {
let path = std::path::Path::new(&path);
let mut file = std::fs::File::open(&path).context("Cannot open the message file.")?;
file.read_to_string(&mut content)
.context("Cannot read the message file.")?;
Expand Down
Loading