Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

feat: Add stake-maturity command #26

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
253 changes: 130 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ path = "src/main.rs"
anyhow = "1.0.34"
async-trait = "0.1.58"
base64 = "0.13.0"
bip39 = "1.0.1"
candid = "0.8.1"
candid_derive = "0.5.0"
clap = { version = "3.1.6", features = ["derive", "cargo"] }
flate2 = "1.0.22"
hex = {version = "0.4.2", features = ["serde"] }
ic-agent = "0.22.0"
ic-base-types = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-ledger-core = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-nns-constants = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-sns-governance = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-sns-root = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-sns-swap = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-sns-wasm = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-icrc1 = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
ic-base-types = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-ledger-core = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-nns-constants = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-sns-governance = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-sns-root = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-sns-swap = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-sns-wasm = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-icrc1 = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
ic-types = "0.4.1"
icp-ledger = { git = "https://github.com/dfinity/ic", rev = "1fc0208b9aeed0554b1be2711605e5b54ace9d6a" }
icp-ledger = { git = "https://github.com/dfinity/ic", rev = "e311a082c34d099cc839ae6db6c32b1ef3f14955" }
libsecp256k1 = "0.7.0"
num-bigint = { version = "0.4.3", features = ["serde"] }
openssl = "0.10.32"
Expand All @@ -42,6 +41,7 @@ serde = { version = "1.0.130", features = ["derive"] }
serde_bytes = "0.11.2"
serde_cbor = "0.11.2"
serde_json = "1.0.57"
tiny-bip39 = "1.0.0"
tiny-hderive = "0.3.0"
tokio = { version = "1.2.0", features = [ "fs" ] }

Expand Down
62 changes: 42 additions & 20 deletions candid/icp_ledger.did
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type ICPTs = record {
type Tokens = record {
e8s : nat64;
};

Expand All @@ -7,57 +7,76 @@ type Duration = record {
nanos: nat32;
};

// Number of nanoseconds from the UNIX epoch in UTC timezone.
type TimeStamp = record {
timestamp_nanos: nat64;
};

type ArchiveOptions = record {
node_max_memory_size_bytes: opt nat32;
max_message_size_bytes: opt nat32;
trigger_threshold : nat64;
num_blocks_to_archive : nat64;
node_max_memory_size_bytes: opt nat64;
max_message_size_bytes: opt nat64;
controller_id: principal;
cycles_for_archive_creation: opt nat64;
};

type BlockHeight = nat64;
// Height of a ledger block.
type BlockIndex = nat64;

// A number associated with a transaction.
// Can be set by the caller in `send` call as a correlation identifier.
type Memo = nat64;

// Account identifier encoded as a 64-byte ASCII hex string.
type AccountIdentifier = text;
type SubAccount = vec nat8;

// Subaccount is an arbitrary 32-byte byte array.
type SubAccount = blob;

type Transfer = variant {
Burn: record {
from: AccountIdentifier;
amount: ICPTs;
amount: Tokens;
};
Mint: record {
to: AccountIdentifier;
amount: ICPTs;
amount: Tokens;
};
Send: record {
from: AccountIdentifier;
to: AccountIdentifier;
amount: ICPTs;
amount: Tokens;
};
};

type Transaction = record {
transfer: Transfer;
memo: Memo;
created_at: BlockHeight;
created_at: BlockIndex;
};

// Arguments for the `send_dfx` call.
type SendArgs = record {
memo: Memo;
amount: ICPTs;
fee: ICPTs;
amount: Tokens;
fee: Tokens;
from_subaccount: opt SubAccount;
to: AccountIdentifier;
created_at_time: opt TimeStamp;
};

// Arguments for the `notify` call.
type NotifyCanisterArgs = record {
block_height: BlockHeight;
max_fee: ICPTs;
// The of the block to send a notification about.
block_height: BlockIndex;
// Max fee, should be 10000 e8s.
max_fee: Tokens;
// Subaccount the payment came from.
from_subaccount: opt SubAccount;
// Canister that received the payment.
to_canister: principal;
// Subaccount that received the payment.
to_subaccount: opt SubAccount;
};

Expand All @@ -67,15 +86,18 @@ type AccountBalanceArgs = record {

type LedgerCanisterInitPayload = record {
minting_account: AccountIdentifier;
initial_values: vec record {AccountIdentifier; ICPTs};
max_message_size_bytes: opt nat32;
initial_values: vec record {AccountIdentifier; Tokens};
max_message_size_bytes: opt nat64;
transaction_window: opt Duration;
archive_options: opt ArchiveOptions;
send_whitelist: vec record {principal};
}
send_whitelist: vec principal;
transfer_fee: opt Tokens;
token_symbol: opt text;
token_name: opt text;
};

service: (LedgerCanisterInitPayload) -> {
send_dfx : (SendArgs) -> (BlockHeight);
send_dfx : (SendArgs) -> (BlockIndex);
notify_dfx: (NotifyCanisterArgs) -> ();
account_balance_dfx : (AccountBalanceArgs) -> (ICPTs) query;
}
account_balance_dfx : (AccountBalanceArgs) -> (Tokens) query;
}
2 changes: 1 addition & 1 deletion candid/ledger.did
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ service : (InitArgs) -> {
icrc1_total_supply : () -> (Tokens) query;
icrc1_fee : () -> (Tokens) query;
icrc1_minting_account : () -> (opt Account) query;

icrc1_balance_of : (Account) -> (Tokens) query;
icrc1_transfer : (TransferArg) -> (TransferResult);
icrc1_supported_standards : () -> (vec record { name : text; url : text }) query;
Expand Down
2 changes: 1 addition & 1 deletion candid/root.did
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ service : (SnsRootCanister) -> {
set_dapp_controllers : (SetDappControllersRequest) -> (
SetDappControllersResponse,
);
}
}
2 changes: 1 addition & 1 deletion candid/snsw.did
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ service : (SnsWasmCanisterInitPayload) -> {
update_sns_subnet_list : (UpdateSnsSubnetListRequest) -> (
UpdateSnsSubnetListResponse,
);
}
}
13 changes: 7 additions & 6 deletions candid/swap.did
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ type DerivedState = record {
buyer_total_icp_e8s : nat64;
};
type DirectInvestment = record { buyer_principal : text };
type ErrorRefundIcpRequest = record {
icp_e8s : nat64;
fee_override_e8s : nat64;
};
type Err = record { description : opt text; error_type : opt int32 };
type ErrorRefundIcpRequest = record { source_principal_id : opt principal };
type ErrorRefundIcpResponse = record { result : opt Result };
type FailedUpdate = record {
err : opt CanisterCallError;
dapp_canister_id : opt principal;
Expand Down Expand Up @@ -70,6 +69,7 @@ type NeuronBasketConstructionParameters = record {
dissolve_delay_interval_seconds : nat64;
count : nat64;
};
type Ok = record { block_height : opt nat64 };
type OpenRequest = record {
cf_participants : vec CfParticipant;
params : opt Params;
Expand Down Expand Up @@ -97,6 +97,7 @@ type RefreshBuyerTokensResponse = record {
icp_ledger_account_balance_e8s : nat64;
};
type Response = record { governance_error : opt GovernanceError };
type Result = variant { Ok : Ok; Err : Err };
type SetDappControllersCallResult = record { possibility : opt Possibility_1 };
type SetDappControllersResponse = record { failed_updates : vec FailedUpdate };
type SetModeCallResult = record { possibility : opt Possibility_2 };
Expand Down Expand Up @@ -124,7 +125,7 @@ type TransferableAmount = record {
transfer_success_timestamp_seconds : nat64;
};
service : (Init) -> {
error_refund_icp : (ErrorRefundIcpRequest) -> (record {});
error_refund_icp : (ErrorRefundIcpRequest) -> (ErrorRefundIcpResponse);
finalize_swap : (record {}) -> (FinalizeSwapResponse);
get_buyer_state : (GetBuyerStateRequest) -> (GetBuyerStateResponse) query;
get_buyers_total : (record {}) -> (GetBuyersTotalResponse);
Expand All @@ -135,4 +136,4 @@ service : (Init) -> {
RefreshBuyerTokensResponse,
);
restore_dapp_controllers : (record {}) -> (SetDappControllersCallResult);
}
}
11 changes: 5 additions & 6 deletions src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,17 @@ pub fn exec(opts: GenerateOpts) -> AnyhowResult {
_ => return Err(anyhow!("Words must be 12 or 24.")),
};
let mnemonic = match opts.phrase {
Some(phrase) => Mnemonic::parse(phrase).context("Failed to parse mnemonic")?,
Some(phrase) => {
Mnemonic::from_phrase(&phrase, Language::English).context("Failed to parse mnemonic")?
}
None => {
let mut key = vec![0u8; bytes];
OsRng.fill_bytes(&mut key);
Mnemonic::from_entropy_in(Language::English, &key).unwrap()
Mnemonic::from_entropy(&key, Language::English).unwrap()
}
};
let pem = mnemonic_to_pem(&mnemonic).context("Failed to convert mnemonic to PEM")?;
let mut phrase = mnemonic
.word_iter()
.collect::<Vec<&'static str>>()
.join(" ");
let mut phrase = mnemonic.into_phrase();
phrase.push('\n');
std::fs::write(opts.seed_file, phrase)?;
if let Some(path) = opts.pem_file {
Expand Down
23 changes: 5 additions & 18 deletions src/commands/get_swap_refund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,22 @@ use crate::{
SnsCanisterIds,
};

use super::transfer;
use super::public::get_ids;

/// Signs a message to request a refund from the SNS swap canister.
/// If the swap was aborted or failed, or some of your contributed ICP never made it into a neuron,
/// this command can retrieve your unused ICP, minus transaction fees.
#[derive(Parser)]
pub struct GetSwapRefundOpts {
/// The amount of ICP to request a refund for.
#[clap(long)]
amount: String,
/// The expected transaction fee. If omitted, defaults to 0.0001 ICP.
#[clap(long)]
fee: Option<String>,
}
pub struct GetSwapRefundOpts;

pub fn exec(
pem: &str,
sns_canister_ids: &SnsCanisterIds,
opts: GetSwapRefundOpts,
_: GetSwapRefundOpts,
) -> AnyhowResult<Vec<IngressWithRequestId>> {
let tokens = transfer::parse_tokens(&opts.amount)?.get_e8s();
let fee = opts
.fee
.map(|fee| anyhow::Ok(transfer::parse_tokens(&fee)?.get_e8s()))
.transpose()?
.unwrap_or(10_000);
let (principal, _) = get_ids(&Some(pem.to_string()))?;
let message = ErrorRefundIcpRequest {
icp_e8s: tokens,
fee_override_e8s: fee,
source_principal_id: Some(principal.into()),
};
let req = sign_ingress_with_request_status_query(
pem,
Expand Down
7 changes: 7 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod qrcode;
mod register_vote;
mod request_status;
mod send;
mod stake_maturity;
mod stake_neuron;
mod status;
mod swap;
Expand All @@ -39,6 +40,7 @@ pub enum Command {
/// provided private key and memo. Second, stake-neuron will sign a ManageNeuron message for
/// Governance to claim the neuron for the principal derived from the provided private key.
StakeNeuron(stake_neuron::StakeNeuronOpts),
StakeMaturity(stake_maturity::StakeMaturityOpts),
/// Signs a ManageNeuron message to configure the dissolve delay of a neuron. With this command
/// neuron holders can start dissolving, stop dissolving, or increase dissolve delay. The
/// dissolve delay of a neuron determines its voting power, its ability to vote, its ability
Expand Down Expand Up @@ -95,6 +97,11 @@ pub fn exec(
let canister_ids = require_canister_ids(sns_canister_ids)?;
stake_neuron::exec(&pem, &canister_ids, opts).and_then(|out| print_vec(qr, &out))
}
Command::StakeMaturity(opts) => {
let pem = require_pem(private_key_pem)?;
let canister_ids = require_canister_ids(sns_canister_ids)?;
stake_maturity::exec(&pem, &canister_ids, opts).and_then(|out| print_vec(qr, &out))
}
Command::ConfigureDissolveDelay(opts) => {
let pem = require_pem(private_key_pem)?;
let canister_ids = require_canister_ids(sns_canister_ids)?;
Expand Down
56 changes: 56 additions & 0 deletions src/commands/stake_maturity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use anyhow::Error;
use candid::Encode;
use clap::Parser;
use ic_base_types::PrincipalId;
use ic_sns_governance::pb::v1::{
manage_neuron::{Command, StakeMaturity},
ManageNeuron,
};

use crate::{
lib::{
parse_neuron_id,
signing::{sign_ingress_with_request_status_query, IngressWithRequestId},
AnyhowResult, TargetCanister,
},
SnsCanisterIds,
};

/// Signs a ManageNeuron message to stake a percentage of a neuron's maturity.
///
/// A neuron's total stake is the combination of its staked governance tokens and staked maturity.
#[derive(Parser)]
pub struct StakeMaturityOpts {
/// The percentage of the current maturity to stake (1-100).
#[clap(long, value_parser = 1..100)]
percentage: i64,
/// The id of the neuron to configure as a hex encoded string. For example:
/// 83a7d2b12f654ff58335e5a2512ccae0d7839c744b1807a47c96f5b9f3969069
neuron_id: String,
}

pub fn exec(
pem: &str,
sns_canister_ids: &SnsCanisterIds,
opts: StakeMaturityOpts,
) -> AnyhowResult<Vec<IngressWithRequestId>> {
let neuron_id = parse_neuron_id(opts.neuron_id)?;
let neuron_subaccount = neuron_id.subaccount().map_err(Error::msg)?;

let governance_canister_id = PrincipalId::from(sns_canister_ids.governance_canister_id);

let command = ManageNeuron {
command: Some(Command::StakeMaturity(StakeMaturity {
percentage_to_stake: Some(opts.percentage as u32),
})),
subaccount: neuron_subaccount.to_vec(),
};

let message = sign_ingress_with_request_status_query(
pem,
"manage_neuron",
Encode!(&command)?,
TargetCanister::Governance(governance_canister_id.0),
)?;
Ok(vec![message])
}
Loading