From 8cea90a5184c4796792e64e5358ea394ac610fc4 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Fri, 16 Jun 2023 16:34:08 -0700 Subject: [PATCH 1/2] Overhaul CLI --- CHANGELOG.md | 1 + Cargo.lock | 146 ++++++++--- Cargo.toml | 2 + README.md | 5 +- .../ckbtc/quill-ckbtc-balance.md | 10 +- docs/cli-reference/index.md | 23 +- ...ll-account-balance.md => quill-balance.md} | 23 +- docs/cli-reference/quill-claim-neurons.md | 4 +- docs/cli-reference/quill-community-fund.md | 61 +++++ docs/cli-reference/quill-disburse.md | 78 ++++++ docs/cli-reference/quill-dissolve-delay.md | 69 +++++ docs/cli-reference/quill-dissolve.md | 60 +++++ docs/cli-reference/quill-follow.md | 92 +++++++ docs/cli-reference/quill-hotkey.md | 68 +++++ docs/cli-reference/quill-merge.md | 61 +++++ docs/cli-reference/quill-neuron-manage.md | 241 ------------------ docs/cli-reference/quill-parent.md | 4 +- docs/cli-reference/quill-spawn.md | 76 ++++++ docs/cli-reference/quill-split.md | 65 +++++ docs/cli-reference/quill-stake-maturity.md | 94 +++++++ ...-neuron-stake.md => quill-stake-neuron.md} | 12 +- docs/cli-reference/quill-transfer.md | 7 +- docs/cli-reference/quill-vote.md | 70 +++++ docs/cli-reference/sns/quill-sns-balance.md | 2 + .../sns/quill-sns-configure-dissolve-delay.md | 38 --- docs/cli-reference/sns/quill-sns-disburse.md | 4 +- .../sns/quill-sns-dissolve-delay.md | 39 +++ docs/cli-reference/sns/quill-sns-dissolve.md | 34 +++ ...s-follow-neuron.md => quill-sns-follow.md} | 6 +- .../sns/quill-sns-get-sale-participation.md | 2 + .../sns/quill-sns-get-swap-refund.md | 2 + .../sns/quill-sns-list-deployed-snses.md | 4 + .../sns/quill-sns-make-proposal.md | 2 + ...uill-sns-make-upgrade-canister-proposal.md | 2 + .../sns/quill-sns-neuron-permission.md | 2 + .../sns/quill-sns-new-sale-ticket.md | 10 +- docs/cli-reference/sns/quill-sns-pay.md | 2 + .../sns/quill-sns-register-vote.md | 30 --- ...sns-split-neuron.md => quill-sns-split.md} | 11 +- .../sns/quill-sns-stake-maturity.md | 17 +- .../sns/quill-sns-stake-neuron.md | 19 +- docs/cli-reference/sns/quill-sns-status.md | 4 + docs/cli-reference/sns/quill-sns-transfer.md | 2 + docs/cli-reference/sns/quill-sns-vote.md | 37 +++ e2e/tests-quill/create_neuron.bash | 6 +- .../{account_balance.rs => balance.rs} | 33 ++- src/commands/ckbtc/balance.rs | 6 +- src/commands/community_fund.rs | 65 +++++ src/commands/disburse.rs | 79 ++++++ src/commands/dissolve.rs | 55 ++++ src/commands/dissolve_delay.rs | 73 ++++++ src/commands/follow.rs | 84 ++++++ src/commands/hotkey.rs | 64 +++++ src/commands/merge.rs | 41 +++ src/commands/mod.rs | 83 +++++- src/commands/neuron_manage.rs | 13 +- src/commands/sns.rs | 38 ++- src/commands/sns/configure_dissolve_delay.rs | 4 + src/commands/sns/disburse.rs | 3 +- src/commands/sns/disburse_maturity.rs | 1 + src/commands/sns/dissolve.rs | 63 +++++ src/commands/sns/dissolve_delay.rs | 79 ++++++ .../sns/{follow_neuron.rs => follow.rs} | 7 +- .../sns/{split_neuron.rs => split.rs} | 29 ++- src/commands/sns/stake_maturity.rs | 47 +++- src/commands/sns/stake_neuron.rs | 53 ++-- src/commands/sns/transfer.rs | 2 +- .../sns/{register_vote.rs => vote.rs} | 41 ++- src/commands/spawn.rs | 46 ++++ src/commands/split.rs | 41 +++ src/commands/stake_maturity.rs | 68 +++++ .../{neuron_stake.rs => stake_neuron.rs} | 51 ++-- src/commands/transfer.rs | 47 ++-- src/commands/vote.rs | 66 +++++ src/lib/mod.rs | 75 +++++- .../output/default/neuron_manage/disburse.txt | 15 ++ .../disburse_stop_dissolving.txt | 32 --- .../dissolve_fixed_timestamp.txt | 21 ++ .../default/neuron_manage/remove_hot_key.txt | 21 ++ .../output/default/neuron_manage/spawn_to.txt | 19 ++ tests/output/default/neuron_manage/vote.txt | 18 -- .../output/default/neuron_stake/with_name.txt | 17 +- .../default/neuron_stake/with_nonce.txt | 17 +- .../sns/dissolve_delay/set_seconds.txt | 20 ++ .../sns/manage_neuron/auto_stake_maturity.txt | 20 ++ .../disable_auto_stake_maturity.txt | 20 ++ .../dissolve_fixed_timestamp.txt | 21 ++ .../output/ledger/neuron_manage/spawn_to.txt | 19 ++ tests/output/neuron_manage.rs | 82 +++--- tests/output/root.rs | 19 +- tests/output/sns.rs | 53 ++-- 91 files changed, 2590 insertions(+), 728 deletions(-) rename docs/cli-reference/{quill-account-balance.md => quill-balance.md} (65%) create mode 100644 docs/cli-reference/quill-community-fund.md create mode 100644 docs/cli-reference/quill-disburse.md create mode 100644 docs/cli-reference/quill-dissolve-delay.md create mode 100644 docs/cli-reference/quill-dissolve.md create mode 100644 docs/cli-reference/quill-follow.md create mode 100644 docs/cli-reference/quill-hotkey.md create mode 100644 docs/cli-reference/quill-merge.md delete mode 100644 docs/cli-reference/quill-neuron-manage.md create mode 100644 docs/cli-reference/quill-spawn.md create mode 100644 docs/cli-reference/quill-split.md create mode 100644 docs/cli-reference/quill-stake-maturity.md rename docs/cli-reference/{quill-neuron-stake.md => quill-stake-neuron.md} (89%) create mode 100644 docs/cli-reference/quill-vote.md delete mode 100644 docs/cli-reference/sns/quill-sns-configure-dissolve-delay.md create mode 100644 docs/cli-reference/sns/quill-sns-dissolve-delay.md create mode 100644 docs/cli-reference/sns/quill-sns-dissolve.md rename docs/cli-reference/sns/{quill-sns-follow-neuron.md => quill-sns-follow.md} (90%) delete mode 100644 docs/cli-reference/sns/quill-sns-register-vote.md rename docs/cli-reference/sns/{quill-sns-split-neuron.md => quill-sns-split.md} (68%) create mode 100644 docs/cli-reference/sns/quill-sns-vote.md rename src/commands/{account_balance.rs => balance.rs} (64%) create mode 100644 src/commands/community_fund.rs create mode 100644 src/commands/disburse.rs create mode 100644 src/commands/dissolve.rs create mode 100644 src/commands/dissolve_delay.rs create mode 100644 src/commands/follow.rs create mode 100644 src/commands/hotkey.rs create mode 100644 src/commands/merge.rs create mode 100644 src/commands/sns/dissolve.rs create mode 100644 src/commands/sns/dissolve_delay.rs rename src/commands/sns/{follow_neuron.rs => follow.rs} (95%) rename src/commands/sns/{split_neuron.rs => split.rs} (62%) rename src/commands/sns/{register_vote.rs => vote.rs} (68%) create mode 100644 src/commands/spawn.rs create mode 100644 src/commands/split.rs create mode 100644 src/commands/stake_maturity.rs rename src/commands/{neuron_stake.rs => stake_neuron.rs} (70%) create mode 100644 src/commands/vote.rs create mode 100644 tests/output/default/neuron_manage/disburse.txt delete mode 100644 tests/output/default/neuron_manage/disburse_stop_dissolving.txt create mode 100644 tests/output/default/neuron_manage/dissolve_fixed_timestamp.txt create mode 100644 tests/output/default/neuron_manage/remove_hot_key.txt create mode 100644 tests/output/default/neuron_manage/spawn_to.txt create mode 100644 tests/output/default/sns/dissolve_delay/set_seconds.txt create mode 100644 tests/output/default/sns/manage_neuron/auto_stake_maturity.txt create mode 100644 tests/output/default/sns/manage_neuron/disable_auto_stake_maturity.txt create mode 100644 tests/output/ledger/neuron_manage/dissolve_fixed_timestamp.txt create mode 100644 tests/output/ledger/neuron_manage/spawn_to.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e23c43ad..107866b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Overhauled the command interface; broke up `neuron-manage` and unified command and flag names between NNS and SNS. (#202) - Added Ledger support via `--ledger`. (#199) - Added `--confirmation-text` to `quill sns pay`. (#195) - Fixed `quill ckbtc update-balance` allowing the anonymous principal. (#191) diff --git a/Cargo.lock b/Cargo.lock index ff767c8f..0776191f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -1266,13 +1266,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1609,6 +1609,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1685,6 +1691,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.19" @@ -3102,12 +3114,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3344,9 +3357,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" @@ -3372,9 +3385,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" @@ -3659,7 +3672,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3826,7 +3839,7 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", + "redox_syscall 0.2.13", "smallvec", "windows-sys 0.36.1", ] @@ -4228,6 +4241,7 @@ dependencies = [ "flate2", "hex", "hidapi", + "humantime", "ic-agent", "ic-base-types", "ic-ckbtc-minter", @@ -4266,6 +4280,7 @@ dependencies = [ "shellwords", "simple_asn1", "tempfile", + "time 0.3.21", "tiny-bip39", "tokio", ] @@ -4333,6 +4348,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -4340,7 +4364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.6", - "redox_syscall", + "redox_syscall 0.2.13", "thiserror", ] @@ -4559,16 +4583,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.8" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -5131,15 +5155,16 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if 1.0.0", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -5674,26 +5699,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows-targets 0.42.1", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -5702,21 +5721,42 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -5729,6 +5769,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -5741,6 +5787,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -5753,6 +5805,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -5765,12 +5823,24 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -5783,6 +5853,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 7c6cc371..91ba7612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ crc32fast = "1.3.2" data-encoding = "2.3.3" flate2 = "1.0.22" hex = { version = "0.4.2", features = ["serde"] } +humantime = "2" hidapi = { version = "1.4", default-features = false, optional = true } indicatif = "0.17" itertools = "0.10.5" @@ -61,6 +62,7 @@ serde_bytes = "0.11.2" serde_cbor = "0.11.2" serde_json = "1.0.57" sha3 = "0.10.6" +time = "0.3" tiny-bip39 = "1.0.0" tokio = { version = "1.18.5", features = ["full"] } diff --git a/README.md b/README.md index d8aefeb6..15e658e1 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,12 @@ To get the principal and the account id: This is how you’d stake/top-up a neuron: - quill neuron-stake --amount 2.5 --name 1 --pem-file + quill stake-neuron --amount 2.5 --name 1 --pem-file Managing the neuron: - quill neuron-manage [OPERATIONS] --pem-file + quill vote --proposal-id --approve --pem-file + quill spawn --percentage 75 All the commands above will generate signed messages, which can be sent on the online machine using the `send` command from above. diff --git a/docs/cli-reference/ckbtc/quill-ckbtc-balance.md b/docs/cli-reference/ckbtc/quill-ckbtc-balance.md index dfb73d71..b696f1b4 100644 --- a/docs/cli-reference/ckbtc/quill-ckbtc-balance.md +++ b/docs/cli-reference/ckbtc/quill-ckbtc-balance.md @@ -21,10 +21,10 @@ quill ckbtc balance [option] ## Options -| Argument | Description | -|-----------------------------------|--------------------------------------------------| -| `--of ` | The account to check. Optional if a key is used. | -| `--of-subaccount ` | The subaccount of the account to check. | +| Argument | Description | +|-----------------------------|--------------------------------------------------| +| `--of ` | The account to check. Optional if a key is used. | +| `--subaccount ` | The subaccount of the account to check. | ## Examples @@ -43,7 +43,7 @@ quill ckbtc balance --pem-file ./id.pem Subaccounts can be provided explicitly: ```sh -quill ckbtc balance --of fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae --of-subaccount 01 +quill ckbtc balance --of fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae --subaccount 01 ``` Or as part of an ICRC-1 account ID: diff --git a/docs/cli-reference/index.md b/docs/cli-reference/index.md index 2434cc67..9a168c7c 100644 --- a/docs/cli-reference/index.md +++ b/docs/cli-reference/index.md @@ -15,8 +15,9 @@ Depending on the subcommand, the options and flags you specify might apply to th When you have quill installed, you can use the following commands to specify the operation you want to perform. For reference information and examples that illustrate using these commands, select an appropriate command. - [quill](./quill-parent.md) -- [quill account-balance](./quill-account-balance.md) +- [quill balance](./quill-balance.md) - [quill claim-neurons](./quill-claim-neurons.md) +- [quill community-fund](./quill-community-fund.md) - [quill ckbtc](./ckbtc/quill-ckbtc.md) - [quill ckbtc balance](./ckbtc/quill-ckbtc-balance.md) - [quill ckbtc retrieve-btc](./ckbtc/quill-ckbtc-retrieve-btc.md) @@ -24,13 +25,17 @@ When you have quill installed, you can use the following commands to specify the - [quill ckbtc transfer](./ckbtc/quill-ckbtc-transfer.md) - [quill ckbtc update-balance](./ckbtc/quill-ckbtc-update-balance.md) - [quill ckbtc withdrawal-address](./ckbtc/quill-ckbtc-withdrawal-address.md) +- [quill disburse](./quill-disburse.md) +- [quill dissolve](./quill-dissolve.md) +- [quill dissolve-delay](./quill-dissolve-delay.md) +- [quill follow](./quill-follow.md) - [quill generate](./quill-generate.md) - [quill get-neuron-info](./quill-get-neuron-info.md) - [quill get-proposal-info](./quill-get-proposal-info.md) +- [quill hotkey](./quill-hotkey.md) - [quill list-neurons](./quill-list-neurons.md) - [quill list-proposals](./quill-list-proposals.md) -- [quill neuron-manage](./quill-neuron-manage.md) -- [quill neuron-stake](./quill-neuron-stake.md) +- [quill merge](./quill-merge.md) - [quill public-ids](./quill-public-ids.md) - [quill qr-code](./quill-qr-code.md) - [quill replace-node-provider-id](./quill-replace-node-provider-id.md) @@ -38,7 +43,9 @@ When you have quill installed, you can use the following commands to specify the - [quill send](./quill-send.md) - [quill sns](./sns/quill-sns.md) - [quill sns balance](./sns/quill-sns-balance.md) - - [quill sns configure-dissolve-delay](./sns/quill-sns-configure-dissolve-delay.md) + - [quill sns dissolve](./sns/quill-sns-dissolve.md) + - [quill sns dissolve-delay](./sns/quill-sns-dissolve-delay.md) + - [quill sns follow](./sns/quill-sns-follow.md) - [quill sns get-swap-refund](./sns/quill-sns-get-swap-refund.md) - [quill sns get-sale-participation](./sns/quill-sns-get-sale-participation.md) - [quill sns list-deployed-snses](./sns/quill-sns-list-deployed-snses.md) @@ -47,10 +54,16 @@ When you have quill installed, you can use the following commands to specify the - [quill sns neuron-permission](./sns/quill-sns-neuron-permission.md) - [quill sns new-sale-ticket](./sns/quill-sns-new-sale-ticket.md) - [quill sns pay](./sns/quill-sns-pay.md) - - [quill sns register-vote](./sns/quill-sns-register-vote.md) + - [quill sns split](./sns/quill-sns-split.md) - [quill sns stake-maturity](./sns/quill-sns-stake-maturity.md) - [quill sns stake-neuron](./sns/quill-sns-stake-neuron.md) - [quill sns status](./sns/quill-sns-status.md) - [quill sns transfer](./sns/quill-sns-transfer.md) + - [quill sns vote](./sns/quill-sns-vote.md) +- [quill spawn](./quill-spawn.md) +- [quill split](./quill-split.md) +- [quill stake-maturity](./quill-stake-maturity.md) +- [quill stake-neuron](./quill-stake-neuron.md) - [quill transfer](./quill-transfer.md) - [quill update-node-provider](./quill-update-node-provider.md) +- [quill vote](./quill-vote.md) diff --git a/docs/cli-reference/quill-account-balance.md b/docs/cli-reference/quill-balance.md similarity index 65% rename from docs/cli-reference/quill-account-balance.md rename to docs/cli-reference/quill-balance.md index 0fb9b942..e68a1f35 100644 --- a/docs/cli-reference/quill-account-balance.md +++ b/docs/cli-reference/quill-balance.md @@ -1,21 +1,15 @@ -# quill account-balance +# quill balance Queries a ledger account for its balance. ## Basic usage -The basic syntax for running `quill account-balance` commands is: +The basic syntax for running `quill balance` commands is: ``` bash -quill account-balance [flag] +quill balance [option] ``` -## Arguments - -| Argument | Description | -|----------------|------------------------------------------------------------| -| `` | The id of the account to query. Optional if a key is used. | - ## Flags | Flag | Description | @@ -24,14 +18,21 @@ quill account-balance [flag] | `-h`, `--help` | Displays usage information. | | `-y`, `--yes` | Skips confirmation and sends the message directly. | +## Options + +| Argument | Description | +|-----------------------------|------------------------------------------------------------| +| `--of ` | The id of the account to query. Optional if a key is used. | +| `--subaccount ` | The subaccount of the account to query. | + ## Examples -The `quill account-balance` command is used to check the ICP balance of a particular NNS ledger account. +The `quill balance` command is used to check the ICP balance of a particular NNS ledger account. For example, to check the balance of the account `345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752`, you would say: ```sh -quill account-balance 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 +quill balance 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 ``` This will produce a response like diff --git a/docs/cli-reference/quill-claim-neurons.md b/docs/cli-reference/quill-claim-neurons.md index 14cc21dc..9d7d2e3e 100644 --- a/docs/cli-reference/quill-claim-neurons.md +++ b/docs/cli-reference/quill-claim-neurons.md @@ -36,8 +36,8 @@ Each number in the `vec` is the ID of one of your neurons. As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send -`. -For more information about genesis rewards, see the [How-To guide]. For more information about neurons, see [Neurons]. For creating non-genesis neurons, consult the documentation for [`quill neuron-stake`]. +For more information about genesis rewards, see the [How-To guide]. For more information about neurons, see [Neurons]. For creating non-genesis neurons, consult the documentation for [`quill stake-neuron`]. [How-To guide]: https://wiki.internetcomputer.org/wiki/How-To:_Claim_neurons_for_seed_participants [Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro#neurons -[`quill neuron-stake`]: quill-neuron-stake.md +[`quill stake-neuron`]: quill-stake-neuron.md diff --git a/docs/cli-reference/quill-community-fund.md b/docs/cli-reference/quill-community-fund.md new file mode 100644 index 00000000..cde043e1 --- /dev/null +++ b/docs/cli-reference/quill-community-fund.md @@ -0,0 +1,61 @@ +# quill community-fund + +Signs a message to join or leave the Internet Computer's community fund with this neuron's maturity. + +## Basic usage + +The basic syntax for running `quill community-fund` commands is: + +```bash +quill community-fund <--join|--leave> +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to configure. | + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | +| `--join` | Join the community fund | +| `--leave` | Leave the community fund | + +## Examples + +The `quill community-fund` command is used to make a neuron join or leave the community fund, which allows the NNS to make investments using the neuron's maturity. + +To join the community fund: + +```sh +quill community-fund $neuron --join +``` + +Or to leave: + +```sh +quill community-fund $neuron --leave +``` + +These will return a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +For more information about the community fund, see [Community Fund]. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +[Community Fund]: https://internetcomputer.org/docs/current/tokenomics/nns/community-fund diff --git a/docs/cli-reference/quill-disburse.md b/docs/cli-reference/quill-disburse.md new file mode 100644 index 00000000..145b49ad --- /dev/null +++ b/docs/cli-reference/quill-disburse.md @@ -0,0 +1,78 @@ +# quill disburse + +Signs a disbursal message to convert a dissolved neuron into ICP. + +## Basic usage + +The basic syntax for running `quill disburse` commands is: + +```bash +quill disburse [option] +``` + +## Arguments + +| Argument | Description | +|---------------|-------------------------| +| `` | The neuron to disburse. | + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|-----------------------------|-------------------------------------| +| `--amount ` | The number of tokens to disburse. | +| `--subaccount ` | The subaccount to transfer to. | +| `--to ` | The account to transfer the ICP to. | + +## Examples + +The `quill disburse` command liquidates a fully dissolved neuron into ICP. + +The simplest case is to disburse the whole neuron to yourself: + +```sh +quill disburse $neuron +``` + +Or to, for example, disburse 2.5 ICP to the anonymous principal `2vxsx-fae`: + +```sh +quill disburse $neuron --to 2vxsx-fae --amount 2.5 +``` + +This will produce a response like: + +```candid +( + record { + command = opt variant { + Disburse = record { + transfer_block_height = 5_581_035 : nat64; + } + }; + } +) +``` + +This block height, also known as the block index, can be looked up on the [IC dashboard]. + +## Remarks + +If `--to` is unset, it will default to the caller; if `--amount` is unset, it will fully consume the neuron. + +Only a fully dissolved neuron can be disbursed. To start dissolving a neuron, see [`quill dissolve`]. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro +[`quill dissolve`]: quill-dissolve.md +[IC dashboard]: https://dashboard.internetcomputer.org/transactions diff --git a/docs/cli-reference/quill-dissolve-delay.md b/docs/cli-reference/quill-dissolve-delay.md new file mode 100644 index 00000000..908b2f0c --- /dev/null +++ b/docs/cli-reference/quill-dissolve-delay.md @@ -0,0 +1,69 @@ +# quill dissolve-delay + +Signs a neuron configuration change to increase a neuron's dissolve delay. + +## Basic usage + +The basic syntax for running `quill dissolve-delay` commands is: + +```bash +quill dissolve-delay <--increase-by |--increase-to > +``` + +## Arguments + +| Argument | Description | +|---------------|-----------------------------------| +| `` | The ID of the neuron to configure | + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|----------------------------|--------------------------------------------------------| +| `--increase-by ` | Additional time to add to the neuron's dissolve delay. | +| `--increase-to ` | Total time to set the neuron's dissolve delay to. | + +## Examples + +The `quill dissolve-delay` command is used to increase a neuron's dissolve delay. + +For example, to set it to four years: + +```sh +quill dissolve-delay $neuron --increase-to 4y +``` + +Or to add an additional six months: + +```sh +quill dissolve-delay $your_neuron --increase-by 6months +``` + +This will return a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +For technical reasons, to hit the eight-year maximum you will need to use `--increase-by`, not `--increase-to`. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro \ No newline at end of file diff --git a/docs/cli-reference/quill-dissolve.md b/docs/cli-reference/quill-dissolve.md new file mode 100644 index 00000000..08147651 --- /dev/null +++ b/docs/cli-reference/quill-dissolve.md @@ -0,0 +1,60 @@ +# quill dissolve + +Signs a neuron configuration message to start or stop a neuron dissolving. + +## Basic usage + +The basic syntax for running `quill dissolve` commands is: + +```bash +quill dissolve <--start|--stop> +``` + +## Arguments + +| Argument | Description | +|---------------|-----------------------------------| +| `` | The ID of the neuron to dissolve. | + +## Flags + +| Flag | Description | +|----------------|------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--start` | Start dissolving the neuron. | +| `--stop` | Stop dissolving the neuron. | + +## Examples + +The `quill dissolve` command is used to dissolve a neuron: + +```sh +quill dissolve $neuron --start +``` + +Or to stop one from dissolving: + +```sh +quill dissolve $neuron --stop +``` + +This will produce a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro diff --git a/docs/cli-reference/quill-follow.md b/docs/cli-reference/quill-follow.md new file mode 100644 index 00000000..53baaa72 --- /dev/null +++ b/docs/cli-reference/quill-follow.md @@ -0,0 +1,92 @@ +# quill follow + +Signs a neuron configuration message to change a neuron's follow relationship. + +## Basic usage + +The basic syntax for running `quill follow` commands is: + +```bash +quill follow <--type |--topic-id > <--followees |--unfollow> +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to configure. | + + +## Flags + +| Flag | Description | +|----------------|--------------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--unfollow` | Unfollow all neurons for this topic. | + +## Options + +| Option | Description | +|---------------------------|----------------------------------------------------------------| +| `--followees ` | A comma-separated list of neuron IDs to follow. | +| `--topic-id ` | The numeric ID of the proposal topic to restrict following to. | +| `--type ` | The name of the proposal topic to restrict following to. | + +## Examples + +The `quill follow` command is used to follow other neurons. Following a neuron causes your neuron to automatically vote when that neuron does. + +To follow another neuron on all topics, for example, ICPMN: + +```sh +quill follow $neuron --followees 4966884161088437903 --type all +``` + +You can also follow a group of neurons, e.g. ICDevs and the ICA, and vote when the majority (by voting power) of the followed neurons do: + +```sh +quill follow $neuron --followees 14231996777861930328,28 --type all +``` + +Follow relationships are granular by the purpose of a proposal. To follow, for example, DFINITY's neuron on exchange rate adjustments: + +```sh +quill follow $neuron --followees 27 --type exchange-rate +``` + +If the name of the topic is not listed in Quill and you know its numeric ID, you can use that; the above command is equivalent to: + +```sh +quill follow $neuron --followees 27 --topic-id 2 +``` + +You can also clear your followed neurons for a particular topic. To return to manual voting on e.g. governance proposals: + +```sh +quill follow $your-neuron --type governance --unfollow +``` + +These will return a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +Built-in proposal topic names: `all`, `neuron-management`, `exchange-rate`, `network-economics`, `governance`, `node-admin`, `participant-management`, `subnet-management`, `network-canister-management`, `kyc`, `node-provider-rewards`, `sns-decentralization-sale`, `subnet-replica-version-management`, `replica-version-management`, `sns-and-community-fund`. + +Any follow command overrides a previous follow command for the same topic; the followee list will be replaced, not added to. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro diff --git a/docs/cli-reference/quill-hotkey.md b/docs/cli-reference/quill-hotkey.md new file mode 100644 index 00000000..1fa65a07 --- /dev/null +++ b/docs/cli-reference/quill-hotkey.md @@ -0,0 +1,68 @@ +# quill hotkey + +Signs a neuron configuration message to add or remove a hotkey. + +## Basic usage + +The basic syntax for running `quill hotkey` commands is: + +```bash +quill hotkey <--add |--remove > +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to configure. | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|------------------------|---------------------------------------------| +| `--add ` | Add the specified principal as a hotkey. | +| `--remove ` | Remove the specified principal as a hotkey. | + +## Examples + +The `quill hotkey` command is used to add a principal as a hotkey to a neuron, allowing the principal to vote or follow other neurons for voting without any financial access. + +To add, for example, the anonymous principal `2vxsx-fae` as a hotkey: + +```sh +quill hotkey $neuron --add 2vxsx-fae +``` + +Or to remove it: + +```sh +quill hotkey $neuron --remove 2vxsx-fae +``` + +This will produce a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro \ No newline at end of file diff --git a/docs/cli-reference/quill-merge.md b/docs/cli-reference/quill-merge.md new file mode 100644 index 00000000..d9e80929 --- /dev/null +++ b/docs/cli-reference/quill-merge.md @@ -0,0 +1,61 @@ +# quill merge + +Signs a neuron management message to merge another neuron into this one. + +## Basic usage + +The basic syntax for running `quill merge` commands is: + +```bash +quill merge --from +``` + +## Arguments + +| Argument | Description | +|---------------|-------------------------------------| +| `` | The ID of the neuron to merge into. | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|-----------------|-------------------------------------| +| `--from ` | The ID of the neuron to merge from. | + +## Examples + +The `quill merge` command is used to merge two neurons together, combining their stake, maturity, age, and dissolve delay: + +```sh +quill merge $main_neuron --from $other_neuron +``` + +This will return a response like: + +```candid +( + record { + command = opt variant { + Merge = record {} + }; + } +) +``` + +## Remarks + +Stakes and maturity combine additively. Age combines according to the formula `((age1 * stake1) + (age2 * stake2)) / (stake1 + stake2)`. Dissolve delays combine to whichever neuron's dissolve delay was higher. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro \ No newline at end of file diff --git a/docs/cli-reference/quill-neuron-manage.md b/docs/cli-reference/quill-neuron-manage.md deleted file mode 100644 index f45b2420..00000000 --- a/docs/cli-reference/quill-neuron-manage.md +++ /dev/null @@ -1,241 +0,0 @@ -# quill neuron-manage - -Signs a neuron configuration change. - -## Basic usage - -The basic syntax for running `quill neuron-manage` commands is: - -``` bash -quill neuron-manage [option] -``` - -## Arguments - -| Argument | Description | -|---------------|---------------------------------| -| `` | The id of the neuron to manage. | - -## Flags - -| Flag | Description | -|----------------------|-------------------------------------------------------------------------------------------| -| `--clear-manage-neuron-followees` | Remove all followees for the NeuronManagement topic. | -| `--disburse` | Disburse the entire staked amount to the controller's account. | -| `-h`, `--help` | Displays usage information. | -| `--join-community-fund` | Join the Internet Computer's community fund with this neuron's entire stake. | -| `--leave-community-fund` | Leave the Internet Computer's community fund. | -| `--reject` | Reject the proposal(s) specified with `--register-vote`. | -| `--spawn` | Spawn rewards to a new neuron under the controller's account. | -| `--start-dissolving` | Start dissolving. | -| `--stop-dissolving` | Stop dissolving. | - -## Options - -| Option | Description | -|-------------------------------------------------------|----------------------------------------------------------------------------------| -| `-a`, `--additional-dissolve-delay-seconds ` | Number of dissolve seconds to add. | -| `--add-hot-key ` | Principal to be used as a hot key. | -| `--auto-stake-maturity enabled|disabled` | Set whether new maturity should be automatically staked. | -| `--disburse-amount` | Disburse only the selected amount. | -| `--disburse-to` | Disburse to the selected NNS account instead of the controller. | -| `--follow-neurons ...` | Defines the neuron ids of a follow rule. | -| `--follow-topic ` | Defines the topic of a follow rule as defined [here][follow-rules]. | -| `--merge-from-neuron ` | Merge stake, maturity and age from the specified neuron into the managed neuron. | -| `--stake-maturity ` | Stake the percentage (between 1 and 100) of the maturity of a neuron. | -| `--remove-hot-key ` | Principal hot key to be removed. | -| `--split ` | Split off the given number of ICP from a neuron. | -| `--register-vote ... [--reject]` | Vote to approve (default) or reject proposal(s). | - -[follow-rules]: https://github.com/dfinity/ic/blob/4c9e71499d90d00da986dbe7b985d861fd031c4e/rs/nns/governance/gen/ic_nns_governance.pb.v1.rs#L1571-L1632 - -## Examples - -The `quill neuron-manage` command has many uses, each operating on a neuron in a different way. The examples below will all operate on a hypothetical neuron 2313380519530470538. - -To vote YES on a proposal such as [108005]: - -```sh -quill neuron-manage 2313380519530470538 --register-vote 108005 -``` - -Or to vote NO on that same proposal: - -```sh -quill neuron-manage 2313380519530470538 --register-vote 108005 --reject -``` - -This will produce a response like: - -```candid -( - record { - command = opt variant { - RegisterVote = record {} - }; - } -) -``` - -To follow another neuron, such as ICPMN, on a specific topic, such as Governance: - -```sh -quill neuron-manage 2313380519530470538 --follow-neurons 4966884161088437903 --follow-topic 4 -``` - -This will produce a response like: - -```candid -( - record { - command = opt variant { - Follow = record {} - }; - } -) -``` - -To stake a neuron's maturity, for example 25% of it, increasing its voting power: - -```sh -quill neuron-manage 2313380519530470538 --stake-maturity 25 -``` - -This will produce a response like: - -```candid -( - record { - command = opt variant { - StakeMaturity = record { - maturity_e8s = 750_000_000 : nat64; - staked_maturity_e8s = 250_000_000 : nat64; - } - }; - } -) -``` - -"e8s" is a shorthand for meaning the number of 1e-8s, or one-hundred-millionths, of an ICP in integer form; this response must be divided by 100,000,000 to get the real maturity, which in this case would be 7.5 and 2.5. - -To disburse a fully dissolved neuron, meaning to convert it into ICP and transfer it to its controller: - -```sh -quill neuron-manage 2313380519530470538 --disburse -``` - -This will produce a response like: - -```candid -( - record { - command = opt variant { - Disburse = record { - transfer_block_height = 5_581_035 : nat64; - } - }; - } -) -``` - -The block height, also known as the block index, can be looked up on the [IC dashboard]. - -To spawn a new neuron, meaning to convert all the maturity from this neuron into a new one (that can be disbursed after one week): - -```sh -quill neuron-manage 2313380519530470538 --spawn -``` - -This will produce a response like: - -```candid -( - record { - command = opt variant { - SpawnResponse = record { - created_neuron_id = opt record { - id = 4966884161088437902 : nat64; - }; - } - }; - } -) -``` - -To increase the neuron's dissolve delay, for example by a full year: - -```sh -quill neuron-manage 2313380519530470538--additional-dissolve-delay-seconds 31536000 -``` - -To start dissolving a neuron: - -```sh -quill neuron-manage 2313380519530470538 --start-dissolving -``` - -Or to stop: - -```sh -quill neuron-manage 2313380519530470538 --stop-dissolving -``` - -To enable auto-staking a neuron's maturity: - -```sh -quill neuron-manage 2313380519530470538 --auto-stake-maturity enabled -``` - -Or to disable: - -```sh -quill neuron-manage 2313380519530470538 --auto-stake-maturity disabled -``` - -To join the IC's community fund: - -```sh -quill neuron-manage 2313380519530470538 --join-community-fund -``` - -Or to leave: - -```sh -quill neuron-manage 2313380519530470538 --leave-community-fund -``` - -To add a neuron hotkey, such as (for example purposes) the management canister: - -```sh -quill neuron-manage 2313380519530470538 --add-hot-key aaaaa-aa -``` - -Or to remove it: - -```sh -quill neuron-manage 2313380519530470538 --remove-hot-key aaaaa-aa -``` - -These 'configuration' operations will all produce a response like: - -```candid -( - record { - command = opt variant { - Configure = record {} - }; - } -) -``` - -## Remarks - -As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send -`. - -For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]; for more information about the community fund, see [Community Fund]. - -[108005]: https://dashboard.internetcomputer.org/proposal/108005 -[IC Dashboard]: https://dashboard.internetcomputer.org/ -[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons -[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro -[Community Fund]: https://internetcomputer.org/docs/current/tokenomics/nns/community-fund diff --git a/docs/cli-reference/quill-parent.md b/docs/cli-reference/quill-parent.md index 0b40cc26..cc506330 100644 --- a/docs/cli-reference/quill-parent.md +++ b/docs/cli-reference/quill-parent.md @@ -49,10 +49,10 @@ See [`quill generate`] to generate a new key file, though Quill should be compat quill list-neurons --pem-file identity.pem ``` -Some commands that do not require your key will still be more useful with it; for example, `quill account-balance` doesn't require authentication, but providing your key prevents you from having to provide your principal or account ID: +Some commands that do not require your key will still be more useful with it; for example, `quill balance` doesn't require authentication, but providing your key prevents you from having to provide your principal or account ID: ```sh -quill account-balance --pem-file identity.pem +quill balance --pem-file identity.pem ``` Quill can also be used with a seed phrase directly, though using [`quill generate`] to convert it into a private key should be preferred instead. To authenticate using a `seed.txt` file containing your seed phrase: diff --git a/docs/cli-reference/quill-spawn.md b/docs/cli-reference/quill-spawn.md new file mode 100644 index 00000000..9bc9fc8a --- /dev/null +++ b/docs/cli-reference/quill-spawn.md @@ -0,0 +1,76 @@ +# quill spawn + +Signs a neuron management message to convert a neuron's maturity into a rapidly-dissolving neuron. + +## Basic usage + +The basic syntax for running `quill spawn` commands is: + +```bash +quill spawn [option] +``` + +## Arguments + +| Argument | Description | +|---------------|-------------------------------------| +| `` | The ID of the neuron to spawn from. | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|-----------------------------|------------------------------------------| +| `--percentage ` | The percentage of the maturity to spawn. | +| `--to ` | The owner of the spawned neuron. | + +## Examples + +The `quill spawn` command is used to convert neuron maturity into ICP by splitting it off into a neuron. + +The simplest case is to spawn all of the maturity: + +```sh +quill spawn $neuron +``` + +Or to, for example, spawn 25% of the maturity to a different principal: + +```sh +quill spawn $neuron --percentage 25 --to "$(dfx identity get-principal)" +``` + +This will return a response like: + +```candid +( + record { + command = opt variant { + Spawn = record { + created_neuron_id = 2_313_380_519_530_470_538 : nat64; + } + }; + } +) +``` + +## Remarks + +Spawned neurons will immediately start dissolving, and take seven days to dissolve. When the neuron is done dissolving, use [`quill disburse`] to liquidate it into ICP. Spawning maturity just to re-stake it is less efficent than staking it directly; to do that, see [`quill stake-maturity`]. + +If `--to` is unset, it will default to the caller; if `--percentage` is unset it will default to 100%. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro +[`quill disburse`]: quill-disburse.md +[`quill stake-maturity`]: quill-stake-maturity.md diff --git a/docs/cli-reference/quill-split.md b/docs/cli-reference/quill-split.md new file mode 100644 index 00000000..3f484a7e --- /dev/null +++ b/docs/cli-reference/quill-split.md @@ -0,0 +1,65 @@ +# quill split + +Signs a neuron management message to split a neuron in two. + +## Basic usage + +The basic syntax for running `quill split` commands is: + +```bash +quill split --amount +``` + +## Arguments + +| Argument | Description | +|---------------|--------------------------------| +| `` | The ID of the neuron to split. | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|---------------------|------------------------------------------------------| +| `--amount ` | The amount of the stake that should be split, in ICP | + +## Examples + +The `quill split` command is used to split a neuron into two neurons. + +To split, for example, 10 ICP off into a new neuron: + +```sh +quill split $neuron --amount 10 +``` + +This will return a response like: + +```candid +( + record { + command = opt variant { + Spawn = record { + created_neuron_id = 2_313_380_519_530_470_538 : nat64; + } + }; + } +) +``` + +## Remarks + +Splitting a neuron has a few use-cases, but the most common one is to dissolve only part of a stake without sacrificing the accrued age bonus. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro diff --git a/docs/cli-reference/quill-stake-maturity.md b/docs/cli-reference/quill-stake-maturity.md new file mode 100644 index 00000000..f77f326f --- /dev/null +++ b/docs/cli-reference/quill-stake-maturity.md @@ -0,0 +1,94 @@ +# quill stake-maturity + +Signs a neuron management message to add maturity to a neuron's stake, or configure auto-staking. + +## Basic usage + +The basic syntax for running `quill stake-maturity` commands is: + +```bash +quill stake-maturity <--percentage |--automatic|--disable-automatic>> +``` + +## Arguments + +| Argument | Description | +|---------------|---------------------------------| +| `` | The ID of the neuron to manage. | + + +## Flags + +| Flag | Description | +|-----------------------|-------------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--automatic` | Enable automatic maturity staking. | +| `--disable-automatic` | Disable automatic maturity staking. | + +## Options + +| Option | Description | +|-----------------------------|-----------------------------------------------------------| +| `--percentage ` | The percentage of the neuron's accrued maturity to stake. | + +## Examples + +The `quill stake-maturity command is used to stake a portion of the neuron's accrued maturity. + +To stake 25% of the neuron's maturity: + +```sh +quill stake-maturity $neuron --percentage 25 +``` + +This will produce a response like: + +```candid +( + record { + commmand = opt variant { + StakeMaturity = record { + maturity_e8s = 450_000_000 : nat64; + staked_maturity_e8s = 150_000_000 : nat64; + } + } + } +) +``` + +The provided numbers are in e8s, or hundred-millionths of an ICP. In this case 1.5 ICP worth of maturity was staked, out of an initial quantity of 6.0. + +You can also configure the neuron to automatically stake all maturity: + +```sh +quill stake-maturity $neuron --automatic +``` + +Or, if this was enabled previously, to disable it: + +```sh +quill stake-maturity $neuron --disable-automatic +``` + +This will produce a response like: + +```candid +( + record { + command = opt variant { + Configure = record {} + }; + } +) +``` + +## Remarks + +A neuron's total voting power is proportional to the sum of its staked ICP and staked maturity. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro diff --git a/docs/cli-reference/quill-neuron-stake.md b/docs/cli-reference/quill-stake-neuron.md similarity index 89% rename from docs/cli-reference/quill-neuron-stake.md rename to docs/cli-reference/quill-stake-neuron.md index b77e3f88..0fd91451 100644 --- a/docs/cli-reference/quill-neuron-stake.md +++ b/docs/cli-reference/quill-stake-neuron.md @@ -1,13 +1,13 @@ -# quill neuron-stake +# quill stake-neuron Signs topping up of a neuron (new or existing). ## Basic usage -The basic syntax for running `quill neuron-stake` commands is: +The basic syntax for running `quill stake-neuron` commands is: ``` bash -quill neuron-stake [option] +quill stake-neuron [option] ``` ## Flags @@ -29,12 +29,12 @@ quill neuron-stake [option] ## Examples -The `quill neuron-stake` command is used to lock ICP into a new neuron or add ICP to an existing one. +The `quill stake-neuron` command is used to lock ICP into a new neuron or add ICP to an existing one. To stake for example 50 ICP into a neuron named 'primary': ```sh -quill neuron-stake --amount 50 --name primary +quill stake-neuron --amount 50 --name primary ``` The command is the same whether the neuron already exists or not. @@ -42,7 +42,7 @@ The command is the same whether the neuron already exists or not. This command consists of two messages, of which the first one is the ICP transfer. If you've already made this transfer through a different tool, you can add the transfer to the neuron's stake by leaving off the `amount` parameter and adding `--already-transferred`: ```sh -quill neuron-stake --name primary --already-transferred +quill stake-neuron --name primary --already-transferred ``` In addition to the transfer response documented at [`quill transfer`], these commands will produce a response like: diff --git a/docs/cli-reference/quill-transfer.md b/docs/cli-reference/quill-transfer.md index 41aee926..de706be2 100644 --- a/docs/cli-reference/quill-transfer.md +++ b/docs/cli-reference/quill-transfer.md @@ -29,6 +29,7 @@ quill transfer [option] --amount | `--amount ` | Amount of ICPs to transfer (with up to 8 decimal digits after comma). | | `--fee ` | Transaction fee, default is 10000 e8s. | | `--from-subaccount ` | The subaccount to transfer from. | +| `--to-subaccount ` | The subaccount to transfer to. | | `--memo ` | Reference number, default is 0. | ## Examples @@ -38,13 +39,13 @@ The `quill transfer` command is used to transfer ICP from one account to another For example, to transfer 5 ICP to the anonymous principal `2vxsx-fae`: ```sh -quill transfer 1c7a48ba6a562aa9eaa2481a9049cdf0433b9738c992d698c31d8abf89cadc79 --amount 5 +quill transfer 2vxsx-fae --amount 5 ``` This will produce a response like: ```candid -(5_581_035 : nat64) +(variant { Ok = 5_581_035 : nat }) ``` This refers to the block index, also known as block height, at which the transaction took place; you can look up this transaction on the [IC dashboard]. @@ -55,5 +56,5 @@ As this is an update call, it will not actually make the request, but rather gen For more information about ICP and transfers, see [ICP Token]. -[IC dashboard]: https://dashboard.internetcomputer.org/ +[IC dashboard]: https://dashboard.internetcomputer.org/transactions [ICP Token]: https://wiki.internetcomputer.org/wiki/ICP_token diff --git a/docs/cli-reference/quill-vote.md b/docs/cli-reference/quill-vote.md new file mode 100644 index 00000000..dfac3653 --- /dev/null +++ b/docs/cli-reference/quill-vote.md @@ -0,0 +1,70 @@ +# quill vote + +Signs a neuron management message to vote on a proposal. + +## Basic usage + +The basic syntax for running `quill vote` commands is: + +```bash +quill vote --proposal-id <--approve|--reject> +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to vote with. | + + +## Flags + +| Flag | Description | +|----------------|-------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--approve` | Vote to approve the proposal. | +| `--reject` | Vote to reject the proposal. | + +## Options + +| Option | Description | +|-------------------------------|------------------------------------| +| `--proposal-id ` | The ID of the proposal to vote on. | + +## Examples + +The `quill vote` command is used to vote on NNS proposals. + +To approve, for example, proposal [108005]: + +```sh +quill vote $neuron --proposal-id 108005 --approve +``` + +Or to reject that same proposal: + +```sh +quill vote $neuron --proposal-id 108005 --reject +``` + +This will produce a response like: + +```candid +( + record { + command = opt variant { + RegisterVote = record {} + }; + } +) +``` + +## Remarks + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +For more information about neurons, see [Neurons]; for more information about their role in the NNS, see [Network Nervous System][NNS]. + +[Neurons]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-staking-voting-rewards#neurons +[NNS]: https://internetcomputer.org/docs/current/tokenomics/nns/nns-intro +[108005]: https://dashboard.internetcomputer.org/proposal/108005 diff --git a/docs/cli-reference/sns/quill-sns-balance.md b/docs/cli-reference/sns/quill-sns-balance.md index 7594ced5..0f8d246b 100644 --- a/docs/cli-reference/sns/quill-sns-balance.md +++ b/docs/cli-reference/sns/quill-sns-balance.md @@ -30,3 +30,5 @@ quill sns balance [option] ## Remarks The queried account can be specified as a separate principal and subaccount, or as a single ICRC-1 account. + +As this is a query call, it cannot be executed on an air-gapped machine, but does not require access to your keys. diff --git a/docs/cli-reference/sns/quill-sns-configure-dissolve-delay.md b/docs/cli-reference/sns/quill-sns-configure-dissolve-delay.md deleted file mode 100644 index d53944b3..00000000 --- a/docs/cli-reference/sns/quill-sns-configure-dissolve-delay.md +++ /dev/null @@ -1,38 +0,0 @@ -# quill sns configure-dissolve-delay - -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 to make proposals, and other -actions it can take (such as disbursing). - -## Basic usage - -The basic syntax for running `quill sns configure-dissolve-delay` commands is: - -```bash -quill sns configure-dissolve-delay [option] -``` - -## Arguments - -| Argument | Description | -|---------------|------------------------------------| -| `` | The ID of the neuron to configure. | - -## Flags - -| Flag | Description | -|----------------------|-----------------------------------------------| -| `-h`, `--help` | Displays usage information. | -| `--start-dissolving` | The neuron will go into the dissolving state. | -| `--stop-dissolving` | The neuron will exit the dissolving state. | - -## Options - -| Option | Description | -|-------------------------------------------------------|--------------------------------------------------------------------------| -| `-a`, `--additional-dissolve-delay-seconds ` | Additional number of seconds to add to the dissolve delay of the neuron. | - -## Remarks - -When the neuron starts dissolving, a countdown timer will begin. When the timer is exhausted (i.e. dissolve_delay_seconds amount of time has elapsed), the neuron can be disbursed. When the neuron stops dissolving, whatever amount of dissolve delay seconds is left in the countdown timer is stored. A neuron's dissolve delay can be extended (for instance to increase voting power) by using the `--additional_dissolve_delay_seconds` flag. If the neuron is already dissolving and this argument is specified, the neuron will stop dissolving and begin aging. diff --git a/docs/cli-reference/sns/quill-sns-disburse.md b/docs/cli-reference/sns/quill-sns-disburse.md index e2f76055..9bbf7647 100644 --- a/docs/cli-reference/sns/quill-sns-disburse.md +++ b/docs/cli-reference/sns/quill-sns-disburse.md @@ -32,10 +32,10 @@ quill sns disburse [option] ## Remarks -Only a neuron that has fully dissolved may be disbursed. To start dissolving a neuron, see [`quill sns configure-dissolve-delay`]. +Only a neuron that has fully dissolved may be disbursed. To start dissolving a neuron, see [`quill sns dissolve`]. If `` is unset, it will default to the caller; if `` is unset, it will fully consume the neuron. As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. -[`quill sns configure-dissolve-delay`]: quill-sns-configure-dissolve-delay.md +[`quill sns dissolve`]: quill-sns-dissolve.md diff --git a/docs/cli-reference/sns/quill-sns-dissolve-delay.md b/docs/cli-reference/sns/quill-sns-dissolve-delay.md new file mode 100644 index 00000000..aac0bd5d --- /dev/null +++ b/docs/cli-reference/sns/quill-sns-dissolve-delay.md @@ -0,0 +1,39 @@ +# quill sns dissolve-delay + +Signs a neuron configure message to increase the dissolve delay of a neuron. + +## Basic usage + +The basic syntax for running `quill sns dissolve-delay` commands is: + +```bash +quill sns dissolve-delay <--increase-by |--increase-to > +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to configure. | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | + +## Options + +| Option | Description | +|----------------------------|------------------------------------------------------------------| +| `--increase-by ` | Additional time to add to the neuron's dissolve delay, e.g. '1y' | +| `--increase-to ` | Total time to set the neuron's dissolve delay to, e.g. '4y' | + +## Remarks + +The dissolve delay of a neuron determines its voting power, its ability to vote, its ability to make proposals, and other actions it can take (such as disbursing). If the neuron is already dissolving and this command is used, the neuron will stop dissolving and begin aging. + +For technical reasons, to hit the maximum dissolve delay, you will need to use `--increase-by`, not `--increase-to`. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-dissolve.md b/docs/cli-reference/sns/quill-sns-dissolve.md new file mode 100644 index 00000000..e8129ebb --- /dev/null +++ b/docs/cli-reference/sns/quill-sns-dissolve.md @@ -0,0 +1,34 @@ +# quill sns dissolve + +Signs a neuron configuration message to start or stop dissolving. + +## Basic usage + +The basic syntax for running `quill sns dissolve` commands is: + +```bash +quill sns dissolve <--start|--stop> +``` + +## Arguments + +| Argument | Description | +|---------------|----------------------------| +| `` | The neuron being dissolved | + + +## Flags + +| Flag | Description | +|----------------|-----------------------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--start` | The neuron will go into the dissolving state. | +| `--stop` | The neuron will exit the dissolving state. | + +## Remarks + +When the neuron goes into the dissolving state, a countdown timer will begin. When the timer is exhausted (i.e. the dissolve delay has elapsed), the neuron can be disbursed. When the neuron exits the dissolving state, whatever amount of time is left in the countdown timer is stored. A neuron's dissolve delay can be extended (for instance to increase voting power) by using the [`quill sns dissolve-delay`] command. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. + +[`quill sns dissolve-delay`]: quill-sns-dissolve-delay.md diff --git a/docs/cli-reference/sns/quill-sns-follow-neuron.md b/docs/cli-reference/sns/quill-sns-follow.md similarity index 90% rename from docs/cli-reference/sns/quill-sns-follow-neuron.md rename to docs/cli-reference/sns/quill-sns-follow.md index 5a51fd03..8cfca601 100644 --- a/docs/cli-reference/sns/quill-sns-follow-neuron.md +++ b/docs/cli-reference/sns/quill-sns-follow.md @@ -1,13 +1,13 @@ -# quill sns follow-neuron +# quill sns follow Configures a neuron to follow another neuron or group of neurons. ## Basic usage -The basic syntax for running `quill sns follow-neuron` commands is: +The basic syntax for running `quill sns follow` commands is: ```bash -quill sns follow-neuron <--type |--function-id > <--followees |--unfollow> [option] +quill sns follow <--type |--function-id > <--followees |--unfollow> [option] ``` ## Arguments diff --git a/docs/cli-reference/sns/quill-sns-get-sale-participation.md b/docs/cli-reference/sns/quill-sns-get-sale-participation.md index 681bcc28..1a4e5c62 100644 --- a/docs/cli-reference/sns/quill-sns-get-sale-participation.md +++ b/docs/cli-reference/sns/quill-sns-get-sale-participation.md @@ -27,3 +27,5 @@ quill sns get-sale-participation [option] ## Remarks If the principal is unspecified, the caller's principal will be used. + +As this is a query call, it cannot be executed on an air-gapped machine, but does not require access to your keys. diff --git a/docs/cli-reference/sns/quill-sns-get-swap-refund.md b/docs/cli-reference/sns/quill-sns-get-swap-refund.md index b760c3b5..55102a52 100644 --- a/docs/cli-reference/sns/quill-sns-get-swap-refund.md +++ b/docs/cli-reference/sns/quill-sns-get-swap-refund.md @@ -25,3 +25,5 @@ quill sns get-swap-refund [option] ## Remarks If no principal is provided, the sender's principal will be used. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-list-deployed-snses.md b/docs/cli-reference/sns/quill-sns-list-deployed-snses.md index 71d092a3..331875c4 100644 --- a/docs/cli-reference/sns/quill-sns-list-deployed-snses.md +++ b/docs/cli-reference/sns/quill-sns-list-deployed-snses.md @@ -17,3 +17,7 @@ quill sns list-deployed-snses [option] | `--dry-run` | Will display the query, but not send it. | | `-h`, `--help` | Displays usage information. | | `-y`, `--yes` | Skips confirmation and sends the message directly. | + +## Remarks + +As this is a query call, it cannot be executed on an air-gapped machine, but does not require access to your keys. diff --git a/docs/cli-reference/sns/quill-sns-make-proposal.md b/docs/cli-reference/sns/quill-sns-make-proposal.md index 1c75da56..8c4fc19e 100644 --- a/docs/cli-reference/sns/quill-sns-make-proposal.md +++ b/docs/cli-reference/sns/quill-sns-make-proposal.md @@ -41,3 +41,5 @@ Proposals must be formatted as candid records. For example: } ) ``` + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-make-upgrade-canister-proposal.md b/docs/cli-reference/sns/quill-sns-make-upgrade-canister-proposal.md index cc530f73..9370ca0b 100644 --- a/docs/cli-reference/sns/quill-sns-make-upgrade-canister-proposal.md +++ b/docs/cli-reference/sns/quill-sns-make-upgrade-canister-proposal.md @@ -36,3 +36,5 @@ quill sns make-upgrade-canister-proposal --target-canister- ## Remarks If an empty summary is provided, a somewhat generic summary will be constructed. The default title is "Upgrade Canister". + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-neuron-permission.md b/docs/cli-reference/sns/quill-sns-neuron-permission.md index 26be57d7..50b4f363 100644 --- a/docs/cli-reference/sns/quill-sns-neuron-permission.md +++ b/docs/cli-reference/sns/quill-sns-neuron-permission.md @@ -48,3 +48,5 @@ Multiple permissions can be specified in one command. The possible permissions a * `disburse-maturity` * `stake-maturity` * `manage-voting-permission` + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-new-sale-ticket.md b/docs/cli-reference/sns/quill-sns-new-sale-ticket.md index fd708747..98529298 100644 --- a/docs/cli-reference/sns/quill-sns-new-sale-ticket.md +++ b/docs/cli-reference/sns/quill-sns-new-sale-ticket.md @@ -13,9 +13,9 @@ quill sns new-sale-ticket --amount-icp-e8s [option] ## Flags -| Flag | Description | -|-----------------|-----------------------------| -| `-h`, `--help` | Displays usage information. | +| Flag | Description | +|----------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | ## Options @@ -23,3 +23,7 @@ quill sns new-sale-ticket --amount-icp-e8s [option] |-----------------------------|-----------------------------------------------------------| | `--amount-icp-e8s ` | The amount of ICP tokens to participate in this sns sale. | | `--subaccount ` | The subaccount you will use to pay for this ticket. | + +## Remarks + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-pay.md b/docs/cli-reference/sns/quill-sns-pay.md index 71a716ab..5a6dc16a 100644 --- a/docs/cli-reference/sns/quill-sns-pay.md +++ b/docs/cli-reference/sns/quill-sns-pay.md @@ -38,3 +38,5 @@ quill sns pay --amount-icp-e8s --ticket-creation-time --proposal-id --vote [option] -``` - -## Arguments - -| Argument | Description | -|---------------|------------------------------------| -| `` | The ID of the neuron to configure. | - -## Flags - -| Flag | Description | -|----------------|-----------------------------| -| `-h`, `--help` | Displays usage information. | - -## Options - -| Option | Description | -|-------------------------------|--------------------------------------------| -| `--proposal-id ` | The ID of the proposal to be voted on. | -| `--vote ` | The vote to be cast on the proposal (y/n). | diff --git a/docs/cli-reference/sns/quill-sns-split-neuron.md b/docs/cli-reference/sns/quill-sns-split.md similarity index 68% rename from docs/cli-reference/sns/quill-sns-split-neuron.md rename to docs/cli-reference/sns/quill-sns-split.md index 52011af6..546f7b34 100644 --- a/docs/cli-reference/sns/quill-sns-split-neuron.md +++ b/docs/cli-reference/sns/quill-sns-split.md @@ -1,13 +1,13 @@ -# quill sns split-neuron +# quill sns split Splits a neuron into two neurons. ## Basic usage -The basic syntax for running `quill sns split-neuron` commands is: +The basic syntax for running `quill sns split` commands is: ```bash -quill sns split-neuron --amount --memo [option] +quill sns split --amount <--name |--nonce > [option] ``` ## Arguments @@ -27,11 +27,12 @@ quill sns split-neuron --amount --memo [option] | Option | Description | |---------------------|------------------------------------------------------| -| `--memo ` | A number to identify this neuron. | +| `--nonce ` | A number to identify this neuron. | +| `--name ` | A name to identify this neuron. | | `--amount ` | The number of tokens, in decimal form, to split off. | ## Remarks -As with other commands staking a new neuron, `` must be unique among your neurons for this SNS. +As with other commands staking a new neuron, ``/`` must be unique among your neurons for this SNS. As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-stake-maturity.md b/docs/cli-reference/sns/quill-sns-stake-maturity.md index 4e7fb9dd..96278331 100644 --- a/docs/cli-reference/sns/quill-sns-stake-maturity.md +++ b/docs/cli-reference/sns/quill-sns-stake-maturity.md @@ -2,14 +2,13 @@ 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. ## Basic usage The basic syntax for running `quill sns stake-maturity` commands is: ```bash -quill sns stake-maturity --percentage [option] +quill sns stake-maturity <--percentage |--automatic|--disable-automatic> [option] ``` ## Arguments @@ -20,12 +19,20 @@ quill sns stake-maturity --percentage [option] ## Flags -| Flag | Description | -|----------------|-----------------------------| -| `-h`, `--help` | Displays usage information. | +| Flag | Description | +|-----------------------|-------------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--automatic` | Enable automatic maturity staking. | +| `--disable-automatic` | Disable automatic maturity staking. | ## Options | Option | Description | |-----------------------------|---------------------------------------------------------| | `--percentage ` | The percentage of the current maturity to stake (1-100) | + +## Remarks + +A neuron's total stake is the combination of its staked governance tokens and staked maturity. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-stake-neuron.md b/docs/cli-reference/sns/quill-sns-stake-neuron.md index b16a5e2c..8998f19e 100644 --- a/docs/cli-reference/sns/quill-sns-stake-neuron.md +++ b/docs/cli-reference/sns/quill-sns-stake-neuron.md @@ -7,24 +7,25 @@ Signs messages needed to stake governance tokens for a neuron. First, stake-neur The basic syntax for running `quill sns stake-neuron` commands is: ```bash -quill sns stake-neuron --memo [option] +quill sns stake-neuron <--name |--nonce > --amount [option] ``` ## Flags -| Flag | Description | -|----------------|-----------------------------| -| `-h`, `--help` | Displays usage information. | -| `--claim-only` | No transfer will be made. | +| Flag | Description | +|-------------------------|-----------------------------| +| `-h`, `--help` | Displays usage information. | +| `--already-transferred` | No transfer will be made. | ## Options -| Option | Descriptio | +| Option | Description | |---------------------------------------|---------------------------------------------------------------------------| | `--amount ` | The amount of tokens in e8s to be transferred to the Governance canister. | | `--fee ` | The amount that the caller pays for the transaction. | | `--from-subaccount ` | The subaccount to make the transfer from. | -| `--memo ` | An arbitrary number used in calculating the neuron's subaccount. | +| `--nonce ` | An arbitrary number to identify this neuron. | +| `--name ` | A name to identify this neuron. | ## Remarks @@ -34,4 +35,6 @@ The memo must be unique among the neurons claimed for a single principal. More i The default fee is 0.0001 tokens. Use the `--fee` flag when using an SNS that sets its own transaction fee. -If `--claim-only` is specified, then only the neuron claim message will be generated. This is useful if there was an error previously submitting the notification which you have since rectified, or if you have made the transfer with another tool. +If `--already-transferred` is specified, then only the neuron claim message will be generated. This is useful if there was an error previously submitting the notification which you have since rectified, or if you have made the transfer with another tool. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-status.md b/docs/cli-reference/sns/quill-sns-status.md index c9dcd9e9..e1f21fa1 100644 --- a/docs/cli-reference/sns/quill-sns-status.md +++ b/docs/cli-reference/sns/quill-sns-status.md @@ -17,3 +17,7 @@ quill sns status [option] | `--dry-run` | Will display the query, but not send it. | | `-h`, `--help` | Displays usage information. | | `-y`, `--yes` | Skips confirmation and sends the message directly. | + +## Remarks + +As this is a query call, it cannot be executed on an air-gapped machine, but does not require access to your keys. diff --git a/docs/cli-reference/sns/quill-sns-transfer.md b/docs/cli-reference/sns/quill-sns-transfer.md index 952cba57..8a95b5fe 100644 --- a/docs/cli-reference/sns/quill-sns-transfer.md +++ b/docs/cli-reference/sns/quill-sns-transfer.md @@ -37,3 +37,5 @@ quill sns transfer --amount [option] The default fee is 0.0001 tokens. Use the `--fee` flag when using an SNS that sets its own transaction fee. The destination account can be specified as a separate principal and subaccount, or as a single ICRC-1 account. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/docs/cli-reference/sns/quill-sns-vote.md b/docs/cli-reference/sns/quill-sns-vote.md new file mode 100644 index 00000000..47dc7e51 --- /dev/null +++ b/docs/cli-reference/sns/quill-sns-vote.md @@ -0,0 +1,37 @@ +# quill sns vote + +Signs a ManageNeuron message to register a vote for a proposal. + +## Basic usage + +The basic syntax for running `quill sns vote` commands is: + +```bash +quill sns vote --proposal-id <--approve|--reject> [option] +``` + +## Arguments + +| Argument | Description | +|---------------|------------------------------------| +| `` | The ID of the neuron to configure. | + +## Flags + +| Flag | Description | +|----------------|-------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--approve` | Vote to approve the proposal. | +| `--reject` | Vote to reject the proposal. | + +## Options + +| Option | Description | +|-------------------------------|----------------------------------------| +| `--proposal-id ` | The ID of the proposal to be voted on. | + +## Remarks + +Registering a vote will update the ballot of the given proposal and could trigger followees to vote. When enough votes are cast or enough time passes, the proposal will either be rejected or adopted and executed. + +As this is an update call, it will not actually make the request, but rather generate a signed and packaged request that can be sent from anywhere. You can use the `--qr` flag to display it as a QR code, or if you are not working with an air-gapped machine, you can pipe it to `quill send`. diff --git a/e2e/tests-quill/create_neuron.bash b/e2e/tests-quill/create_neuron.bash index f369abe8..88a7903c 100644 --- a/e2e/tests-quill/create_neuron.bash +++ b/e2e/tests-quill/create_neuron.bash @@ -10,11 +10,11 @@ teardown() { @test "basic create neuron" { #account is initialized with 10_000 tokens - assert_command quill account-balance 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --yes --insecure-local-dev-mode + assert_command quill balance --of 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --yes --insecure-local-dev-mode assert_string_match '(record { e8s = 100_000_000_000_000_000 : nat64 })' # stake 3 tokens - assert_command bash -c "quill neuron-stake --amount 3 --name myneur --pem-file \"$PEM_LOCATION/identity.pem\" > stake.call" + assert_command bash -c "quill stake-neuron --amount 3 --name myneur --pem-file \"$PEM_LOCATION/identity.pem\" > stake.call" assert_file_not_empty stake.call SEND_OUTPUT="$(quill send stake.call --yes --insecure-local-dev-mode)" assert_command echo "$SEND_OUTPUT" # replay the output so string matches work @@ -32,7 +32,7 @@ teardown() { assert_string_match 'stake_e8s = 300_000_000' # increase dissolve delay by 6 months - assert_command bash -c "quill neuron-manage --additional-dissolve-delay-seconds 15778800 --pem-file \"$PEM_LOCATION/identity.pem\" \"$NEURON_ID\" > more-delay.call" + assert_command bash -c "quill dissolve-delay --increase-by '6 months' --pem-file \"$PEM_LOCATION/identity.pem\" \"$NEURON_ID\" > more-delay.call" assert_file_not_empty more-delay.call 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 diff --git a/src/commands/account_balance.rs b/src/commands/balance.rs similarity index 64% rename from src/commands/account_balance.rs rename to src/commands/balance.rs index 839f8c9e..607b5c1d 100644 --- a/src/commands/account_balance.rs +++ b/src/commands/balance.rs @@ -1,10 +1,11 @@ use crate::{ commands::send::submit_unsigned_ingress, lib::{ - ledger_canister_id, AnyhowResult, AuthInfo, ParsedNnsAccount, ROLE_ICRC1_LEDGER, - ROLE_NNS_LEDGER, + ledger_canister_id, AnyhowResult, AuthInfo, ParsedNnsAccount, ParsedSubaccount, + ROLE_ICRC1_LEDGER, ROLE_NNS_LEDGER, }, }; +use anyhow::ensure; use candid::{CandidType, Encode}; use clap::Parser; @@ -17,11 +18,19 @@ pub struct AccountBalanceArgs { /// Queries a ledger account balance. #[derive(Parser)] -pub struct AccountBalanceOpts { - /// The id of the account to query. Optional if a key is used. - #[clap(required_unless_present = "auth")] +#[clap(alias = "account-balance")] +pub struct BalanceOpts { + #[clap(hidden = true)] account_id: Option, + /// The id of the account to query. Optional if a key is used. + #[clap(long, required_unless_present_any = ["auth", "account-id"])] + of: Option, + + /// The subaccount of the account to query. + #[clap(long)] + subaccount: Option, + /// Skips confirmation and sends the message directly. #[clap(long, short)] yes: bool, @@ -31,10 +40,9 @@ pub struct AccountBalanceOpts { dry_run: bool, } -// We currently only support a subset of the functionality. #[tokio::main] -pub async fn exec(auth: &AuthInfo, opts: AccountBalanceOpts, fetch_root_key: bool) -> AnyhowResult { - let account_id = if let Some(id) = opts.account_id { +pub async fn exec(auth: &AuthInfo, opts: BalanceOpts, fetch_root_key: bool) -> AnyhowResult { + let account_id = if let Some(id) = opts.of.or(opts.account_id) { id } else { let (_, id) = get_ids(auth)?; @@ -42,6 +50,10 @@ pub async fn exec(auth: &AuthInfo, opts: AccountBalanceOpts, fetch_root_key: boo }; match account_id { ParsedNnsAccount::Original(id) => { + ensure!( + opts.subaccount.is_none(), + "Cannot specify both --subaccount and a legacy account ID" + ); let args = Encode!(&AccountBalanceArgs { account: id.to_hex() })?; @@ -56,7 +68,10 @@ pub async fn exec(auth: &AuthInfo, opts: AccountBalanceOpts, fetch_root_key: boo ) .await } - ParsedNnsAccount::Icrc1(id) => { + ParsedNnsAccount::Icrc1(mut id) => { + if let Some(sub) = opts.subaccount { + id.subaccount = Some(sub.0 .0); + } let args = Encode!(&id)?; submit_unsigned_ingress( ledger_canister_id(), diff --git a/src/commands/ckbtc/balance.rs b/src/commands/ckbtc/balance.rs index 0cc7052b..d2cc3ba1 100644 --- a/src/commands/ckbtc/balance.rs +++ b/src/commands/ckbtc/balance.rs @@ -19,8 +19,8 @@ pub struct BalanceOpts { of: Option, /// The subaccount of the account to check. - #[clap(long)] - of_subaccount: Option, + #[clap(long, alias = "of-subaccount")] + subaccount: Option, /// Skips confirmation and sends the message immediately. #[clap(long, short)] @@ -37,7 +37,7 @@ pub struct BalanceOpts { #[tokio::main] pub async fn exec(auth: &AuthInfo, opts: BalanceOpts, fetch_root_key: bool) -> AnyhowResult { - let account = get_account(Some(auth), opts.of, opts.of_subaccount)?; + let account = get_account(Some(auth), opts.of, opts.subaccount)?; submit_unsigned_ingress( ckbtc_canister_id(opts.testnet), ROLE_ICRC1_LEDGER, diff --git a/src/commands/community_fund.rs b/src/commands/community_fund.rs new file mode 100644 index 00000000..62e7a445 --- /dev/null +++ b/src/commands/community_fund.rs @@ -0,0 +1,65 @@ +use anyhow::ensure; +use candid::Encode; +use clap::{ArgGroup, Parser}; +use ic_nns_governance::pb::v1::{ + manage_neuron::{ + configure::Operation, Command, Configure, JoinCommunityFund, LeaveCommunityFund, + }, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a message to join or leave the Internet Computer's community fund with this neuron's maturity. +#[derive(Parser)] +#[clap(group(ArgGroup::new("state").required(true)))] +pub struct CommunityFundOpts { + /// The ID of the neuron to configure. + neuron_id: ParsedNeuron, + + /// Join the community fund. + #[clap(long, group = "state")] + join: bool, + + /// Leave the community fund. + #[clap(long, group = "state")] + leave: bool, + + #[clap(from_global)] + ledger: bool, +} + +pub fn exec(auth: &AuthInfo, opts: CommunityFundOpts) -> AnyhowResult> { + ensure!( + !opts.ledger, + "Cannot use `--ledger` with this command. This version of Quill does not support joining or leaving the community fund with a Ledger device.", + ); + let command = if opts.join { + Command::Configure(Configure { + operation: Some(Operation::JoinCommunityFund(JoinCommunityFund {})), + }) + } else if opts.leave { + Command::Configure(Configure { + operation: Some(Operation::LeaveCommunityFund(LeaveCommunityFund {})), + }) + } else { + unreachable!() + }; + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(command), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/disburse.rs b/src/commands/disburse.rs new file mode 100644 index 00000000..312d71a5 --- /dev/null +++ b/src/commands/disburse.rs @@ -0,0 +1,79 @@ +use anyhow::{bail, ensure}; +use candid::Encode; +use clap::Parser; +use ic_nns_governance::pb::v1::{ + manage_neuron::{disburse::Amount, Command, Disburse}, + ManageNeuron, +}; +use icp_ledger::Tokens; +use icrc_ledger_types::icrc1::account::Account; + +use crate::lib::parse_tokens; +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ParsedNnsAccount, ParsedSubaccount, ROLE_NNS_GOVERNANCE, +}; + +use super::get_ids; + +/// Signs a disbursal message to convert a dissolved neuron into ICP. +#[derive(Parser)] +pub struct DisburseOpts { + /// The ID of the neuron to disburse. + neuron_id: ParsedNeuron, + + /// The account to transfer the ICP to. If unset, defaults to the caller. + #[clap(long, required_unless_present = "auth")] + to: Option, + + /// The subaccount to transfer to. + #[clap(long)] + subaccount: Option, + + /// The number of tokens, in decimal form, to disburse. If unset, fully consumes the neuron. + #[clap(long, value_parser = parse_tokens)] + amount: Option, + + #[clap(from_global)] + ledger: bool, +} + +pub fn exec(auth: &AuthInfo, opts: DisburseOpts) -> AnyhowResult> { + ensure!(!opts.ledger, "Cannot use `--ledger` with this command. This version of Quill does not support disbursing neurons with a Ledger device."); + let to = if let Some(sub) = opts.subaccount { + match opts.to { + Some(ParsedNnsAccount::Icrc1(acct)) => Some(ParsedNnsAccount::Icrc1(Account { + subaccount: Some(sub.0 .0), + ..acct + })), + Some(ParsedNnsAccount::Original(_)) => { + bail!("Cannot specify both --subaccount and a legacy account ID") + } + None => Some(ParsedNnsAccount::Icrc1(Account { + owner: get_ids(auth)?.0, + subaccount: Some(sub.0 .0), + })), + } + } else { + opts.to + }; + let args = ManageNeuron { + command: Some(Command::Disburse(Disburse { + amount: opts.amount.map(|amount| Amount { + e8s: amount.get_e8s(), + }), + to_account: to.map(|to| to.into_identifier().into()), + })), + id: Some(opts.neuron_id.0), + neuron_id_or_subaccount: None, + }; + let message = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&args)?, + )?; + Ok(vec![message]) +} diff --git a/src/commands/dissolve.rs b/src/commands/dissolve.rs new file mode 100644 index 00000000..6823c78c --- /dev/null +++ b/src/commands/dissolve.rs @@ -0,0 +1,55 @@ +use candid::Encode; +use clap::{ArgGroup, Parser}; +use ic_nns_governance::pb::v1::{ + manage_neuron::{configure::Operation, Command, Configure, StartDissolving, StopDissolving}, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron configuration message to start or stop a neuron dissolving. +#[derive(Parser)] +#[clap(group = ArgGroup::new("state").required(true))] +pub struct DissolveOpts { + /// The ID of the neuron to dissolve. + neuron_id: ParsedNeuron, + + /// Start dissolving the neuron. + #[clap(long, group = "state")] + start: bool, + + /// Stop dissolving the neuron. + #[clap(long, group = "state")] + stop: bool, +} + +pub fn exec(auth: &AuthInfo, opts: DissolveOpts) -> AnyhowResult> { + let command = if opts.start { + Command::Configure(Configure { + operation: Some(Operation::StartDissolving(StartDissolving {})), + }) + } else if opts.stop { + Command::Configure(Configure { + operation: Some(Operation::StopDissolving(StopDissolving {})), + }) + } else { + unreachable!() + }; + let arg = Encode!(&ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(command), + neuron_id_or_subaccount: None, + })?; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + arg, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/dissolve_delay.rs b/src/commands/dissolve_delay.rs new file mode 100644 index 00000000..97b42904 --- /dev/null +++ b/src/commands/dissolve_delay.rs @@ -0,0 +1,73 @@ +use std::time::Duration; + +use crate::lib::{ + governance_canister_id, now, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +use anyhow::ensure; +use candid::Encode; +use clap::{ArgGroup, Parser}; +use humantime::Duration as HumanDuration; +use ic_nns_governance::pb::v1::{ + manage_neuron::{ + configure::Operation, Command, Configure, IncreaseDissolveDelay, SetDissolveTimestamp, + }, + ManageNeuron, +}; + +/// Signs a neuron configuration change to increase a neuron's dissolve delay. +#[derive(Parser)] +#[clap(group(ArgGroup::new("delay").required(true)))] +pub struct DissolveDelayOpts { + /// The ID of the neuron to configure. + neuron_id: ParsedNeuron, + + /// Additional time to add to the neuron's dissolve delay, e.g. '1y' + #[clap(long, group = "delay", value_name = "DURATION")] + increase_by: Option, + + /// Total time to set the neuron's dissolve delay to, e.g. '4y' + #[clap(long, group = "delay", value_name = "DURATION")] + increase_to: Option, +} + +const DELAY_MAX: Duration = Duration::from_secs(252_460_800); + +pub fn exec(auth: &AuthInfo, opts: DissolveDelayOpts) -> AnyhowResult> { + let command = if let Some(by) = opts.increase_by { + ensure!(*by <= DELAY_MAX, "Cannot increase by more than eight years"); + Command::Configure(Configure { + operation: Some(Operation::IncreaseDissolveDelay(IncreaseDissolveDelay { + additional_dissolve_delay_seconds: by.as_secs() as u32, + })), + }) + } else if let Some(to) = opts.increase_to { + ensure!(*to <= DELAY_MAX, "Cannot increase to more than eight years"); + // pad by a couple minutes to account for clock drift; user is likely trying to hit a milestone + let to = (*to + Duration::from_secs(300)).max(DELAY_MAX); + let now = now(); + let future = now + to; + Command::Configure(Configure { + operation: Some(Operation::SetDissolveTimestamp(SetDissolveTimestamp { + dissolve_timestamp_seconds: future.unix_timestamp() as u64, + })), + }) + } else { + unreachable!() + }; + let arg = Encode!(&ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(command), + neuron_id_or_subaccount: None, + })?; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + arg, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/follow.rs b/src/commands/follow.rs new file mode 100644 index 00000000..ba2a69c8 --- /dev/null +++ b/src/commands/follow.rs @@ -0,0 +1,84 @@ +use anyhow::ensure; +use candid::Encode; +use clap::{ArgAction, ArgEnum, ArgGroup, Parser}; +use ic_nns_governance::pb::v1::{ + manage_neuron::{Command, Follow}, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron configuration message to change a neuron's follow relationships. +#[derive(Parser)] +#[clap( + group(ArgGroup::new("topic").required(true)), + group(ArgGroup::new("following").required(true)) +)] +pub struct FollowOpts { + /// The ID of the neuron to configure. + neuron_id: ParsedNeuron, + + /// The name of the proposal topic to restrict following to. + #[clap(long, arg_enum, group = "topic")] + r#type: Option, + + /// The numeric ID of the proposal topic to restrict following to. + #[clap(long, group = "topic", alias = "function-id")] + topic_id: Option, + + /// A comma-separated list of neuron IDs to follow. + #[clap(long, action = ArgAction::Append, value_delimiter = ',', group = "following")] + followees: Vec, + + /// Unfollow all neurons for this topic. + #[clap(long, group = "following")] + unfollow: bool, + + #[clap(from_global)] + ledger: bool, +} + +pub fn exec(auth: &AuthInfo, opts: FollowOpts) -> AnyhowResult> { + ensure!(!opts.ledger, "Cannot use `--ledger` with this command. This version of Quill does not support changing follow relationships with a Ledger device."); + let topic = opts.topic_id.unwrap_or_else(|| opts.r#type.unwrap() as i32); + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(Command::Follow(Follow { + followees: opts.followees.into_iter().map(|x| x.0).collect(), // empty vec if --unfollow was specified + topic, + })), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} + +#[derive(ArgEnum, Clone)] +enum Type { + All = 0, + #[clap(alias = "manage-neuron")] + NeuronManagement = 1, + ExchangeRate = 2, + NetworkEconomics = 3, + Governance = 4, + NodeAdmin = 5, + ParticipantManagement = 6, + SubnetManagement = 7, + NetworkCanisterManagement = 8, + Kyc = 9, + NodeProviderRewards = 10, + SnsDecentralizationSale = 11, + SubnetReplicaVersionManagement = 12, + ReplicaVersionManagement = 13, + SnsAndCommunityFund = 14, +} diff --git a/src/commands/hotkey.rs b/src/commands/hotkey.rs new file mode 100644 index 00000000..36552baf --- /dev/null +++ b/src/commands/hotkey.rs @@ -0,0 +1,64 @@ +use anyhow::ensure; +use candid::{Encode, Principal}; +use clap::{ArgGroup, Parser}; +use ic_nns_governance::pb::v1::{ + manage_neuron::{configure::Operation, AddHotKey, Command, Configure, RemoveHotKey}, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron configuration message to add or remove a hotkey. +#[derive(Parser)] +#[clap(group(ArgGroup::new("operation").required(true)), alias = "hot-key")] +pub struct HotkeyOpts { + /// The ID of the neuron to configure. + neuron_id: ParsedNeuron, + + /// Add the specified principal as a hotkey. + #[clap(long, group = "operation", value_name = "PRINCIPAL")] + add: Option, + + /// Remove the specified principal as a hotkey. + #[clap(long, group = "operation", value_name = "PRINCIPAL")] + remove: Option, + + #[clap(from_global)] + ledger: bool, +} + +pub fn exec(auth: &AuthInfo, opts: HotkeyOpts) -> AnyhowResult> { + ensure!(!opts.ledger, "Cannot use `--ledger` with this command. This version of Quill does not support modifying hotkeys with a Ledger device."); + let command = if let Some(add) = opts.add { + Command::Configure(Configure { + operation: Some(Operation::AddHotKey(AddHotKey { + new_hot_key: Some(add.into()), + })), + }) + } else if let Some(remove) = opts.remove { + Command::Configure(Configure { + operation: Some(Operation::RemoveHotKey(RemoveHotKey { + hot_key_to_remove: Some(remove.into()), + })), + }) + } else { + unreachable!() + }; + let arg = Encode!(&ManageNeuron { + command: Some(command), + id: Some(opts.neuron_id.0), + neuron_id_or_subaccount: None, + })?; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + arg, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/merge.rs b/src/commands/merge.rs new file mode 100644 index 00000000..25b38a61 --- /dev/null +++ b/src/commands/merge.rs @@ -0,0 +1,41 @@ +use candid::Encode; +use clap::Parser; +use ic_nns_governance::pb::v1::{ + manage_neuron::{Command, Merge}, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron management message to merge another neuron into this one. +#[derive(Parser)] +pub struct MergeOpts { + /// The ID of the neuron to merge into. + neuron_id: ParsedNeuron, + + /// The ID of the neuron to merge from. + #[clap(long)] + from: ParsedNeuron, +} + +pub fn exec(auth: &AuthInfo, opts: MergeOpts) -> AnyhowResult> { + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(Command::Merge(Merge { + source_neuron_id: Some(opts.from.0), + })), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index ca6b3af6..e4e4896d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,24 +6,35 @@ use clap::Parser; use icrc_ledger_types::icrc1::account::Account; use std::io::{self, Write}; -mod account_balance; +mod balance; mod ckbtc; mod claim_neurons; +mod community_fund; +mod disburse; +mod dissolve; +mod dissolve_delay; +mod follow; mod generate; mod get_neuron_info; mod get_proposal_info; +mod hotkey; mod list_neurons; mod list_proposals; +mod merge; mod neuron_manage; -mod neuron_stake; mod public; mod qrcode; mod replace_node_provider_id; mod request_status; mod send; mod sns; +mod spawn; +mod split; +mod stake_maturity; +mod stake_neuron; mod transfer; mod update_node_provider; +mod vote; pub use public::get_ids; @@ -33,13 +44,25 @@ pub enum Command { Send(send::SendOpts), Transfer(transfer::TransferOpts), ClaimNeurons(claim_neurons::ClaimNeuronOpts), - NeuronStake(neuron_stake::StakeOpts), + CommunityFund(community_fund::CommunityFundOpts), + StakeNeuron(stake_neuron::StakeOpts), + Disburse(disburse::DisburseOpts), + Dissolve(dissolve::DissolveOpts), + DissolveDelay(dissolve_delay::DissolveDelayOpts), + Follow(follow::FollowOpts), + Hotkey(hotkey::HotkeyOpts), + Merge(merge::MergeOpts), + #[clap(hide = true)] NeuronManage(neuron_manage::ManageOpts), ListNeurons(list_neurons::ListNeuronsOpts), ListProposals(list_proposals::ListProposalsOpts), + Spawn(spawn::SpawnOpts), + Split(split::SplitOpts), + StakeMaturity(stake_maturity::StakeMaturityOpts), + Vote(vote::VoteOpts), GetProposalInfo(get_proposal_info::GetProposalInfoOpts), GetNeuronInfo(get_neuron_info::GetNeuronInfoOpts), - AccountBalance(account_balance::AccountBalanceOpts), + Balance(balance::BalanceOpts), UpdateNodeProvider(update_node_provider::UpdateNodeProviderOpts), ReplaceNodeProviderId(replace_node_provider_id::ReplaceNodeProviderIdOpts), #[clap(subcommand)] @@ -58,14 +81,58 @@ pub fn dispatch(auth: &AuthInfo, cmd: Command, fetch_root_key: bool, qr: bool) - let out = transfer::exec(auth, opts)?; print_vec(qr, &out)?; } - Command::NeuronStake(opts) => { - let out = neuron_stake::exec(auth, opts)?; + Command::Disburse(opts) => { + let out = disburse::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Dissolve(opts) => { + let out = dissolve::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::DissolveDelay(opts) => { + let out = dissolve_delay::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::CommunityFund(opts) => { + let out = community_fund::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Follow(opts) => { + let out = follow::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Hotkey(opts) => { + let out = hotkey::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Merge(opts) => { + let out = merge::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::StakeNeuron(opts) => { + let out = stake_neuron::exec(auth, opts)?; print_vec(qr, &out)?; } Command::NeuronManage(opts) => { let out = neuron_manage::exec(auth, opts)?; print_vec(qr, &out)?; } + Command::Spawn(opts) => { + let out = spawn::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Split(opts) => { + let out = split::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::StakeMaturity(opts) => { + let out = stake_maturity::exec(auth, opts)?; + print_vec(qr, &out)?; + } + Command::Vote(opts) => { + let out = vote::exec(auth, opts)?; + print_vec(qr, &out)?; + } Command::ListNeurons(opts) => { let out = list_neurons::exec(auth, opts)?; print_vec(qr, &out)?; @@ -82,8 +149,8 @@ pub fn dispatch(auth: &AuthInfo, cmd: Command, fetch_root_key: bool, qr: bool) - Command::GetNeuronInfo(opts) => { get_neuron_info::exec(opts, fetch_root_key)?; } - Command::AccountBalance(opts) => { - account_balance::exec(auth, opts, fetch_root_key)?; + Command::Balance(opts) => { + balance::exec(auth, opts, fetch_root_key)?; } Command::UpdateNodeProvider(opts) => { let out = update_node_provider::exec(auth, opts)?; diff --git a/src/commands/neuron_manage.rs b/src/commands/neuron_manage.rs index f96a5660..1358d64b 100644 --- a/src/commands/neuron_manage.rs +++ b/src/commands/neuron_manage.rs @@ -1,4 +1,5 @@ -use crate::commands::transfer::parse_tokens; +use crate::lib::parse_tokens; +use crate::lib::parse_neuron_id; use crate::lib::{ governance_canister_id, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, @@ -143,7 +144,7 @@ Cannot use --ledger with these flags. This version of quill only supports the fo let mut msgs = Vec::new(); let id = Some(NeuronId { - id: parse_neuron_id(opts.neuron_id)?, + id: parse_neuron_id(&opts.neuron_id)?, }); if opts.add_hot_key.is_some() { let args = Encode!(&ManageNeuron { @@ -291,7 +292,7 @@ Cannot use --ledger with these flags. This version of quill only supports the fo id: id.clone(), command: Some(Command::Merge(Merge { source_neuron_id: Some(NeuronId { - id: parse_neuron_id(neuron_id)? + id: parse_neuron_id(&neuron_id)? }), })), neuron_id_or_subaccount: None, @@ -398,9 +399,3 @@ Cannot use --ledger with these flags. This version of quill only supports the fo } Ok(generated) } - -fn parse_neuron_id(id: String) -> AnyhowResult { - id.replace('_', "") - .parse() - .context("Failed to parse the neuron id") -} diff --git a/src/commands/sns.rs b/src/commands/sns.rs index dfdfbd9e..d1f8c98d 100644 --- a/src/commands/sns.rs +++ b/src/commands/sns.rs @@ -21,7 +21,9 @@ mod balance; mod configure_dissolve_delay; mod disburse; mod disburse_maturity; -mod follow_neuron; +mod dissolve; +mod dissolve_delay; +mod follow; mod get_sale_participation; mod get_swap_refund; mod list_deployed_snses; @@ -31,12 +33,12 @@ mod neuron_id; mod neuron_permission; mod new_sale_ticket; mod pay; -mod register_vote; -mod split_neuron; +mod split; mod stake_maturity; mod stake_neuron; mod status; mod transfer; +mod vote; /// Commands for interacting with a Service Nervous System's Ledger & Governance canisters. /// @@ -63,10 +65,13 @@ pub struct SnsOpts { #[derive(Subcommand)] pub enum SnsCommand { Balance(balance::BalanceOpts), + #[clap(hide = true)] ConfigureDissolveDelay(configure_dissolve_delay::ConfigureDissolveDelayOpts), Disburse(disburse::DisburseOpts), DisburseMaturity(disburse_maturity::DisburseMaturityOpts), - FollowNeuron(follow_neuron::FollowNeuronOpts), + Dissolve(dissolve::DissolveOpts), + DissolveDelay(dissolve_delay::DissolveDelayOpts), + Follow(follow::FollowOpts), GetSwapRefund(get_swap_refund::GetSwapRefundOpts), ListDeployedSnses(list_deployed_snses::ListDeployedSnsesOpts), MakeProposal(make_proposal::MakeProposalOpts), @@ -74,9 +79,9 @@ pub enum SnsCommand { NeuronId(neuron_id::NeuronIdOpts), NeuronPermission(neuron_permission::NeuronPermissionOpts), NewSaleTicket(new_sale_ticket::NewSaleTicketOpts), - RegisterVote(register_vote::RegisterVoteOpts), + Vote(vote::VoteOpts), GetSaleParticipation(get_sale_participation::GetSaleParticipationOpts), - SplitNeuron(split_neuron::SplitNeuronOpts), + Split(split::SplitOpts), StakeMaturity(stake_maturity::StakeMaturityOpts), StakeNeuron(stake_neuron::StakeNeuronOpts), Status(status::StatusOpts), @@ -90,6 +95,7 @@ pub fn dispatch(auth: &AuthInfo, opts: SnsOpts, qr: bool, fetch_root_key: bool) opts.subcommand, SnsCommand::Balance(_) | SnsCommand::Transfer(_) | SnsCommand::NeuronPermission(_) | SnsCommand::Disburse(_) | SnsCommand::ConfigureDissolveDelay(_) | SnsCommand::StakeMaturity(_) | SnsCommand::NeuronId(_) + | SnsCommand::Dissolve(_) | SnsCommand::DissolveDelay(_) ), "Cannot use --ledger with this command. This version of Quill only supports transfers and certain neuron management operations with a Ledger device"); } let canister_ids = opts.canister_ids_file @@ -111,8 +117,16 @@ pub fn dispatch(auth: &AuthInfo, opts: SnsOpts, qr: bool, fetch_root_key: bool) let out = disburse_maturity::exec(auth, &canister_ids?, opts)?; print_vec(qr, &out)?; } - SnsCommand::FollowNeuron(opts) => { - let out = follow_neuron::exec(auth, &canister_ids?, opts)?; + SnsCommand::Dissolve(opts) => { + let out = dissolve::exec(auth, &canister_ids?, opts)?; + print_vec(qr, &out)?; + } + SnsCommand::DissolveDelay(opts) => { + let out = dissolve_delay::exec(auth, &canister_ids?, opts)?; + print_vec(qr, &out)?; + } + SnsCommand::Follow(opts) => { + let out = follow::exec(auth, &canister_ids?, opts)?; print_vec(qr, &out)?; } SnsCommand::GetSwapRefund(opts) => { @@ -139,15 +153,15 @@ pub fn dispatch(auth: &AuthInfo, opts: SnsOpts, qr: bool, fetch_root_key: bool) let out = new_sale_ticket::exec(auth, &canister_ids?, opts)?; print_vec(qr, &out)?; } - SnsCommand::RegisterVote(opts) => { - let out = register_vote::exec(auth, &canister_ids?, opts)?; + SnsCommand::Vote(opts) => { + let out = vote::exec(auth, &canister_ids?, opts)?; print_vec(qr, &out)?; } SnsCommand::GetSaleParticipation(opts) => { get_sale_participation::exec(auth, &canister_ids?, opts, fetch_root_key)? } - SnsCommand::SplitNeuron(opts) => { - let out = split_neuron::exec(auth, &canister_ids?, opts)?; + SnsCommand::Split(opts) => { + let out = split::exec(auth, &canister_ids?, opts)?; print_vec(qr, &out)?; } SnsCommand::StakeMaturity(opts) => { diff --git a/src/commands/sns/configure_dissolve_delay.rs b/src/commands/sns/configure_dissolve_delay.rs index 5ccc0944..450d0525 100644 --- a/src/commands/sns/configure_dissolve_delay.rs +++ b/src/commands/sns/configure_dissolve_delay.rs @@ -19,6 +19,10 @@ use ic_sns_governance::pb::v1::{ use super::{ParsedSnsNeuron, SnsCanisterIds}; +/* + Deprecated. Further functionality should target the dissolve or dissolve-delay commands. +*/ + /// 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 diff --git a/src/commands/sns/disburse.rs b/src/commands/sns/disburse.rs index a593f78f..3a94b66d 100644 --- a/src/commands/sns/disburse.rs +++ b/src/commands/sns/disburse.rs @@ -7,8 +7,9 @@ use ic_sns_governance::pb::v1::{ use icp_ledger::Tokens; use crate::{ - commands::{get_account, transfer::parse_tokens}, + commands::get_account, lib::{ + parse_tokens, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, ParsedAccount, ParsedSubaccount, ROLE_SNS_GOVERNANCE, }, diff --git a/src/commands/sns/disburse_maturity.rs b/src/commands/sns/disburse_maturity.rs index 77dd011f..e0be6b16 100644 --- a/src/commands/sns/disburse_maturity.rs +++ b/src/commands/sns/disburse_maturity.rs @@ -17,6 +17,7 @@ use super::{governance_account, ParsedSnsNeuron, SnsCanisterIds}; /// Converts the maturity from a neuron into SNS utility tokens. #[derive(Parser)] +#[clap(alias = "spawn")] pub struct DisburseMaturityOpts { /// The neuron ID to disburse maturity from. neuron_id: ParsedSnsNeuron, diff --git a/src/commands/sns/dissolve.rs b/src/commands/sns/dissolve.rs new file mode 100644 index 00000000..6bfc9a0b --- /dev/null +++ b/src/commands/sns/dissolve.rs @@ -0,0 +1,63 @@ +use candid::Encode; +use clap::{ArgGroup, Parser}; +use ic_sns_governance::pb::v1::{ + manage_neuron::{configure::Operation, Command, Configure, StartDissolving, StopDissolving}, + ManageNeuron, +}; + +use crate::lib::{ + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ROLE_SNS_GOVERNANCE, +}; + +use super::{ParsedSnsNeuron, SnsCanisterIds}; + +/// Signs a neuron configuration message to start or stop dissolving. +#[derive(Parser)] +#[clap(group = ArgGroup::new("state").required(true))] +pub struct DissolveOpts { + /// The neuron being dissolved. + neuron_id: ParsedSnsNeuron, + + /// The neuron will go into the dissolving state and a + /// countdown timer will begin. When the timer is exhausted (i.e. the dissolve delay + /// has elapsed), the neuron can be disbursed. + #[clap(long, group = "state")] + start: bool, + /// The neuron will exit the dissolving state and whatever + /// amount of time is left in the countdown timer is stored. A neuron's + /// dissolve delay can be extended (for instance to increase voting power) by using the + /// `quill sns dissolve-delay` command. + #[clap(long, group = "state")] + stop: bool, +} + +pub fn exec( + auth: &AuthInfo, + sns_canister_ids: &SnsCanisterIds, + opts: DissolveOpts, +) -> AnyhowResult> { + let command = if opts.start { + Command::Configure(Configure { + operation: Some(Operation::StartDissolving(StartDissolving {})), + }) + } else if opts.stop { + Command::Configure(Configure { + operation: Some(Operation::StopDissolving(StopDissolving {})), + }) + } else { + unreachable!() + }; + let arg = Encode!(&ManageNeuron { + command: Some(command), + subaccount: opts.neuron_id.0.subaccount().unwrap().to_vec(), + })?; + let msg = sign_ingress_with_request_status_query( + auth, + sns_canister_ids.governance_canister_id, + ROLE_SNS_GOVERNANCE, + "manage_neuron", + arg, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/sns/dissolve_delay.rs b/src/commands/sns/dissolve_delay.rs new file mode 100644 index 00000000..f95ef691 --- /dev/null +++ b/src/commands/sns/dissolve_delay.rs @@ -0,0 +1,79 @@ +use std::time::Duration; + +use anyhow::ensure; +use candid::Encode; +use clap::{ArgGroup, Parser}; +use humantime::Duration as HumanDuration; +use ic_sns_governance::pb::v1::{ + manage_neuron::{ + configure::Operation, Command, Configure, IncreaseDissolveDelay, SetDissolveTimestamp, + }, + ManageNeuron, +}; + +use crate::lib::{ + now, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ROLE_SNS_GOVERNANCE, +}; + +use super::{ParsedSnsNeuron, SnsCanisterIds}; + +/// Signs a neuron configure message to increase the dissolve delay of a neuron. +/// The dissolve delay of a neuron determines its voting power, its ability to vote, its ability +/// to make proposals, and other actions it can take (such as disbursing). +#[derive(Parser)] +#[clap(group = ArgGroup::new("delay").required(true))] +pub struct DissolveDelayOpts { + /// The ID of the neuron to configure. + neuron_id: ParsedSnsNeuron, + + /// Additional time to add to the neuron's dissolve delay, e.g. '1y' + #[clap(long, group = "delay", value_name = "DURATION")] + increase_by: Option, + /// Total time to set the neuron's dissolve delay to, e.g. '4y' + #[clap(long, group = "delay", value_name = "DURATION")] + increase_to: Option, +} + +const DELAY_MAX: Duration = Duration::from_secs(252_460_800); + +pub fn exec( + auth: &AuthInfo, + sns_canister_ids: &SnsCanisterIds, + opts: DissolveDelayOpts, +) -> AnyhowResult> { + let command = if let Some(by) = opts.increase_by { + ensure!(*by <= DELAY_MAX, "Cannot increase by more than eight years"); + Command::Configure(Configure { + operation: Some(Operation::IncreaseDissolveDelay(IncreaseDissolveDelay { + additional_dissolve_delay_seconds: by.as_secs() as u32, + })), + }) + } else if let Some(to) = opts.increase_to { + ensure!(*to <= DELAY_MAX, "Cannot increase to more than eight years"); + // pad by a couple minutes to account for clock drift; user is likely trying to hit a milestone + let to = (*to + Duration::from_secs(300)).max(DELAY_MAX); + let now = now(); + let future = now + to; + Command::Configure(Configure { + operation: Some(Operation::SetDissolveTimestamp(SetDissolveTimestamp { + dissolve_timestamp_seconds: future.unix_timestamp() as u64, + })), + }) + } else { + unreachable!() + }; + let arg = Encode!(&ManageNeuron { + command: Some(command), + subaccount: opts.neuron_id.0.subaccount().unwrap().to_vec() + })?; + let msg = sign_ingress_with_request_status_query( + auth, + sns_canister_ids.governance_canister_id, + ROLE_SNS_GOVERNANCE, + "manage_neuron", + arg, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/sns/follow_neuron.rs b/src/commands/sns/follow.rs similarity index 95% rename from src/commands/sns/follow_neuron.rs rename to src/commands/sns/follow.rs index f0faea3d..0df6edf5 100644 --- a/src/commands/sns/follow_neuron.rs +++ b/src/commands/sns/follow.rs @@ -24,15 +24,16 @@ use super::{ParsedSnsNeuron, SnsCanisterIds}; #[clap( group(ArgGroup::new("function").required(true)), group(ArgGroup::new("following").required(true)), + alias = "follow-neuron" )] -pub struct FollowNeuronOpts { +pub struct FollowOpts { /// The neuron to configure. neuron_id: ParsedSnsNeuron, /// The name of the built-in proposal function to restrict following to. #[clap(long, group = "function", value_enum)] r#type: Option, /// The numeric ID of the proposal function to restrict following to. - #[clap(long, group = "function")] + #[clap(long, group = "function", alias = "topic-id")] function_id: Option, /// A list of neurons to follow for this proposal function, separated by commas. #[clap(long, action = ArgAction::Append, value_delimiter = ',', group = "following")] @@ -60,7 +61,7 @@ enum Type { pub fn exec( auth: &AuthInfo, canister_ids: &SnsCanisterIds, - opts: FollowNeuronOpts, + opts: FollowOpts, ) -> AnyhowResult> { let function_id = if let Some(id) = opts.function_id { id diff --git a/src/commands/sns/split_neuron.rs b/src/commands/sns/split.rs similarity index 62% rename from src/commands/sns/split_neuron.rs rename to src/commands/sns/split.rs index a5766c5d..6de7fe0d 100644 --- a/src/commands/sns/split_neuron.rs +++ b/src/commands/sns/split.rs @@ -6,24 +6,31 @@ use ic_sns_governance::pb::v1::{ }; use icp_ledger::Tokens; -use crate::{ - commands::transfer::parse_tokens, - lib::{ - signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, - AnyhowResult, AuthInfo, ROLE_SNS_GOVERNANCE, - }, +use crate::lib::{ + neuron_name_to_nonce, parse_tokens, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ROLE_SNS_GOVERNANCE, }; use super::{ParsedSnsNeuron, SnsCanisterIds}; /// Splits a neuron into two neurons. #[derive(Parser)] -pub struct SplitNeuronOpts { +#[clap(alias = "split-neuron")] +pub struct SplitOpts { /// The neuron to split. neuron_id: ParsedSnsNeuron, /// A number to identify this neuron. Must be unique among your neurons for this SNS. - #[clap(long)] - memo: u64, + #[clap(long, alias = "memo")] + nonce: Option, + /// A name to identify this neuron. Must be unique among your neurons for this SNS. + #[clap( + long, + conflicts_with = "nonce", + required_unless_present = "nonce", + value_parser = neuron_name_to_nonce, + )] + name: Option, /// The number of tokens, in decimal form, to split off. #[clap(long, value_parser = parse_tokens)] amount: Tokens, @@ -32,13 +39,13 @@ pub struct SplitNeuronOpts { pub fn exec( auth: &AuthInfo, canister_ids: &SnsCanisterIds, - opts: SplitNeuronOpts, + opts: SplitOpts, ) -> AnyhowResult> { let args = ManageNeuron { subaccount: opts.neuron_id.0.id, command: Some(Command::Split(Split { amount_e8s: opts.amount.get_e8s(), - memo: opts.memo, + memo: opts.name.unwrap_or_else(|| opts.nonce.unwrap()), })), }; let message = sign_ingress_with_request_status_query( diff --git a/src/commands/sns/stake_maturity.rs b/src/commands/sns/stake_maturity.rs index c9363c77..5a163141 100644 --- a/src/commands/sns/stake_maturity.rs +++ b/src/commands/sns/stake_maturity.rs @@ -1,8 +1,10 @@ use anyhow::Error; use candid::Encode; -use clap::Parser; +use clap::{ArgGroup, Parser}; use ic_sns_governance::pb::v1::{ - manage_neuron::{Command, StakeMaturity}, + manage_neuron::{ + configure::Operation, ChangeAutoStakeMaturity, Command, Configure, StakeMaturity, + }, ManageNeuron, }; @@ -17,10 +19,18 @@ use super::{ParsedSnsNeuron, SnsCanisterIds}; /// /// A neuron's total stake is the combination of its staked governance tokens and staked maturity. #[derive(Parser)] +#[clap(group(ArgGroup::new("operation").required(true)))] pub struct StakeMaturityOpts { /// The percentage of the current maturity to stake (1-100). - #[clap(long, value_parser = 1..=100)] - percentage: i64, + #[clap(long, value_parser = 1..=100, group = "operation")] + percentage: Option, + + /// Enable automatic maturity staking. + #[clap(long, group = "operation")] + automatic: bool, + /// Disable automatic maturity staking. + #[clap(long, group = "operation")] + disable_automatic: bool, /// The id of the neuron to configure as a hex encoded string. neuron_id: ParsedSnsNeuron, @@ -35,10 +45,29 @@ pub fn exec( let governance_canister_id = sns_canister_ids.governance_canister_id; - let command = ManageNeuron { - command: Some(Command::StakeMaturity(StakeMaturity { - percentage_to_stake: Some(opts.percentage as u32), - })), + let command = if opts.automatic { + Command::Configure(Configure { + operation: Some(Operation::ChangeAutoStakeMaturity( + ChangeAutoStakeMaturity { + requested_setting_for_auto_stake_maturity: true, + }, + )), + }) + } else if opts.disable_automatic { + Command::Configure(Configure { + operation: Some(Operation::ChangeAutoStakeMaturity( + ChangeAutoStakeMaturity { + requested_setting_for_auto_stake_maturity: false, + }, + )), + }) + } else { + Command::StakeMaturity(StakeMaturity { + percentage_to_stake: Some(opts.percentage.unwrap_or(100) as u32), + }) + }; + let arg = ManageNeuron { + command: Some(command), subaccount: neuron_subaccount.to_vec(), }; @@ -47,7 +76,7 @@ pub fn exec( governance_canister_id, ROLE_SNS_GOVERNANCE, "manage_neuron", - Encode!(&command)?, + Encode!(&arg)?, )?; Ok(vec![message]) } diff --git a/src/commands/sns/stake_neuron.rs b/src/commands/sns/stake_neuron.rs index 93c5202e..b863ea2c 100644 --- a/src/commands/sns/stake_neuron.rs +++ b/src/commands/sns/stake_neuron.rs @@ -1,14 +1,10 @@ -use crate::commands::transfer::parse_tokens; -use crate::lib::now_nanos; -use crate::lib::signing::{ - sign_ingress_with_request_status_query, sign_staking_ingress_with_request_status_query, -}; -use crate::{ - lib::{ - signing::IngressWithRequestId, AuthInfo, ParsedSubaccount, ROLE_ICRC1_LEDGER, - ROLE_SNS_GOVERNANCE, +use crate::lib::{ + neuron_name_to_nonce, now_nanos, parse_tokens, + signing::IngressWithRequestId, + signing::{ + sign_ingress_with_request_status_query, sign_staking_ingress_with_request_status_query, }, - AnyhowResult, + AnyhowResult, AuthInfo, ParsedSubaccount, ROLE_ICRC1_LEDGER, ROLE_SNS_GOVERNANCE, }; use candid::Encode; use clap::Parser; @@ -36,18 +32,27 @@ pub struct StakeNeuronOpts { /// The amount of tokens to be transferred to the Governance canister's ledger subaccount /// (the neuron's AccountId) from the AccountId derived from the provided private key. This is /// known as a staking transfer. These funds will be returned when disbursing the neuron. - #[clap(long, value_parser = parse_tokens, required_unless_present = "claim-only")] + #[clap(long, value_parser = parse_tokens, required_unless_present = "already-transferred")] amount: Option, /// The subaccount to make the transfer from. Only necessary if `--amount` is specified. #[clap(long, requires = "amount")] from_subaccount: Option, - /// An arbitrary number used in calculating the neuron's subaccount. The memo must be unique among - /// the neurons claimed for a single PrincipalId. More information on ledger accounts and - /// subaccounts can be found here: https://smartcontracts.org/docs/integration/ledger-quick-start.html#_ledger_canister_overview - #[clap(long)] - memo: u64, + /// An arbitrary number to identify this neuron. The nonce must be unique among + /// the neurons claimed for a single principal. + #[clap(long, alias = "memo")] + nonce: Option, + + /// A name to identify this neuron. The name must be unique among the + /// neurons claimed for a single principal. + #[clap( + long, + value_parser = neuron_name_to_nonce, + conflicts_with = "nonce", + required_unless_present = "nonce", + )] + name: Option, /// The amount that the caller pays for the transaction, default is 0.0001 tokens. Specify this amount /// when using an SNS that sets its own transaction fee @@ -57,8 +62,13 @@ pub struct StakeNeuronOpts { /// If this flag is set, then no transfer will be made, and only the neuron claim message will be generated. /// This is useful if there was an error previously submitting the notification which you have since rectified, /// or if you have made the transfer with another tool. - #[clap(long, conflicts_with = "amount", conflicts_with = "from-subaccount")] - claim_only: bool, + #[clap( + long, + conflicts_with = "amount", + conflicts_with = "from-subaccount", + alias = "claim-only" + )] + already_transferred: bool, } pub fn exec( @@ -66,8 +76,9 @@ pub fn exec( sns_canister_ids: &SnsCanisterIds, opts: StakeNeuronOpts, ) -> AnyhowResult> { + let nonce = opts.name.unwrap_or_else(|| opts.nonce.unwrap()); let (controller, _) = crate::commands::public::get_ids(auth)?; - let neuron_subaccount = ledger::compute_neuron_staking_subaccount(controller.into(), opts.memo); + let neuron_subaccount = ledger::compute_neuron_staking_subaccount(controller.into(), nonce); let governance_canister_id = sns_canister_ids.governance_canister_id; @@ -77,7 +88,7 @@ pub fn exec( // account on the ledger to a subaccount of the governance canister. if let Some(amount) = opts.amount { let args = TransferArg { - memo: Some(Memo::from(opts.memo)), + memo: Some(Memo::from(nonce)), amount: amount.get_e8s().into(), fee: opts.fee.map(|fee| fee.get_e8s().into()), from_subaccount: opts.from_subaccount.map(|x| x.0 .0), @@ -103,7 +114,7 @@ pub fn exec( subaccount: neuron_subaccount.to_vec(), command: Some(manage_neuron::Command::ClaimOrRefresh(ClaimOrRefresh { by: Some(By::MemoAndController(MemoAndController { - memo: opts.memo, + memo: nonce, controller: Some(controller.into()), })) })) diff --git a/src/commands/sns/transfer.rs b/src/commands/sns/transfer.rs index 7abbd55d..4d516bb5 100644 --- a/src/commands/sns/transfer.rs +++ b/src/commands/sns/transfer.rs @@ -1,5 +1,5 @@ use crate::commands::get_account; -use crate::commands::transfer::parse_tokens; +use crate::lib::parse_tokens; use crate::lib::{now_nanos, ParsedAccount, ROLE_ICRC1_LEDGER}; use crate::lib::{ signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, diff --git a/src/commands/sns/register_vote.rs b/src/commands/sns/vote.rs similarity index 68% rename from src/commands/sns/register_vote.rs rename to src/commands/sns/vote.rs index d190c19f..cf924558 100644 --- a/src/commands/sns/register_vote.rs +++ b/src/commands/sns/vote.rs @@ -5,9 +5,9 @@ use crate::{ }, AnyhowResult, }; -use anyhow::{anyhow, Error}; +use anyhow::Error; use candid::Encode; -use clap::Parser; +use clap::{ArgGroup, Parser}; use ic_sns_governance::pb::v1::{ manage_neuron, manage_neuron::RegisterVote, ManageNeuron, ProposalId, Vote, }; @@ -19,34 +19,47 @@ use super::{ParsedSnsNeuron, SnsCanisterIds}; /// enough votes are cast or enough time passes, the proposal will either be rejected or /// adopted and executed. #[derive(Parser)] -pub struct RegisterVoteOpts { +#[clap(alias = "register-vote")] +#[clap(group(ArgGroup::new("yn").required(true)))] +pub struct VoteOpts { /// The id of the neuron to configure as a hex encoded string. neuron_id: ParsedSnsNeuron, - /// The id of the proposal to voted on. + /// The id of the proposal to be voted on. #[clap(long)] proposal_id: u64, /// The vote to be cast on the proposal [y/n] - #[clap(long)] - vote: String, + #[clap(long, hide = true, group = "yn", value_parser = ["y", "n"])] + vote: Option, + + /// Vote to approve the proposal. + #[clap(long, group = "yn")] + approve: bool, + /// Vote to reject the proposal. + #[clap(long, group = "yn")] + reject: bool, } pub fn exec( auth: &AuthInfo, sns_canister_ids: &SnsCanisterIds, - opts: RegisterVoteOpts, + opts: VoteOpts, ) -> AnyhowResult> { let neuron_subaccount = opts.neuron_id.0.subaccount().map_err(Error::msg)?; let governance_canister_id = sns_canister_ids.governance_canister_id; - let vote = match opts.vote.as_str() { - "y" => Ok(Vote::Yes), - "n" => Ok(Vote::No), - _ => Err(anyhow!( - "Unsupported vote supplied to --vote. Supported values: ['y', 'n']" - )), - }?; + let vote = if opts.approve { + Vote::Yes + } else if opts.reject { + Vote::No + } else { + match opts.vote.unwrap().as_str() { + "y" => Vote::Yes, + "n" => Vote::No, + _ => unreachable!(), + } + }; let args = Encode!(&ManageNeuron { subaccount: neuron_subaccount.to_vec(), diff --git a/src/commands/spawn.rs b/src/commands/spawn.rs new file mode 100644 index 00000000..38e7f9f7 --- /dev/null +++ b/src/commands/spawn.rs @@ -0,0 +1,46 @@ +use candid::{Encode, Principal}; +use clap::Parser; +use ic_nns_governance::pb::v1::{ + manage_neuron::{Command, Spawn}, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron management message to convert a neuron's maturity into a rapidly-dissolving neuron. +#[derive(Parser)] +#[clap(alias = "disburse-maturity")] +pub struct SpawnOpts { + /// The ID of the neuron to spawn from. + neuron_id: ParsedNeuron, + /// The owner of the spawned neuron. + #[clap(long)] + to: Option, + /// The percentage of the maturity to spawn. + #[clap(long, value_parser = 1..=100)] + percentage: Option, +} + +pub fn exec(auth: &AuthInfo, opts: SpawnOpts) -> AnyhowResult> { + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(Command::Spawn(Spawn { + new_controller: opts.to.map(|x| x.into()), + nonce: None, + percentage_to_spawn: opts.percentage.map(|x| x as u32), + })), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/split.rs b/src/commands/split.rs new file mode 100644 index 00000000..9197159f --- /dev/null +++ b/src/commands/split.rs @@ -0,0 +1,41 @@ +use candid::Encode; +use clap::Parser; +use ic_nns_governance::pb::v1::{ + manage_neuron::{Command, Split}, + ManageNeuron, +}; +use icp_ledger::Tokens; + +use crate::lib::{ + governance_canister_id, parse_tokens, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron management message to split a neuron in two. +#[derive(Parser)] +pub struct SplitOpts { + /// The ID of the neuron to split. + neuron_id: ParsedNeuron, + /// The amount of the stake that should be split, in ICP. + #[clap(long, value_parser = parse_tokens)] + amount: Tokens, +} + +pub fn exec(auth: &AuthInfo, opts: SplitOpts) -> AnyhowResult> { + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(Command::Split(Split { + amount_e8s: opts.amount.get_e8s(), + })), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/stake_maturity.rs b/src/commands/stake_maturity.rs new file mode 100644 index 00000000..977969ca --- /dev/null +++ b/src/commands/stake_maturity.rs @@ -0,0 +1,68 @@ +use candid::Encode; +use clap::{ArgGroup, Parser}; +use ic_nns_governance::pb::v1::{ + manage_neuron::{ + configure::Operation, ChangeAutoStakeMaturity, Command, Configure, StakeMaturity, + }, + ManageNeuron, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron management message to add maturity to a neuron's stake, or configure auto-staking. +#[derive(Parser)] +#[clap(group(ArgGroup::new("operation").required(true)))] +pub struct StakeMaturityOpts { + /// The ID of the neuron to manage. + neuron_id: ParsedNeuron, + /// The percentage of the neuron's accrued maturity to stake. + #[clap(long, value_parser = 1..=100, group = "operation")] + percentage: Option, + /// Enable automatic maturity staking. + #[clap(long, group = "operation")] + automatic: bool, + /// Disable automatic maturity staking. + #[clap(long, group = "operation")] + disable_automatic: bool, +} + +pub fn exec(auth: &AuthInfo, opts: StakeMaturityOpts) -> AnyhowResult> { + let command = if opts.automatic { + Command::Configure(Configure { + operation: Some(Operation::ChangeAutoStakeMaturity( + ChangeAutoStakeMaturity { + requested_setting_for_auto_stake_maturity: true, + }, + )), + }) + } else if opts.disable_automatic { + Command::Configure(Configure { + operation: Some(Operation::ChangeAutoStakeMaturity( + ChangeAutoStakeMaturity { + requested_setting_for_auto_stake_maturity: false, + }, + )), + }) + } else { + Command::StakeMaturity(StakeMaturity { + percentage_to_stake: Some(opts.percentage.unwrap() as u32), + }) + }; + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(command), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/commands/neuron_stake.rs b/src/commands/stake_neuron.rs similarity index 70% rename from src/commands/neuron_stake.rs rename to src/commands/stake_neuron.rs index 9b42be73..bc4b6a2b 100644 --- a/src/commands/neuron_stake.rs +++ b/src/commands/stake_neuron.rs @@ -1,19 +1,17 @@ use crate::{ - commands::{ - send::Memo, - transfer::{self, parse_tokens}, - }, + commands::{send::Memo, transfer}, lib::{ - governance_canister_id, + governance_canister_id, neuron_name_to_nonce, parse_tokens, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, ParsedNnsAccount, ParsedSubaccount, ROLE_NNS_GOVERNANCE, }, }; -use anyhow::{anyhow, ensure}; +use anyhow::ensure; use candid::{CandidType, Encode, Principal}; use clap::Parser; use ic_nns_constants::GOVERNANCE_CANISTER_ID; -use icp_ledger::{AccountIdentifier, Subaccount, Tokens}; +use icp_ledger::Tokens; +use icrc_ledger_types::icrc1::account::Account; #[derive(CandidType)] pub struct ClaimOrRefreshNeuronFromAccount { @@ -35,11 +33,11 @@ pub struct StakeOpts { /// The name of the neuron (up to 8 ASCII characters). #[clap( long, - validator(neuron_name_validator), + value_parser = neuron_name_to_nonce, conflicts_with = "nonce", required_unless_present = "nonce" )] - name: Option, + name: Option, /// The nonce of the neuron. #[clap(long)] @@ -63,22 +61,22 @@ pub fn exec(auth: &AuthInfo, opts: StakeOpts) -> AnyhowResult *nonce, - (_, Some(name)) => convert_name_to_nonce(name), - _ => return Err(anyhow!("Either a nonce or a name should be specified")), - }; + let nonce = opts.name.unwrap_or_else(|| opts.nonce.unwrap()); let gov_subaccount = get_neuron_subaccount(&controller, nonce); - let account = AccountIdentifier::new(GOVERNANCE_CANISTER_ID.get(), Some(gov_subaccount)); + let account = Account { + owner: GOVERNANCE_CANISTER_ID.into(), + subaccount: Some(gov_subaccount), + }; let mut messages = if !opts.already_transferred { transfer::exec( auth, transfer::TransferOpts { - to: ParsedNnsAccount::Original(account), + to: ParsedNnsAccount::Icrc1(account), amount: opts.amount.unwrap(), fee: opts.fee, memo: Some(nonce), from_subaccount: opts.from_subaccount, + to_subaccount: None, }, )? } else { @@ -102,29 +100,12 @@ pub fn exec(auth: &AuthInfo, opts: StakeOpts) -> AnyhowResult Subaccount { +fn get_neuron_subaccount(controller: &Principal, nonce: u64) -> [u8; 32] { use openssl::sha::Sha256; let mut data = Sha256::new(); data.update(&[0x0c]); data.update(b"neuron-stake"); data.update(controller.as_slice()); data.update(&nonce.to_be_bytes()); - Subaccount(data.finish()) -} - -fn convert_name_to_nonce(name: &str) -> u64 { - let mut bytes = std::collections::VecDeque::from(name.as_bytes().to_vec()); - while bytes.len() < 8 { - bytes.push_front(0) - } - let mut arr: [u8; 8] = [0; 8]; - arr.copy_from_slice(&bytes.into_iter().collect::>()); - u64::from_be_bytes(arr) -} - -fn neuron_name_validator(name: &str) -> Result<(), String> { - if name.len() > 8 || name.chars().any(|c| !c.is_ascii()) { - return Err("The neuron name must be 8 character or less".to_string()); - } - Ok(()) + data.finish() } diff --git a/src/commands/transfer.rs b/src/commands/transfer.rs index bfd74114..190a4e36 100644 --- a/src/commands/transfer.rs +++ b/src/commands/transfer.rs @@ -1,13 +1,10 @@ use crate::commands::send::{Memo, SendArgs, TimeStamp}; use crate::lib::{ - ledger_canister_id, + ledger_canister_id, now_nanos, parse_tokens, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, - AnyhowResult, AuthInfo, + AnyhowResult, AuthInfo, ParsedNnsAccount, ParsedSubaccount, ROLE_ICRC1_LEDGER, ROLE_NNS_LEDGER, }; -use crate::lib::{ - now_nanos, ParsedNnsAccount, ParsedSubaccount, ROLE_ICRC1_LEDGER, ROLE_NNS_LEDGER, -}; -use anyhow::{anyhow, bail, Context}; +use anyhow::ensure; use candid::Encode; use clap::Parser; use icp_ledger::{Tokens, DEFAULT_TRANSFER_FEE}; @@ -19,6 +16,10 @@ pub struct TransferOpts { /// Destination account. pub to: ParsedNnsAccount, + /// Destination subaccount. + #[clap(long)] + pub to_subaccount: Option, + /// Amount of ICPs to transfer (with up to 8 decimal digits after comma). #[clap(long, value_parser = parse_tokens)] pub amount: Tokens, @@ -43,6 +44,10 @@ pub fn exec(auth: &AuthInfo, opts: TransferOpts) -> AnyhowResult { + ensure!( + opts.to_subaccount.is_none(), + "Cannot specify both --subaccount and a legacy account ID" + ); let args = Encode!(&SendArgs { memo, amount, @@ -63,7 +68,10 @@ pub fn exec(auth: &AuthInfo, opts: TransferOpts) -> AnyhowResult { + ParsedNnsAccount::Icrc1(mut to) => { + if let Some(sub) = opts.to_subaccount { + to.subaccount = Some(sub.0 .0); + } let args = Encode!(&TransferArg { memo: Some(memo.0.into()), amount: amount.get_e8s().into(), @@ -83,28 +91,3 @@ pub fn exec(auth: &AuthInfo, opts: TransferOpts) -> AnyhowResult AnyhowResult { - Tokens::new(tokens, e8s) - .map_err(|err| anyhow!(err)) - .context("Cannot create new tokens structure") -} - -pub fn parse_tokens(amount: &str) -> AnyhowResult { - let parse = |s: &str| { - s.parse::() - .context("Failed to parse tokens as unsigned integer") - }; - match &amount.split('.').collect::>().as_slice() { - [tokens] => new_tokens(parse(tokens)?, 0), - [tokens, e8s] => { - let mut e8s = e8s.to_string(); - while e8s.len() < 8 { - e8s.push('0'); - } - let e8s = &e8s[..8]; - new_tokens(parse(tokens)?, parse(e8s)?) - } - _ => bail!("Cannot parse amount {}", amount), - } -} diff --git a/src/commands/vote.rs b/src/commands/vote.rs new file mode 100644 index 00000000..3e6dcbe6 --- /dev/null +++ b/src/commands/vote.rs @@ -0,0 +1,66 @@ +use anyhow::ensure; +use candid::Encode; +use clap::{ArgGroup, Parser}; +use ic_nns_common::pb::v1::ProposalId; +use ic_nns_governance::pb::v1::{ + manage_neuron::{Command, RegisterVote}, + ManageNeuron, Vote, +}; + +use crate::lib::{ + governance_canister_id, + signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, + AnyhowResult, AuthInfo, ParsedNeuron, ROLE_NNS_GOVERNANCE, +}; + +/// Signs a neuron management message to vote on a proposal. +#[derive(Parser)] +#[clap(group(ArgGroup::new("yn").required(true)))] +pub struct VoteOpts { + /// The ID of the neuron to vote with. + neuron_id: ParsedNeuron, + + /// Vote to approve the proposal. + #[clap(long, group = "yn")] + approve: bool, + + /// Vote to reject the proposal. + #[clap(long, group = "yn")] + reject: bool, + + /// The ID of the proposal to vote on. + #[clap(long)] + proposal_id: u64, + + #[clap(from_global)] + ledger: bool, +} + +pub fn exec(auth: &AuthInfo, opts: VoteOpts) -> AnyhowResult> { + ensure!(!opts.ledger, "Cannot use `--ledger` with this command. This version of Quill does not support voting with a Ledger device."); + let vote = if opts.approve { + Vote::Yes + } else if opts.reject { + Vote::No + } else { + unreachable!(); + }; + let arg = ManageNeuron { + id: Some(opts.neuron_id.0), + command: Some(Command::RegisterVote(RegisterVote { + proposal: Some(ProposalId { + id: opts.proposal_id, + }), + vote: vote as i32, + })), + neuron_id_or_subaccount: None, + }; + let msg = sign_ingress_with_request_status_query( + auth, + governance_canister_id(), + ROLE_NNS_GOVERNANCE, + "manage_neuron", + Encode!(&arg)?, + )?; + Ok(vec![msg]) +} diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 91d3879c..9d4aebc6 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -17,11 +17,12 @@ use ic_agent::{ use ic_base_types::PrincipalId; #[cfg(feature = "hsm")] use ic_identity_hsm::HardwareIdentity; +use ic_nns_common::pb::v1::NeuronId; use ic_nns_constants::{ GENESIS_TOKEN_CANISTER_ID, GOVERNANCE_CANISTER_ID, LEDGER_CANISTER_ID, REGISTRY_CANISTER_ID, SNS_WASM_CANISTER_ID, }; -use icp_ledger::{AccountIdentifier, Subaccount}; +use icp_ledger::{AccountIdentifier, Subaccount, Tokens}; use icrc_ledger_types::icrc1::account::Account; use k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey}; use pem::{encode, Pem}; @@ -30,6 +31,7 @@ use simple_asn1::ASN1Block::{ BitString, Explicit, Integer, ObjectIdentifier, OctetString, Sequence, }; use simple_asn1::{oid, to_der, ASN1Class, BigInt, BigUint}; +use std::str::FromStr; #[cfg(feature = "hsm")] use std::{cell::RefCell, path::PathBuf}; use std::{ @@ -38,7 +40,7 @@ use std::{ path::Path, time::Duration, }; -use std::{str::FromStr, time::SystemTime}; +use time::OffsetDateTime; #[cfg(feature = "ledger")] use self::ledger::LedgerIdentity; @@ -372,6 +374,24 @@ pub fn get_account_id(principal_id: Principal) -> AnyhowResult AnyhowResult { + id.replace('_', "") + .parse() + .context("Invalid digit in neuron ID") +} + +#[derive(Clone)] +pub struct ParsedNeuron(pub NeuronId); + +impl FromStr for ParsedNeuron { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + Ok(Self(NeuronId { + id: parse_neuron_id(s)?, + })) + } +} + /// Converts menmonic to PEM format pub fn mnemonic_to_pem(mnemonic: &Mnemonic) -> AnyhowResult { fn der_encode_secret_key(public_key: Vec, secret: Vec) -> AnyhowResult> { @@ -422,6 +442,44 @@ fn derivation_path() -> DerivationPath { DERIVATION_PATH.parse().unwrap() } +pub fn parse_tokens(amount: &str) -> AnyhowResult { + let parse = |s: &str| { + s.parse::() + .context("Failed to parse tokens as unsigned integer") + }; + match &amount.split('.').collect::>().as_slice() { + [tokens] => new_tokens(parse(tokens)?, 0), + [tokens, e8s] => { + let mut e8s = e8s.to_string(); + while e8s.len() < 8 { + e8s.push('0'); + } + let e8s = &e8s[..8]; + new_tokens(parse(tokens)?, parse(e8s)?) + } + _ => bail!("Cannot parse amount {}", amount), + } +} + +fn new_tokens(tokens: u64, e8s: u64) -> AnyhowResult { + Tokens::new(tokens, e8s) + .map_err(|err| anyhow!(err)) + .context("Cannot create new tokens structure") +} + +pub fn neuron_name_to_nonce(name: &str) -> AnyhowResult { + if name.len() > 8 || name.chars().any(|c| !c.is_ascii()) { + bail!("The neuron name must be 8 character or less"); + } + let mut bytes = std::collections::VecDeque::from(name.as_bytes().to_vec()); + while bytes.len() < 8 { + bytes.push_front(0) + } + let mut arr: [u8; 8] = [0; 8]; + arr.copy_from_slice(&bytes.into_iter().collect::>()); + Ok(u64::from_be_bytes(arr)) +} + pub struct ParsedSubaccount(pub Subaccount); impl FromStr for ParsedSubaccount { @@ -563,14 +621,19 @@ impl ParsedNnsAccount { } } +pub fn now() -> OffsetDateTime { + if std::env::var("QUILL_TEST_FIXED_TIMESTAMP").is_ok() { + OffsetDateTime::from_unix_timestamp_nanos(1_669_073_904_187_044_208).unwrap() + } else { + OffsetDateTime::now_utc() + } +} + pub fn now_nanos() -> u64 { if std::env::var("QUILL_TEST_FIXED_TIMESTAMP").is_ok() { 1_669_073_904_187_044_208 } else { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_nanos() as u64 + OffsetDateTime::now_utc().unix_timestamp_nanos() as u64 } } diff --git a/tests/output/default/neuron_manage/disburse.txt b/tests/output/default/neuron_manage/disburse.txt new file mode 100644 index 00000000..906ee0ac --- /dev/null +++ b/tests/output/default/neuron_manage/disburse.txt @@ -0,0 +1,15 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Disburse = record { to_account = null; amount = null } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/default/neuron_manage/disburse_stop_dissolving.txt b/tests/output/default/neuron_manage/disburse_stop_dissolving.txt deleted file mode 100644 index 1ab1b0db..00000000 --- a/tests/output/default/neuron_manage/disburse_stop_dissolving.txt +++ /dev/null @@ -1,32 +0,0 @@ -Sending message with - - Call type: update - Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae - Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai - Method name: manage_neuron - Arguments: ( - record { - id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; - command = opt variant { - Configure = record { - operation = opt variant { StopDissolving = record {} }; - } - }; - neuron_id_or_subaccount = null; - }, -) -Sending message with - - Call type: update - Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae - Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai - Method name: manage_neuron - Arguments: ( - record { - id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; - command = opt variant { - Disburse = record { to_account = null; amount = null } - }; - neuron_id_or_subaccount = null; - }, -) diff --git a/tests/output/default/neuron_manage/dissolve_fixed_timestamp.txt b/tests/output/default/neuron_manage/dissolve_fixed_timestamp.txt new file mode 100644 index 00000000..a3f371de --- /dev/null +++ b/tests/output/default/neuron_manage/dissolve_fixed_timestamp.txt @@ -0,0 +1,21 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Configure = record { + operation = opt variant { + SetDissolveTimestamp = record { + dissolve_timestamp_seconds = 1_921_534_704 : nat64; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/default/neuron_manage/remove_hot_key.txt b/tests/output/default/neuron_manage/remove_hot_key.txt new file mode 100644 index 00000000..f7ecad10 --- /dev/null +++ b/tests/output/default/neuron_manage/remove_hot_key.txt @@ -0,0 +1,21 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Configure = record { + operation = opt variant { + RemoveHotKey = record { + hot_key_to_remove = opt principal "fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae"; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/default/neuron_manage/spawn_to.txt b/tests/output/default/neuron_manage/spawn_to.txt new file mode 100644 index 00000000..ac2cb795 --- /dev/null +++ b/tests/output/default/neuron_manage/spawn_to.txt @@ -0,0 +1,19 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Spawn = record { + percentage_to_spawn = opt (20 : nat32); + new_controller = opt principal "pnf55-r7gzn-s3oqn-ah2v7-r6b63-a2ma2-wyzhb-dzbwb-sghid-lzcxh-4ae"; + nonce = null; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/default/neuron_manage/vote.txt b/tests/output/default/neuron_manage/vote.txt index acdf6845..495f8615 100644 --- a/tests/output/default/neuron_manage/vote.txt +++ b/tests/output/default/neuron_manage/vote.txt @@ -16,21 +16,3 @@ Sending message with neuron_id_or_subaccount = null; }, ) -Sending message with - - Call type: update - Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae - Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai - Method name: manage_neuron - Arguments: ( - record { - id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; - command = opt variant { - RegisterVote = record { - vote = 1 : int32; - proposal = opt record { id = 456 : nat64 }; - } - }; - neuron_id_or_subaccount = null; - }, -) diff --git a/tests/output/default/neuron_stake/with_name.txt b/tests/output/default/neuron_stake/with_name.txt index eac5c4af..27ebd6f2 100644 --- a/tests/output/default/neuron_stake/with_name.txt +++ b/tests/output/default/neuron_stake/with_name.txt @@ -3,17 +3,18 @@ Sending message with Call type: update Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai - Method name: send_dfx + Method name: icrc1_transfer Arguments: ( record { - to = "9bc4e24ff90c6898938d5fb339e779cea4edad4de592e591b22429289851b563"; - fee = record { e8s = 10_000 : nat64 }; - memo = 7_888_422_419_985_231_726 : nat64; - from_subaccount = null; - created_at_time = opt record { - timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + to = record { + owner = principal "rrkah-fqaaa-aaaaa-aaaaq-cai"; + subaccount = opt blob "\e1\ba\ec\1a\91u\f3\ec\fb\c1\87\e5J\eb|\96\01)0S\b9\95\98\0e\ad\b2\ee\19\c3\96\88\bf"; }; - amount = record { e8s = 1_200_000_000 : nat64 }; + fee = opt (10_000 : nat); + memo = opt blob "myNeuron"; + from_subaccount = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 1_200_000_000 : nat; }, ) Sending message with diff --git a/tests/output/default/neuron_stake/with_nonce.txt b/tests/output/default/neuron_stake/with_nonce.txt index 4abc5649..cfe31827 100644 --- a/tests/output/default/neuron_stake/with_nonce.txt +++ b/tests/output/default/neuron_stake/with_nonce.txt @@ -3,17 +3,18 @@ Sending message with Call type: update Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai - Method name: send_dfx + Method name: icrc1_transfer Arguments: ( record { - to = "a0ea9002c2bc3d442050f4431f3732c91dbec13eff79f414b15255d60c4a324c"; - fee = record { e8s = 10_000 : nat64 }; - memo = 777 : nat64; - from_subaccount = opt blob "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01"; - created_at_time = opt record { - timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + to = record { + owner = principal "rrkah-fqaaa-aaaaa-aaaaq-cai"; + subaccount = opt blob "r\8a\c1;\10I\a3\ac\af\fe\13\f1;\c7\1e\fd\9b4\97e\80\d4\0d\f2y\b2s n\f14$"; }; - amount = record { e8s = 1_200_000_000 : nat64 }; + fee = opt (10_000 : nat); + memo = opt blob "\00\00\00\00\00\00\03\09"; + from_subaccount = opt blob "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01"; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 1_200_000_000 : nat; }, ) Sending message with diff --git a/tests/output/default/sns/dissolve_delay/set_seconds.txt b/tests/output/default/sns/dissolve_delay/set_seconds.txt new file mode 100644 index 00000000..7165d9f7 --- /dev/null +++ b/tests/output/default/sns/dissolve_delay/set_seconds.txt @@ -0,0 +1,20 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + subaccount = blob "\83\a7\d2\b1/eO\f5\835\e5\a2Q,\ca\e0\d7\83\9ctK\18\07\a4|\96\f5\b9\f3\96\90i"; + command = opt variant { + Configure = record { + operation = opt variant { + SetDissolveTimestamp = record { + dissolve_timestamp_seconds = 1_921_534_704 : nat64; + } + }; + } + }; + }, +) diff --git a/tests/output/default/sns/manage_neuron/auto_stake_maturity.txt b/tests/output/default/sns/manage_neuron/auto_stake_maturity.txt new file mode 100644 index 00000000..9a13c0ef --- /dev/null +++ b/tests/output/default/sns/manage_neuron/auto_stake_maturity.txt @@ -0,0 +1,20 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + subaccount = blob "\83\a7\d2\b1/eO\f5\835\e5\a2Q,\ca\e0\d7\83\9ctK\18\07\a4|\96\f5\b9\f3\96\90i"; + command = opt variant { + Configure = record { + operation = opt variant { + ChangeAutoStakeMaturity = record { + requested_setting_for_auto_stake_maturity = true; + } + }; + } + }; + }, +) diff --git a/tests/output/default/sns/manage_neuron/disable_auto_stake_maturity.txt b/tests/output/default/sns/manage_neuron/disable_auto_stake_maturity.txt new file mode 100644 index 00000000..648d513a --- /dev/null +++ b/tests/output/default/sns/manage_neuron/disable_auto_stake_maturity.txt @@ -0,0 +1,20 @@ +Sending message with + + Call type: update + Sender: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + subaccount = blob "\83\a7\d2\b1/eO\f5\835\e5\a2Q,\ca\e0\d7\83\9ctK\18\07\a4|\96\f5\b9\f3\96\90i"; + command = opt variant { + Configure = record { + operation = opt variant { + ChangeAutoStakeMaturity = record { + requested_setting_for_auto_stake_maturity = false; + } + }; + } + }; + }, +) diff --git a/tests/output/ledger/neuron_manage/dissolve_fixed_timestamp.txt b/tests/output/ledger/neuron_manage/dissolve_fixed_timestamp.txt new file mode 100644 index 00000000..f03220d4 --- /dev/null +++ b/tests/output/ledger/neuron_manage/dissolve_fixed_timestamp.txt @@ -0,0 +1,21 @@ +Sending message with + + Call type: update + Sender: 5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Configure = record { + operation = opt variant { + SetDissolveTimestamp = record { + dissolve_timestamp_seconds = 1_921_534_704 : nat64; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/spawn_to.txt b/tests/output/ledger/neuron_manage/spawn_to.txt new file mode 100644 index 00000000..d7390eb0 --- /dev/null +++ b/tests/output/ledger/neuron_manage/spawn_to.txt @@ -0,0 +1,19 @@ +Sending message with + + Call type: update + Sender: 5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe + Canister id: rrkah-fqaaa-aaaaa-aaaaq-cai + Method name: manage_neuron + Arguments: ( + record { + id = opt record { id = 2_313_380_519_530_470_538 : nat64 }; + command = opt variant { + Spawn = record { + percentage_to_spawn = opt (20 : nat32); + new_controller = opt principal "pnf55-r7gzn-s3oqn-ah2v7-r6b63-a2ma2-wyzhb-dzbwb-sghid-lzcxh-4ae"; + nonce = null; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/neuron_manage.rs b/tests/output/neuron_manage.rs index 4eca2620..6a5cf814 100644 --- a/tests/output/neuron_manage.rs +++ b/tests/output/neuron_manage.rs @@ -5,7 +5,7 @@ const NEURON_ID: &str = "2313380519530470538"; // uncomment tests on next ledger app update ledger_compatible![ // hot_key, - additional_dissolve_delay_seconds, + dissolve_delay, // disburse, dissolve, // follow, @@ -18,97 +18,85 @@ ledger_compatible![ #[test] fn hot_key() { - quill_send(&format!( - "neuron-manage {NEURON_ID} --add-hot-key {PRINCIPAL}" - )) - .diff("neuron_manage/add_hot_key.txt"); - quill_send(&format!( - "neuron-manage {NEURON_ID} -a 7200 --remove-hot-key {PRINCIPAL} --start-dissolving" - )) - .diff("neuron_manage/remove_hot_key_and_dissolve.txt"); + quill_send(&format!("hotkey {NEURON_ID} --add {PRINCIPAL}")) + .diff("neuron_manage/add_hot_key.txt"); + quill_send(&format!("hotkey {NEURON_ID} --remove {PRINCIPAL}")) + .diff("neuron_manage/remove_hot_key.txt"); } #[test] -fn additional_dissolve_delay_seconds() { - quill_send(&format!("neuron-manage {NEURON_ID} -a 3600")) +fn dissolve_delay() { + quill_send(&format!("dissolve-delay {NEURON_ID} --increase-by 1h")) .diff("neuron_manage/additional_dissolve_delay_seconds.txt"); + quill_send(&format!( + "dissolve-delay {NEURON_ID} --increase-to '10 days'" + )) + .diff("neuron_manage/dissolve_fixed_timestamp.txt"); } #[test] fn disburse() { - quill_send(&format!( - "neuron-manage {NEURON_ID} --disburse-to {ALICE} --disburse-amount 57.31" - )) - .diff("neuron_manage/disburse_somewhat_to_someone.txt"); - quill_send(&format!( - "neuron-manage {NEURON_ID} --disburse --stop-dissolving" - )) - .diff("neuron_manage/disburse_stop_dissolving.txt"); + quill_send(&format!("disburse {NEURON_ID} --to {ALICE} --amount 57.31")) + .diff("neuron_manage/disburse_somewhat_to_someone.txt"); + quill_send(&format!("disburse {NEURON_ID}")).diff("neuron_manage/disburse.txt"); } #[test] fn dissolve() { - quill_send(&format!("neuron-manage {NEURON_ID} --start-dissolving")) - .diff("neuron_manage/start_dissolving.txt"); - quill_send(&format!("neuron-manage {NEURON_ID} --stop-dissolving")) - .diff("neuron_manage/stop_dissolving.txt"); + quill_send(&format!("dissolve {NEURON_ID} --start")).diff("neuron_manage/start_dissolving.txt"); + quill_send(&format!("dissolve {NEURON_ID} --stop")).diff("neuron_manage/stop_dissolving.txt"); } #[test] fn follow() { - quill_send(&format!("neuron-manage {NEURON_ID} --follow-topic 0 --follow-neurons 380519530470538 380519530470539")) - .diff("neuron_manage/follow.txt"); quill_send(&format!( - "neuron-manage {NEURON_ID} --clear-manage-neuron-followees" + "follow {NEURON_ID} --topic-id 0 --followees 380519530470538,380519530470539" + )) + .diff("neuron_manage/follow.txt"); + quill_send(&format!( + "follow {NEURON_ID} --unfollow --type neuron-management" )) .diff("neuron_manage/clear.txt"); } #[test] fn community_fund() { - quill_send(&format!("neuron-manage {NEURON_ID} --join-community-fund")) + quill_send(&format!("community-fund {NEURON_ID} --join")) .diff("neuron_manage/join_community_fund.txt"); - quill_send(&format!("neuron-manage {NEURON_ID} --leave-community-fund")) + quill_send(&format!("community-fund {NEURON_ID} --leave")) .diff("neuron_manage/leave_community_fund.txt"); } #[test] fn maturity() { - quill_send(&format!("neuron-manage {NEURON_ID} --stake-maturity 100")) + quill_send(&format!("stake-maturity {NEURON_ID} --percentage 100")) .diff("neuron_manage/stake_maturity.txt"); - quill_send(&format!( - "neuron-manage {NEURON_ID} --auto-stake-maturity disabled" - )) - .diff("neuron_manage/auto_stake_disable.txt"); - quill_send(&format!( - "neuron-manage {NEURON_ID} --auto-stake-maturity enabled" - )) - .diff("neuron_manage/auto_stake_enable.txt"); + quill_send(&format!("stake-maturity {NEURON_ID} --disable-automatic")) + .diff("neuron_manage/auto_stake_disable.txt"); + quill_send(&format!("stake-maturity {NEURON_ID} --automatic")) + .diff("neuron_manage/auto_stake_enable.txt"); quill_send("neuron-manage 65 --merge-maturity 100") .diff_err("neuron_manage/merge_maturity.txt"); - quill_send(&format!("neuron-manage {NEURON_ID} --spawn")).diff("neuron_manage/spawn.txt") + quill_send(&format!("spawn {NEURON_ID}")).diff("neuron_manage/spawn.txt"); + quill_send(&format!("spawn {NEURON_ID} --to {ALICE} --percentage 20")) + .diff("neuron_manage/spawn_to.txt"); } #[test] fn merge() { - quill_send(&format!( - "neuron-manage {NEURON_ID} --merge-from-neuron 380519530470538" - )) - .diff("neuron_manage/merge.txt") + quill_send(&format!("merge {NEURON_ID} --from 380519530470538")).diff("neuron_manage/merge.txt") } #[test] fn split() { - quill_send(&format!("neuron-manage {NEURON_ID} --split 100")).diff("neuron_manage/split.txt") + quill_send(&format!("split {NEURON_ID} --amount 100")).diff("neuron_manage/split.txt") } #[test] fn vote() { - quill_send(&format!( - "neuron-manage {NEURON_ID} --register-vote 123 456" - )) - .diff("neuron_manage/vote.txt") + quill_send(&format!("vote {NEURON_ID} --approve --proposal-id 123")) + .diff("neuron_manage/vote.txt") } diff --git a/tests/output/root.rs b/tests/output/root.rs index b80af1cb..46128c84 100644 --- a/tests/output/root.rs +++ b/tests/output/root.rs @@ -19,15 +19,18 @@ ledger_compatible![ #[test] fn account_balance() { - quill_query("account-balance ec0e2456fb9ff6c80f1d475b301d9b2ab873612f96e7fd74e7c0c0b2d58e6693") + quill_query("balance --of ec0e2456fb9ff6c80f1d475b301d9b2ab873612f96e7fd74e7c0c0b2d58e6693") .diff("account_balance/simple.txt"); - quill_query_authed("account-balance").diff("account_balance/authed.txt"); + quill_query_authed("balance").diff("account_balance/authed.txt"); quill_query( - "account-balance bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe-ce6fvoi.1", + "balance --of bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe-ce6fvoi.1", ) - .diff("account_balance/icrc1.txt") + .diff("account_balance/icrc1.txt"); + + quill_query("balance --of bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe --subaccount 1") + .diff("account_balance/icrc1.txt"); } #[test] @@ -58,10 +61,10 @@ fn list_proposals() { #[test] fn neuron_stake() { - quill_send("neuron-stake --amount 12 --from-subaccount 01 --nonce 777") + quill_send("stake-neuron --amount 12 --from-subaccount 01 --nonce 777") .diff("neuron_stake/with_nonce.txt"); - quill_send("neuron-stake --amount 12 --name myNeuron").diff("neuron_stake/with_name.txt"); - quill_send("neuron-stake --name myNeuron --already-transferred") + quill_send("stake-neuron --amount 12 --name myNeuron").diff("neuron_stake/with_name.txt"); + quill_send("stake-neuron --name myNeuron --already-transferred") .diff("neuron_stake/stake_only.txt"); } @@ -150,7 +153,7 @@ fn ledger_fail_early() { quill("replace-node-provider-id --ledger --node-operator-id fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae \ --node-provider-id pnf55-r7gzn-s3oqn-ah2v7-r6b63-a2ma2-wyzhb-dzbwb-sghid-lzcxh-4ae") .diff_err("ledger_incompatible/by_function.txt"); - quill("neuron-stake --ledger --amount 12 --name myNeuron") + quill("stake-neuron --ledger --amount 12 --name myNeuron") .diff_err("ledger_incompatible/by_command.txt"); quill("neuron-manage 1 --ledger --join-community-fund") .diff_err("ledger_incompatible/by_flag.txt"); diff --git a/tests/output/sns.rs b/tests/output/sns.rs index 233de83d..ca6a1f06 100644 --- a/tests/output/sns.rs +++ b/tests/output/sns.rs @@ -17,6 +17,7 @@ ledger_compatible![ // make_proposal, // stake_neuron, stake_maturity, + // auto_stake_maturity, // vote, ]; @@ -28,21 +29,21 @@ fn balance() { #[test] fn dissolve_delay() { quill_sns_send(&format!( - "sns configure-dissolve-delay {NEURON_ID} --additional-dissolve-delay-seconds 1000" + "sns dissolve-delay {NEURON_ID} --increase-by 1000s" )) .diff("sns/dissolve_delay/add_seconds.txt"); + quill_sns_send(&format!( + "sns dissolve-delay {NEURON_ID} --increase-to '6 days'" + )) + .diff("sns/dissolve_delay/set_seconds.txt"); } #[test] fn dissolve() { - quill_sns_send(&format!( - "sns configure-dissolve-delay {NEURON_ID} --start-dissolving" - )) - .diff("sns/dissolve_delay/start_dissolving.txt"); - quill_sns_send(&format!( - "sns configure-dissolve-delay {NEURON_ID} --stop-dissolving" - )) - .diff("sns/dissolve_delay/stop_dissolving.txt"); + quill_sns_send(&format!("sns dissolve {NEURON_ID} --start")) + .diff("sns/dissolve_delay/start_dissolving.txt"); + quill_sns_send(&format!("sns dissolve {NEURON_ID} --stop")) + .diff("sns/dissolve_delay/stop_dissolving.txt"); } #[test] @@ -61,6 +62,16 @@ fn stake_maturity() { .diff("sns/manage_neuron/stake_maturity.txt"); } +#[test] +fn auto_stake_maturity() { + quill_sns_send(&format!("sns stake-maturity {NEURON_ID} --automatic")) + .diff("sns/manage_neuron/auto_stake_maturity.txt"); + quill_sns_send(&format!( + "sns stake-maturity {NEURON_ID} --disable-automatic" + )) + .diff("sns/manage_neuron/disable_auto_stake_maturity.txt"); +} + #[test] fn manage_neuron() { quill_sns_send(&format!( @@ -71,20 +82,18 @@ fn manage_neuron() { "sns disburse-maturity {NEURON_ID} --subaccount 03" )) .diff("sns/manage_neuron/disburse_subaccount.txt"); - quill_sns_send(&format!( - "sns split-neuron {NEURON_ID} --memo 47 --amount 230.5" - )) - .diff("sns/manage_neuron/split.txt") + quill_sns_send(&format!("sns split {NEURON_ID} --memo 47 --amount 230.5")) + .diff("sns/manage_neuron/split.txt") } #[test] fn follow() { quill_sns_send(&format!( - "sns follow-neuron {NEURON_ID} --type transfer-sns-treasury-funds --followees {FOLLOWEE}" + "sns follow {NEURON_ID} --type transfer-sns-treasury-funds --followees {FOLLOWEE}" )) .diff("sns/follow/follow.txt"); quill_sns_send(&format!( - "sns follow-neuron {NEURON_ID} --type upgrade-sns-to-next-version --unfollow" + "sns follow {NEURON_ID} --type upgrade-sns-to-next-version --unfollow" )) .diff("sns/follow/unfollow.txt"); } @@ -149,7 +158,7 @@ fn neuron_permission() { #[test] fn stake_neuron() { quill_sns_send("sns stake-neuron --amount 12 --memo 777").diff("sns/stake_neuron/memo.txt"); - quill_sns_send("sns stake-neuron --memo 777 --claim-only") + quill_sns_send("sns stake-neuron --memo 777 --already-transferred") .diff("sns/stake_neuron/no_amount.txt"); } @@ -167,14 +176,10 @@ fn swap() { #[test] fn vote() { - quill_sns_send(&format!( - "sns register-vote {NEURON_ID} --proposal-id 1 --vote n" - )) - .diff("sns/vote/no.txt"); - quill_sns_send(&format!( - "sns register-vote {NEURON_ID} --proposal-id 1 --vote y" - )) - .diff("sns/vote/yes.txt"); + quill_sns_send(&format!("sns vote {NEURON_ID} --proposal-id 1 --reject")) + .diff("sns/vote/no.txt"); + quill_sns_send(&format!("sns vote {NEURON_ID} --proposal-id 1 --approve")) + .diff("sns/vote/yes.txt"); } #[test] From f62b14296df7d9267c32a94c0c3e298820e01445 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 21 Jun 2023 08:38:06 -0700 Subject: [PATCH 2/2] humantime says 30.44 days in a month, not 30.4375 --- e2e/tests-quill/create_neuron.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-quill/create_neuron.bash b/e2e/tests-quill/create_neuron.bash index 88a7903c..1e718340 100644 --- a/e2e/tests-quill/create_neuron.bash +++ b/e2e/tests-quill/create_neuron.bash @@ -39,5 +39,5 @@ teardown() { # check that increasing dissolve delay worked, this time using list-neurons 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" + assert_string_match "dissolve_delay_seconds = 15_780_096" }