From 68ab41281cdb919d1e83f3c8f4ee8fe454f41619 Mon Sep 17 00:00:00 2001 From: Adam Spofford <93943719+adamspofford-dfinity@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:40:00 -0700 Subject: [PATCH] feat: Ledger device support (#199) --- .github/PULL_REQUEST_TEMPLATE.md | 5 +- .github/workflows/ci.yml | 17 +- .github/workflows/e2e.yml | 15 +- .github/workflows/release.yml | 15 +- CHANGELOG.md | 1 + CONTRIBUTING.md | 5 +- Cargo.lock | 672 ++++++++++++------ Cargo.toml | 47 +- Cross.toml | 4 +- README.md | 9 +- docs/cli-reference/quill-parent.md | 15 + docs/cli-reference/quill-public-ids.md | 9 +- scripts/workflows/provision-darwin-build.sh | 4 + ...sion-darwin.sh => provision-darwin-e2e.sh} | 0 scripts/workflows/provision-linux-build.sh | 15 + ...vision-linux.sh => provision-linux-e2e.sh} | 0 scripts/workflows/provision-windows-build.ps1 | 7 + src/commands/ckbtc/retrieve_btc.rs | 4 +- src/commands/ckbtc/transfer.rs | 4 +- src/commands/claim_neurons.rs | 21 +- src/commands/neuron_manage.rs | 15 +- src/commands/neuron_stake.rs | 9 +- src/commands/public.rs | 16 + src/commands/request_status.rs | 27 +- src/commands/send.rs | 5 +- src/commands/sns.rs | 11 +- src/commands/sns/stake_neuron.rs | 12 +- src/commands/sns/transfer.rs | 4 +- src/commands/transfer.rs | 12 +- src/lib/ledger.rs | 357 ++++++++++ src/lib/mod.rs | 30 +- src/lib/signing.rs | 73 +- src/main.rs | 71 +- tests/{output-tests => output}/ckbtc.rs | 10 +- .../default}/account_balance/authed.txt | 0 .../default}/account_balance/icrc1.txt | 0 .../default}/account_balance/simple.txt | 0 .../default}/ckbtc/balance/authed.txt | 0 .../default}/ckbtc/balance/testnet.txt | 0 .../retrieve_btc/already_transferred.txt | 0 .../default}/ckbtc/retrieve_btc/simple.txt | 2 +- .../default}/ckbtc/retrieve_btc/status.txt | 0 .../default}/ckbtc/transfer/simple.txt | 2 +- .../default}/ckbtc/update_balance/simple.txt | 0 .../default}/ckbtc/update_balance/testnet.txt | 0 .../default/ckbtc/withdrawal_address.txt | 1 + .../default}/claim_neurons/simple.txt | 0 .../default}/get_neuron_info/simple.txt | 0 .../default}/get_proposal_info/simple.txt | 0 .../ledger_incompatible/by_command.txt | 1 + .../default/ledger_incompatible/by_flag.txt | 2 + .../ledger_incompatible/by_function.txt | 1 + .../default}/list_neurons/many.txt | 0 .../default}/list_neurons/simple.txt | 0 .../default}/list_proposals/simple.txt | 0 .../default}/neuron_manage/add_hot_key.txt | 0 .../additional_dissolve_delay_seconds.txt | 0 .../neuron_manage/auto_stake_disable.txt | 0 .../neuron_manage/auto_stake_enable.txt | 0 .../default}/neuron_manage/clear.txt | 0 .../disburse_somewhat_to_someone.txt | 0 .../disburse_stop_dissolving.txt | 0 .../default}/neuron_manage/follow.txt | 0 .../neuron_manage/join_community_fund.txt | 0 .../neuron_manage/leave_community_fund.txt | 0 .../default}/neuron_manage/merge.txt | 0 .../default}/neuron_manage/merge_maturity.txt | 0 .../remove_hot_key_and_dissolve.txt | 0 .../default}/neuron_manage/spawn.txt | 0 .../default}/neuron_manage/split.txt | 0 .../default}/neuron_manage/stake_maturity.txt | 0 .../neuron_manage/start_dissolving.txt | 17 + .../default/neuron_manage/stop_dissolving.txt | 17 + .../default}/neuron_manage/vote.txt | 0 .../default}/neuron_stake/stake_only.txt | 0 .../default}/neuron_stake/with_name.txt | 4 +- .../default}/neuron_stake/with_nonce.txt | 4 +- .../default}/node_provider/replace.txt | 0 .../default}/node_provider/update.txt | 0 .../default}/sns/balance/simple.txt | 0 .../default}/sns/deployed.txt | 0 .../default}/sns/disburse/simple.txt | 0 .../default}/sns/disburse/subaccount.txt | 0 .../sns/dissolve_delay/add_seconds.txt | 0 .../sns/dissolve_delay/start_dissolving.txt | 0 .../sns/dissolve_delay/stop_dissolving.txt | 0 .../default}/sns/follow/follow.txt | 0 .../default}/sns/follow/unfollow.txt | 0 .../default}/sns/make_proposal/from_file.txt | 0 .../default}/sns/make_proposal/simple.txt | 0 .../default}/sns/make_proposal/upgrade.txt | 0 .../sns/make_proposal/upgrade_arg.txt | 0 .../make_proposal/upgrade_summary_path.txt | 0 .../default}/sns/manage_neuron/disburse.txt | 0 .../sns/manage_neuron/disburse_subaccount.txt | 0 .../default}/sns/manage_neuron/split.txt | 0 .../sns/manage_neuron/stake_maturity.txt | 0 tests/output/default/sns/neuron_id/memo0.txt | 1 + .../default}/sns/neuron_permission/add.txt | 0 .../default}/sns/neuron_permission/remove.txt | 0 .../default}/sns/stake_neuron/memo.txt | 2 +- .../default}/sns/stake_neuron/no_amount.txt | 0 .../outputs => output/default}/sns/status.txt | 0 .../default}/sns/swap/new_ticket.txt | 0 .../default}/sns/swap/participation.txt | 0 .../default}/sns/swap/pay.txt | 0 .../sns/swap/pay_with_confirmation_text.txt | 0 .../default}/sns/swap/refund.txt | 0 .../default}/sns/transfer/fees_and_memo.txt | 2 +- .../default}/sns/transfer/simple.txt | 2 +- .../default}/sns/vote/no.txt | 0 .../default}/sns/vote/yes.txt | 0 .../default}/transfer/e8s-2.txt | 4 +- .../default}/transfer/e8s.txt | 4 +- .../default}/transfer/icp-and-e8s.txt | 4 +- .../default}/transfer/icrc1.txt | 2 +- .../default}/transfer/simple.txt | 4 +- .../default}/transfer/with-fees-and-memo.txt | 4 +- .../default}/transfer/with-fees.txt | 4 +- .../output/ledger/account_balance/authed.txt | 11 + tests/output/ledger/account_balance/icrc1.txt | 12 + .../output/ledger/account_balance/simple.txt | 11 + tests/output/ledger/ckbtc/balance/authed.txt | 12 + tests/output/ledger/ckbtc/balance/testnet.txt | 12 + tests/output/ledger/ckbtc/transfer/simple.txt | 19 + .../ledger/ckbtc/withdrawal_address.txt | 1 + tests/output/ledger/claim_neurons/simple.txt | 9 + tests/output/ledger/list_neurons/many.txt | 12 + tests/output/ledger/list_neurons/simple.txt | 7 + .../additional_dissolve_delay_seconds.txt | 21 + .../neuron_manage/auto_stake_disable.txt | 21 + .../neuron_manage/auto_stake_enable.txt | 21 + tests/output/ledger/neuron_manage/merge.txt | 17 + .../ledger/neuron_manage/merge_maturity.txt | 1 + tests/output/ledger/neuron_manage/spawn.txt | 19 + tests/output/ledger/neuron_manage/split.txt | 15 + .../ledger/neuron_manage/stake_maturity.txt | 15 + .../ledger/neuron_manage/start_dissolving.txt | 17 + .../ledger/neuron_manage/stop_dissolving.txt | 17 + tests/output/ledger/sns/disburse/simple.txt | 20 + .../output/ledger/sns/disburse/subaccount.txt | 22 + .../sns/dissolve_delay/start_dissolving.txt | 16 + .../sns/dissolve_delay/stop_dissolving.txt | 16 + .../sns/manage_neuron/stake_maturity.txt | 14 + tests/output/ledger/sns/neuron_id/memo0.txt | 1 + .../ledger/sns/neuron_permission/add.txt | 19 + .../ledger/sns/neuron_permission/remove.txt | 19 + .../ledger/sns/transfer/fees_and_memo.txt | 19 + tests/output/ledger/sns/transfer/simple.txt | 19 + tests/output/ledger/transfer/icrc1.txt | 19 + tests/{output-tests => output}/main.rs | 110 ++- .../{output-tests => output}/neuron_manage.rs | 24 +- tests/{output-tests => output}/root.rs | 41 +- tests/{output-tests => output}/sns.rs | 33 +- 154 files changed, 1883 insertions(+), 384 deletions(-) create mode 100755 scripts/workflows/provision-darwin-build.sh rename scripts/workflows/{provision-darwin.sh => provision-darwin-e2e.sh} (100%) mode change 100644 => 100755 create mode 100755 scripts/workflows/provision-linux-build.sh rename scripts/workflows/{provision-linux.sh => provision-linux-e2e.sh} (100%) mode change 100644 => 100755 create mode 100755 scripts/workflows/provision-windows-build.ps1 create mode 100644 src/lib/ledger.rs rename tests/{output-tests => output}/ckbtc.rs (81%) rename tests/{output-tests/outputs => output/default}/account_balance/authed.txt (100%) rename tests/{output-tests/outputs => output/default}/account_balance/icrc1.txt (100%) rename tests/{output-tests/outputs => output/default}/account_balance/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/balance/authed.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/balance/testnet.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/retrieve_btc/already_transferred.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/retrieve_btc/simple.txt (93%) rename tests/{output-tests/outputs => output/default}/ckbtc/retrieve_btc/status.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/transfer/simple.txt (88%) rename tests/{output-tests/outputs => output/default}/ckbtc/update_balance/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/ckbtc/update_balance/testnet.txt (100%) create mode 100644 tests/output/default/ckbtc/withdrawal_address.txt rename tests/{output-tests/outputs => output/default}/claim_neurons/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/get_neuron_info/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/get_proposal_info/simple.txt (100%) create mode 100644 tests/output/default/ledger_incompatible/by_command.txt create mode 100644 tests/output/default/ledger_incompatible/by_flag.txt create mode 100644 tests/output/default/ledger_incompatible/by_function.txt rename tests/{output-tests/outputs => output/default}/list_neurons/many.txt (100%) rename tests/{output-tests/outputs => output/default}/list_neurons/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/list_proposals/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/add_hot_key.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/additional_dissolve_delay_seconds.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/auto_stake_disable.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/auto_stake_enable.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/clear.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/disburse_somewhat_to_someone.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/disburse_stop_dissolving.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/follow.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/join_community_fund.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/leave_community_fund.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/merge.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/merge_maturity.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/remove_hot_key_and_dissolve.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/spawn.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/split.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_manage/stake_maturity.txt (100%) create mode 100644 tests/output/default/neuron_manage/start_dissolving.txt create mode 100644 tests/output/default/neuron_manage/stop_dissolving.txt rename tests/{output-tests/outputs => output/default}/neuron_manage/vote.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_stake/stake_only.txt (100%) rename tests/{output-tests/outputs => output/default}/neuron_stake/with_name.txt (89%) rename tests/{output-tests/outputs => output/default}/neuron_stake/with_nonce.txt (90%) rename tests/{output-tests/outputs => output/default}/node_provider/replace.txt (100%) rename tests/{output-tests/outputs => output/default}/node_provider/update.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/balance/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/deployed.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/disburse/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/disburse/subaccount.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/dissolve_delay/add_seconds.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/dissolve_delay/start_dissolving.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/dissolve_delay/stop_dissolving.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/follow/follow.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/follow/unfollow.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/make_proposal/from_file.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/make_proposal/simple.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/make_proposal/upgrade.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/make_proposal/upgrade_arg.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/make_proposal/upgrade_summary_path.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/manage_neuron/disburse.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/manage_neuron/disburse_subaccount.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/manage_neuron/split.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/manage_neuron/stake_maturity.txt (100%) create mode 100644 tests/output/default/sns/neuron_id/memo0.txt rename tests/{output-tests/outputs => output/default}/sns/neuron_permission/add.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/neuron_permission/remove.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/stake_neuron/memo.txt (94%) rename tests/{output-tests/outputs => output/default}/sns/stake_neuron/no_amount.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/status.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/swap/new_ticket.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/swap/participation.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/swap/pay.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/swap/pay_with_confirmation_text.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/swap/refund.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/transfer/fees_and_memo.txt (89%) rename tests/{output-tests/outputs => output/default}/sns/transfer/simple.txt (87%) rename tests/{output-tests/outputs => output/default}/sns/vote/no.txt (100%) rename tests/{output-tests/outputs => output/default}/sns/vote/yes.txt (100%) rename tests/{output-tests/outputs => output/default}/transfer/e8s-2.txt (84%) rename tests/{output-tests/outputs => output/default}/transfer/e8s.txt (81%) rename tests/{output-tests/outputs => output/default}/transfer/icp-and-e8s.txt (81%) rename tests/{output-tests/outputs => output/default}/transfer/icrc1.txt (90%) rename tests/{output-tests/outputs => output/default}/transfer/simple.txt (81%) rename tests/{output-tests/outputs => output/default}/transfer/with-fees-and-memo.txt (81%) rename tests/{output-tests/outputs => output/default}/transfer/with-fees.txt (81%) create mode 100644 tests/output/ledger/account_balance/authed.txt create mode 100644 tests/output/ledger/account_balance/icrc1.txt create mode 100644 tests/output/ledger/account_balance/simple.txt create mode 100644 tests/output/ledger/ckbtc/balance/authed.txt create mode 100644 tests/output/ledger/ckbtc/balance/testnet.txt create mode 100644 tests/output/ledger/ckbtc/transfer/simple.txt create mode 100644 tests/output/ledger/ckbtc/withdrawal_address.txt create mode 100644 tests/output/ledger/claim_neurons/simple.txt create mode 100644 tests/output/ledger/list_neurons/many.txt create mode 100644 tests/output/ledger/list_neurons/simple.txt create mode 100644 tests/output/ledger/neuron_manage/additional_dissolve_delay_seconds.txt create mode 100644 tests/output/ledger/neuron_manage/auto_stake_disable.txt create mode 100644 tests/output/ledger/neuron_manage/auto_stake_enable.txt create mode 100644 tests/output/ledger/neuron_manage/merge.txt create mode 100644 tests/output/ledger/neuron_manage/merge_maturity.txt create mode 100644 tests/output/ledger/neuron_manage/spawn.txt create mode 100644 tests/output/ledger/neuron_manage/split.txt create mode 100644 tests/output/ledger/neuron_manage/stake_maturity.txt create mode 100644 tests/output/ledger/neuron_manage/start_dissolving.txt create mode 100644 tests/output/ledger/neuron_manage/stop_dissolving.txt create mode 100644 tests/output/ledger/sns/disburse/simple.txt create mode 100644 tests/output/ledger/sns/disburse/subaccount.txt create mode 100644 tests/output/ledger/sns/dissolve_delay/start_dissolving.txt create mode 100644 tests/output/ledger/sns/dissolve_delay/stop_dissolving.txt create mode 100644 tests/output/ledger/sns/manage_neuron/stake_maturity.txt create mode 100644 tests/output/ledger/sns/neuron_id/memo0.txt create mode 100644 tests/output/ledger/sns/neuron_permission/add.txt create mode 100644 tests/output/ledger/sns/neuron_permission/remove.txt create mode 100644 tests/output/ledger/sns/transfer/fees_and_memo.txt create mode 100644 tests/output/ledger/sns/transfer/simple.txt create mode 100644 tests/output/ledger/transfer/icrc1.txt rename tests/{output-tests => output}/main.rs (63%) rename tests/{output-tests => output}/neuron_manage.rs (81%) rename tests/{output-tests => output}/root.rs (81%) rename tests/{output-tests => output}/sns.rs (91%) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 257842aa..26d9a35f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,6 +8,7 @@ Fixes # (issue) Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. -# Checklist: +# Checklist -- [ ] I have made corresponding changes to the documentation in src/docs. \ No newline at end of file +- [ ] I have made corresponding changes to the documentation in docs/cli-reference. +- [ ] I have added corresponding integration tests. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ccfd0029..13af1e5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,13 @@ jobs: command: cargo clippy --all --all-targets --all-features --tests -- -D warnings - name: Test command: cargo test + include: + - os: ubuntu-latest + build_deps: scripts/workflows/provision-linux-build.sh + - os: macos-latest + build_deps: scripts/workflows/provision-darwin-build.sh + - os: windows-latest + build_deps: scripts/workflows/provision-windows-build.ps1 env: VCPKG_ROOT: 'C:\vcpkg' steps: @@ -34,14 +41,8 @@ jobs: ${{ env.VCPKG_ROOT }} key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ env.rust }}-ci-1 - - name: Install dependencies (windows only) - if: ${{ contains(matrix.os, 'windows') }} - shell: bash - run: | - vcpkg integrate install - vcpkg install openssl:x64-windows-static-md - echo "OPENSSL_DIR=C:\vcpkg\installed\x64-windows-static-md" >> $GITHUB_ENV - echo "OPENSSL_STATIC=Yes" >> $GITHUB_ENV + - name: Install dependencies + run: ${{ matrix.build_deps }} - uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b55ae8f1..32d9e768 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,8 +17,13 @@ jobs: fail-fast: false matrix: # windows not supported by dfx - os: [ubuntu-latest, macos-latest] - binary_path: ["target/release"] + include: + - os: ubuntu-latest + binary_path: target/release + build_deps: scripts/workflows/provision-linux-build.sh + - os: macos-latest + binary_path: target/release + build_deps: scripts/workflows/provision-darwin-build.sh steps: - uses: actions/checkout@master @@ -36,6 +41,8 @@ jobs: toolchain: ${{ env.rust }} override: true # components: rustfmt, clippy + - name: Install dependencies + run: ${{ matrix.build_deps }} - name: Build release run: cargo build --release --locked @@ -78,10 +85,10 @@ jobs: run: chmod +x /usr/local/bin/quill - name: Provision Darwin if: ${{ contains(matrix.os, 'macos') }} - run: bash scripts/workflows/provision-darwin.sh + run: bash scripts/workflows/provision-darwin-e2e.sh - name: Provision Linux if: ${{ contains(matrix.os, 'ubuntu') }} - run: bash scripts/workflows/provision-linux.sh + run: bash scripts/workflows/provision-linux-e2e.sh - name: Prepare environment run: | echo "archive=$(pwd)/e2e/archive" >> "$GITHUB_ENV" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 460e7844..2f83e9c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,20 +28,24 @@ jobs: name: windows target_file: target/release/quill.exe asset_name: quill-windows-x86_64.exe + build_deps: scripts/workflows/provision-windows-build.ps1 - os: macos-latest name: macos target_file: target/release/quill asset_name: quill-macos-x86_64 + build_deps: scripts/workflows/provision-darwin-build.sh - os: ubuntu-latest name: linux-arm32 target: arm-unknown-linux-gnueabihf cross: true target_file: target/arm-unknown-linux-gnueabihf/release-arm/quill asset_name: quill-linux-arm32 + features: [static-ssl, hsm] - os: ubuntu-latest name: linux target_file: target/release/quill asset_name: quill-linux-x86_64 + build_deps: scripts/workflows/provision-linux-build.sh env: VCPKG_ROOT: 'C:\vcpkg' steps: @@ -55,14 +59,9 @@ jobs: ${{ env.VCPKG_ROOT }} key: ${{ matrix.os }}-cargo-${{ env.rust }}-release-1 - - name: Install dependencies (windows only) - if: ${{ contains(matrix.os, 'windows') }} - shell: bash - run: | - vcpkg integrate install - vcpkg install openssl:x64-windows-static-md - echo "OPENSSL_DIR=C:\vcpkg\installed\x64-windows-static-md" >> $GITHUB_ENV - echo "OPENSSL_STATIC=Yes" >> $GITHUB_ENV + - name: Install dependencies + if: ${{ matrix.build_deps }} + run: ${{ matrix.build_deps }} - name: Install toolchain uses: actions-rs/toolchain@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 450e19cf..e23c43ad 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 +- Added Ledger support via `--ledger`. (#199) - Added `--confirmation-text` to `quill sns pay`. (#195) - Fixed `quill ckbtc update-balance` allowing the anonymous principal. (#191) - Added `disburse`, `disburse-maturity`, `split-neuron`, and `follow-neuron` to `quill sns`. (#191) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91ce6789..f8efc90d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,11 +20,10 @@ All code contributions are subject to our [Contributor License Agreement (CLA)]( Quill has three kinds of tests to be aware of: - Rust unit tests. Add these whenever internal helpers are added or changed, using standard Rust test conventions. -- Output tests. These are located in `tests/output-tests`. Add these whenever the command interface is updated. Each test is an integration test dry-running a `quill` command; its output is checked against a file in `outputs`. Set `FIX_OUTPUTS=1` when running `cargo test` to generate this file for a new test. Ensure that the output contains Candid field names rather than field hashes. +- Output tests. These are located in `tests/output`. Add these whenever the command interface is updated. Each test is an integration test dry-running a `quill` command; its output is checked against a file in `default`. Set `FIX_OUTPUTS=1` when running `cargo test` to generate this file for a new test. Ensure that the output contains Candid field names rather than field hashes. + - Some output tests require a Ledger device. To run these tests, a Ledger Nano should be plugged in and initialized with the seed "equip will roof matter pink blind book anxiety banner elbow sun young", and the ICP app must be installed. Then run `cargo test -- --ignored` and accept each request on the device. These tests are manually run so if you do not own a Ledger device you will need to wait for the reviewer to notify you of test failures. - End-to-end tests. These are located in `e2e/tests-quill`. These should be added whenever a test needs to be run against a real replica. They are run with [`bats`](https://github.com/bats-core/bats-core), and you will need to install [`dfx`](https://github.com/dfinity/sdk) and [`bats-support`](https://github.com/ztombol/bats-support), and point the `BATSLIB` environment variable to the `bats-support` installation path. More examples of writing tests for our E2E test setup can be found in the [SDK repository](https://github.com/dfinity/sdk). -The latter two require you to have run `cargo build` before executing them. - ## Documentation Every change to the command-line interface must contain documentation; we use `clap`, so Rustdoc comments turn into CLI documentation. Additionally, this in-code documentation must be mirrored by a corresponding change in `docs/cli-reference`. See existing doc pages for examples. Finally, any feature or notable bugfix should be mentioned in [CHANGELOG.md](CHANGELOG.md), under the `## Unreleased` header. diff --git a/Cargo.lock b/Cargo.lock index c24a9dfe..ff767c8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,12 @@ version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "arrayvec" version = "0.5.2" @@ -103,13 +109,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.63" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.16", ] [[package]] @@ -129,12 +135,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.6", + "instant", + "rand", +] + [[package]] name = "base16ct" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base32" version = "0.4.0" @@ -153,6 +176,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + [[package]] name = "base64ct" version = "1.5.0" @@ -216,7 +245,7 @@ dependencies = [ "pbkdf2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.5", + "sha2 0.10.6", "subtle", "zeroize", ] @@ -279,8 +308,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62250ece575fa9b22068b3a8d59586f01d426dd7785522efd97632959e71c986" dependencies = [ "digest 0.9.0", - "ff", - "group", + "ff 0.12.0", + "group 0.12.0", "pairing", "rand_core 0.6.4", "subtle", @@ -428,9 +457,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00748d2466ccd456787852ce1c3bd8d47f946639b46fd0fd2e728c3bb23d307f" +checksum = "244005a1917bb7614cd775ca8a5d59efeb5ac74397bb14ba29a19347ebd78591" dependencies = [ "anyhow", "binread", @@ -451,7 +480,7 @@ dependencies = [ "pretty", "serde", "serde_bytes", - "sha2 0.10.5", + "sha2 0.10.6", "thiserror", ] @@ -657,11 +686,24 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-oid" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "convert_case" @@ -669,22 +711,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - [[package]] name = "cpufeatures" version = "0.2.5" @@ -721,6 +747,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -871,6 +909,18 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown", + "lock_api", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.3.3" @@ -884,7 +934,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" dependencies = [ "const-oid", - "pem-rfc7468", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -1038,6 +1099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.2", + "const-oid", "crypto-common", "subtle", ] @@ -1063,15 +1125,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "ecdsa" version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", + "der 0.6.0", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", "signature 1.6.4", ] @@ -1081,12 +1149,26 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", + "der 0.6.0", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", "signature 2.0.0", ] +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der 0.7.6", + "digest 0.10.6", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", + "signature 2.0.0", + "spki 0.7.2", +] + [[package]] name = "ed25519-consensus" version = "2.0.1" @@ -1114,17 +1196,37 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", + "base16ct 0.1.1", + "crypto-bigint 0.4.8", + "der 0.6.0", + "digest 0.10.6", + "ff 0.12.0", + "generic-array", + "group 0.12.0", + "pem-rfc7468 0.6.0", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.2", "digest 0.10.6", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pem-rfc7468", - "pkcs8", + "group 0.13.0", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.2", "subtle", "zeroize", ] @@ -1138,6 +1240,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -1218,6 +1326,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -1374,23 +1492,15 @@ dependencies = [ "slab", ] -[[package]] -name = "garcon" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83fb8961dcd3c26123863998521ae4d07e5e5aa8fb50b503380448f2e0ea069" -dependencies = [ - "futures-util", -] - [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1428,7 +1538,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" dependencies = [ "byteorder", - "ff", + "ff 0.12.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -1503,6 +1624,18 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hidapi" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "winapi", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1584,61 +1717,41 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "log", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", - "webpki-roots", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", ] [[package]] name = "ic-agent" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699755970b44fbc3fd97ed75c584bd764fc42a37db8bacf9fed909857750d770" +version = "0.24.1" +source = "git+https://github.com/dfinity/agent-rs?rev=b9c57f8bab23cc9936dca4560293ffe485471c6d#b9c57f8bab23cc9936dca4560293ffe485471c6d" dependencies = [ - "async-trait", - "base32", - "base64 0.13.0", - "byteorder", + "backoff", "candid", "futures-util", - "garcon", "hex", "http", "http-body", - "hyper-rustls", + "ic-certification", "ic-verify-bls-signature", - "k256 0.11.6", + "k256 0.13.1", "leb128", - "mime", - "pem", - "pkcs8", + "pem 2.0.1", + "pkcs8 0.10.2", "rand", "reqwest", "ring", "rustls", - "sec1", + "sec1 0.7.2", "serde", "serde_bytes", "serde_cbor", - "sha2 0.10.5", + "serde_repr", + "sha2 0.10.6", "simple_asn1", "thiserror", + "tokio", "url", ] @@ -1779,6 +1892,17 @@ dependencies = [ "slotmap", ] +[[package]] +name = "ic-certification" +version = "0.24.1" +source = "git+https://github.com/dfinity/agent-rs?rev=b9c57f8bab23cc9936dca4560293ffe485471c6d#b9c57f8bab23cc9936dca4560293ffe485471c6d" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.6", +] + [[package]] name = "ic-certified-map" version = "0.3.2" @@ -1787,7 +1911,7 @@ checksum = "c301b1d4fc0b8ec0ee9bc558f702a2480c821e1fe647bf0d75fda46b3efa5602" dependencies = [ "serde", "serde_bytes", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -1859,7 +1983,7 @@ source = "git+https://github.com/dfinity/ic?rev=5e56d9e6f7da97bbad0ef0269a198e18 dependencies = [ "k256 0.12.0", "lazy_static", - "pem", + "pem 1.1.1", "rand", "simple_asn1", "zeroize", @@ -2268,15 +2392,13 @@ dependencies = [ [[package]] name = "ic-identity-hsm" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed32fe4c1338979f4add6257d6dd20875ef7adefb3435d7415a2a3f5806e5f21" +version = "0.24.1" +source = "git+https://github.com/dfinity/agent-rs?rev=b9c57f8bab23cc9936dca4560293ffe485471c6d#b9c57f8bab23cc9936dca4560293ffe485471c6d" dependencies = [ "hex", "ic-agent", - "num-bigint 0.4.3", "pkcs11", - "sha2 0.10.5", + "sha2 0.10.6", "simple_asn1", "thiserror", ] @@ -2868,8 +2990,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a180f02c79a71fcbc10b194406dbffd6a883c916f96be4f17ae3aeb96348c5" dependencies = [ "digest 0.9.0", - "ff", - "group", + "ff 0.12.0", + "group 0.12.0", "pairing", "rand_core 0.6.4", "subtle", @@ -2919,7 +3041,7 @@ dependencies = [ "num-traits", "serde", "serde_bytes", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -2948,6 +3070,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" +dependencies = [ + "console", + "number_prefix", + "portable-atomic 0.3.20", + "unicode-width", +] + [[package]] name = "instant" version = "0.1.12" @@ -3041,8 +3175,8 @@ checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa 0.14.8", - "elliptic-curve", - "sha2 0.10.5", + "elliptic-curve 0.12.3", + "sha2 0.10.6", "sha3", ] @@ -3054,9 +3188,23 @@ checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397" dependencies = [ "cfg-if 1.0.0", "ecdsa 0.15.1", - "elliptic-curve", + "elliptic-curve 0.12.3", "once_cell", - "sha2 0.10.5", + "sha2 0.10.6", + "signature 2.0.0", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa 0.16.7", + "elliptic-curve 0.13.5", + "once_cell", + "sha2 0.10.6", "signature 2.0.0", ] @@ -3113,6 +3261,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "ledger-apdu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" +dependencies = [ + "arrayref", + "no-std-compat", + "snafu", +] + [[package]] name = "ledger-canister" version = "0.8.0" @@ -3144,6 +3303,32 @@ dependencies = [ "serde_cbor", ] +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport-hid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee" +dependencies = [ + "byteorder", + "cfg-if 1.0.0", + "hex", + "hidapi", + "ledger-transport", + "libc", + "log", + "thiserror", +] + [[package]] name = "lexical-core" version = "0.7.6" @@ -3315,24 +3500,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "native-tls" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -3352,6 +3519,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nom" version = "6.1.2" @@ -3511,6 +3684,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "oid-registry" version = "0.1.5" @@ -3572,12 +3751,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - [[package]] name = "openssl-src" version = "111.25.0+1.1.1t" @@ -3622,7 +3795,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.12.3", "primeorder", ] @@ -3632,7 +3805,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" dependencies = [ - "group", + "group 0.12.0", ] [[package]] @@ -3682,6 +3855,16 @@ dependencies = [ "base64 0.13.0", ] +[[package]] +name = "pem" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" +dependencies = [ + "base64 0.21.2", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.6.0" @@ -3691,6 +3874,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -3804,8 +3996,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.0", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.6", + "spki 0.7.2", ] [[package]] @@ -3814,6 +4016,21 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "portable-atomic" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" +dependencies = [ + "portable-atomic 1.3.2", +] + +[[package]] +name = "portable-atomic" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5" + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -3864,7 +4081,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.12.3", ] [[package]] @@ -4010,6 +4227,7 @@ dependencies = [ "data-encoding", "flate2", "hex", + "hidapi", "ic-agent", "ic-base-types", "ic-ckbtc-minter", @@ -4024,19 +4242,26 @@ dependencies = [ "ic-sns-wasm", "icp-ledger", "icrc-ledger-types", + "indicatif", + "itertools", "k256 0.11.6", + "ledger-apdu", "ledger-canister", + "ledger-transport-hid", + "num-bigint 0.4.3", + "once_cell", "openssl", - "pem", + "pem 1.1.1", "qrcodegen", "rand", - "reqwest", "rpassword", "rust_decimal", + "scopeguard", "serde", "serde_bytes", "serde_cbor", "serde_json", + "serial_test", "sha3", "shellwords", "simple_asn1", @@ -4198,22 +4423,19 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "lazy_static", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", "rustls", - "rustls-pemfile 0.3.0", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util 0.6.10", "url", @@ -4230,11 +4452,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.8", "hmac", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -4351,18 +4583,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.0", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "0.3.0" @@ -4372,15 +4592,6 @@ dependencies = [ "base64 0.13.0", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" -dependencies = [ - "base64 0.13.0", -] - [[package]] name = "rustversion" version = "1.0.6" @@ -4393,16 +4604,6 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] - [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -4431,35 +4632,26 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.0", "generic-array", - "pkcs8", + "pkcs8 0.9.0", "subtle", "zeroize", ] [[package]] -name = "security-framework" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" +name = "sec1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ - "core-foundation-sys", - "libc", + "base16ct 0.2.0", + "der 0.7.6", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", ] [[package]] @@ -4473,9 +4665,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] @@ -4501,13 +4693,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.16", ] [[package]] @@ -4521,6 +4713,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "serde_tokenstream" version = "0.1.6" @@ -4578,6 +4781,31 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "sha1" version = "0.10.5" @@ -4604,9 +4832,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -4710,6 +4938,28 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "snafu" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "socket2" version = "0.4.4" @@ -4733,7 +4983,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.0", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.6", ] [[package]] @@ -4910,22 +5170,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.16", ] [[package]] @@ -4984,7 +5244,7 @@ dependencies = [ "pbkdf2", "rand", "rustc-hash", - "sha2 0.10.5", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -5046,16 +5306,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.23.4" diff --git a/Cargo.toml b/Cargo.toml index 09100430..7c6cc371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,6 @@ name = "quill" path = "src/main.rs" [dependencies] -anyhow = "1.0.34" -base64 = "0.13.0" -bip32 = "0.4.0" -candid = "0.8.2" -clap = { version = "3.1.18", features = ["derive", "cargo"] } -flate2 = "1.0.22" -hex = { version = "0.4.2", features = ["serde"] } -ic-agent = "0.21.0" -ic-identity-hsm = { version = "0.21.0", optional = true } ic-base-types = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } ic-ckbtc-minter = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } @@ -35,36 +26,54 @@ ic-sns-wasm = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad icp-ledger = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } icrc-ledger-types = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } ledger-canister = { git = "https://github.com/dfinity/ic", rev = "5e56d9e6f7da97bbad0ef0269a198e189c6d7120" } + +candid = "0.8.2" +ic-agent = { git = "https://github.com/dfinity/agent-rs", rev = "b9c57f8bab23cc9936dca4560293ffe485471c6d" } +ic-identity-hsm = { git = "https://github.com/dfinity/agent-rs", rev = "b9c57f8bab23cc9936dca4560293ffe485471c6d", optional = true } + +anyhow = "1.0.34" +atty = "0.2.14" +base64 = "0.13.0" +bip32 = "0.4.0" +clap = { version = "3.1.18", features = ["derive", "cargo"] } +crc32fast = "1.3.2" +data-encoding = "2.3.3" +flate2 = "1.0.22" +hex = { version = "0.4.2", features = ["serde"] } +hidapi = { version = "1.4", default-features = false, optional = true } +indicatif = "0.17" +itertools = "0.10.5" +k256 = { version = "0.11.4", features = ["pem"] } +ledger-apdu = { version = "0.10", optional = true } +ledger-transport-hid = { version = "0.10", optional = true } +num-bigint = "0.4.3" +once_cell = "1.17.1" openssl = "0.10.48" pem = "1.0.1" qrcodegen = "1.8" rand = { version = "0.8.4", features = ["getrandom"] } +rpassword = "6.0.0" rust_decimal = "1.26" simple_asn1 = "0.6.1" +scopeguard = "1" serde = { version = "1.0.130", features = ["derive"] } serde_bytes = "0.11.2" serde_cbor = "0.11.2" serde_json = "1.0.57" +sha3 = "0.10.6" tiny-bip39 = "1.0.0" tokio = { version = "1.18.5", features = ["full"] } -rpassword = "6.0.0" - -# forces reqwest to be >0.11.6 to avoid issues in agent-rs -reqwest = "0.11.10" -k256 = "0.11.4" -crc32fast = "1.3.2" -data-encoding = "2.3.3" -atty = "0.2.14" -sha3 = "0.10.6" [dev-dependencies] tempfile = "3.3.0" shellwords = "1" +serial_test = "2.0.0" [features] static-ssl = ["openssl/vendored"] hsm = ["dep:ic-identity-hsm"] -default = ["static-ssl", "hsm"] +ledger = ["dep:hidapi", "dep:ledger-apdu", "dep:ledger-transport-hid"] +default = ["static-ssl", "hsm", "ledger"] [profile.release] opt-level = 2 diff --git a/Cross.toml b/Cross.toml index 4f1cfd4d..985438dd 100644 --- a/Cross.toml +++ b/Cross.toml @@ -1,4 +1,2 @@ [build] -pre-build = [ - "apt-get update && apt-get install --assume-yes libssl-dev" -] +pre-build = "scripts/workflows/provision-linux-build.sh" diff --git a/README.md b/README.md index 87f1254d..d8aefeb6 100644 --- a/README.md +++ b/README.md @@ -126,15 +126,16 @@ cargo build --release --locked After this, find the binary at `target/release/quill`. -Quill has two optional features, both activated by default: +Quill has three optional features, all activated by default: - `static-ssl`, to build OpenSSL from source instead of dynamically linking a preinstalled version (requires a C compiler) - `hsm`, to enable PKCS#11 HSM support (requires runtime dynamic linking) +- `ledger`, to enable Ledger Nano support (requires runtime dynamic linking, and incompatible with armv6) To build a version of Quill that links OpenSSL dynamically, but retains HSM support, run: ```sh -cargo build --release --locked --no-default-features --feature hsm +cargo build --release --locked --no-default-features --features hsm,ledger ``` To build a version of Quill compatible with statically-linked-only environments, such as Alpine, run: @@ -148,8 +149,8 @@ cargo build --release --locked --no-default-features --feature static-ssl Quill can be reproducibly built or cross-compiled in a Docker container using [`cross`](https://github.com/cross-rs/cross). 1. Follow the instructions at [cross-rs/cross](https://github.com/cross-rs/cross) to install `cross`. -2. If using a target with particular restrictions, such as `x86_64-apple-darwin`, ensure you have built a local image via the instructions at [cross-rs/cross-toolchains](https://github.com/cross-rs/cross-toolchains). -3. Run `cross build --release --locked --target `, e.g. `--target x86_64-unknown-linux-gnu`. +2. If using a target with particular restrictions, such as `x86_64-apple-darwin` or `x86_64-pc-windows-msvc`, ensure you have built a local image via the instructions at [cross-rs/cross-toolchains](https://github.com/cross-rs/cross-toolchains). +3. Run `cross build --release --locked --target `, e.g. `--target x86_64-unknown-linux-gnu` or `--target armv7-unknown-linux-gnueabihf`. ## Testnets diff --git a/docs/cli-reference/quill-parent.md b/docs/cli-reference/quill-parent.md index eb3e420d..0b40cc26 100644 --- a/docs/cli-reference/quill-parent.md +++ b/docs/cli-reference/quill-parent.md @@ -25,6 +25,7 @@ You can use the following optional flags with the `quill` parent command or with | `--insecure-local-dev-mode` | Enter local testing mode. | | `--qr` | Output the result(s) as UTF-8 QR codes. | | `-V`, `--version` | Displays version information. | +| `--ledger` | Authenticate using a Ledger hardware wallet | ## Options @@ -78,6 +79,20 @@ Other PKCS#11 modules than OpenSC can be used as well. For example, to make use quill list-neurons --hsm-slot 0 --hsm-id 05 --hsm-libpath /usr/local/lib/libykcs11.so ``` +Quill can use a Ledger device such as the Ledger Nano, allowing you to use the same principal and account with Quill that you use with the NNS website. + +```sh +quill list-neurons --ledger +``` + +The Ledger device can be signaled to display the principal Quill uses with it: + +```sh +quill public-ids --ledger --display-on-ledger +``` + +Note that PKCS#11 is not supported on the linux-musl build and Ledger is not supported on either linux-musl or linux-arm32. The latter is to preserve compatibility with armv6; if you have an armv7 device you can [build Quill from source](https://github.com/dfinity/quill#build). + ## Remarks HSM commands ask for your PIN interactively, and for security cannot be piped. To use them in a script, you can instead pass the PIN via the `QUILL_HSM_PIN` environment variable. The other three flags can also be specified via `QUILL_HSM_SLOT`, `QUILL_HSM_LIBPATH`, and `QUILL_HSM_ID`. diff --git a/docs/cli-reference/quill-public-ids.md b/docs/cli-reference/quill-public-ids.md index 6d91ed8d..b9a53c35 100644 --- a/docs/cli-reference/quill-public-ids.md +++ b/docs/cli-reference/quill-public-ids.md @@ -12,10 +12,11 @@ quill public-ids [option] ## Flags -| Flag | Description | -|-----------------|----------------------------------------------------------------| -| `-h`, `--help` | Displays usage information. | -| `--genesis-dfn` | Additionally prints the legacy DFN address for Genesis claims. | +| Flag | Description | +|-----------------------|-----------------------------------------------------------------| +| `-h`, `--help` | Displays usage information. | +| `--genesis-dfn` | Additionally prints the legacy DFN address for Genesis claims. | +| `--display-on-ledger` | Additionally prints the principal on an attached Ledger device. | ## Options diff --git a/scripts/workflows/provision-darwin-build.sh b/scripts/workflows/provision-darwin-build.sh new file mode 100755 index 00000000..64ab3c7b --- /dev/null +++ b/scripts/workflows/provision-darwin-build.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euxo pipefail + +# 🌵 diff --git a/scripts/workflows/provision-darwin.sh b/scripts/workflows/provision-darwin-e2e.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/workflows/provision-darwin.sh rename to scripts/workflows/provision-darwin-e2e.sh diff --git a/scripts/workflows/provision-linux-build.sh b/scripts/workflows/provision-linux-build.sh new file mode 100755 index 00000000..20ca6664 --- /dev/null +++ b/scripts/workflows/provision-linux-build.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euxo pipefail + +if [[ $# = 1 ]]; then # docker + dpkg --add-architecture "$CROSS_DEB_ARCH" + if [[ $1 != "arm-"* && $1 != *"-musl" ]]; then + arch=":$CROSS_DEB_ARCH" + fi + sudo() { + "$@" + } +fi + +sudo apt-get update -y +sudo apt-get install "libudev-dev${arch-}" libssl-dev -y diff --git a/scripts/workflows/provision-linux.sh b/scripts/workflows/provision-linux-e2e.sh old mode 100644 new mode 100755 similarity index 100% rename from scripts/workflows/provision-linux.sh rename to scripts/workflows/provision-linux-e2e.sh diff --git a/scripts/workflows/provision-windows-build.ps1 b/scripts/workflows/provision-windows-build.ps1 new file mode 100755 index 00000000..e280a203 --- /dev/null +++ b/scripts/workflows/provision-windows-build.ps1 @@ -0,0 +1,7 @@ +Set-StrictMode -Version 2 +$ErrorActionPreference = 'Stop' + +vcpkg integrate install +vcpkg install openssl:x64-windows-static-md +'OPENSSL_DIR=C:\vcpkg\installed\x64-windows-static-md' >> $env:GITHUB_ENV +'OPENSSL_STATIC=Yes' >> $env:GITHUB_ENV diff --git a/src/commands/ckbtc/retrieve_btc.rs b/src/commands/ckbtc/retrieve_btc.rs index a4cd468f..02c99746 100644 --- a/src/commands/ckbtc/retrieve_btc.rs +++ b/src/commands/ckbtc/retrieve_btc.rs @@ -9,7 +9,7 @@ use icrc_ledger_types::icrc1::transfer::{Memo, TransferArg}; use crate::{ commands::get_ids, lib::{ - ckbtc_canister_id, ckbtc_minter_canister_id, + ckbtc_canister_id, ckbtc_minter_canister_id, now_nanos, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, ParsedSubaccount, ROLE_CKBTC_MINTER, ROLE_ICRC1_LEDGER, }, @@ -64,7 +64,7 @@ pub fn exec(auth: &AuthInfo, opts: RetrieveBtcOpts) -> AnyhowResult AnyhowResult AnyhowResult> { sig, )?]) } else { + #[cfg(feature = "ledger")] + if let AuthInfo::Ledger = auth { + let (_, pk) = LedgerIdentity::new()?.public_key()?; + let sig = Encode!(&hex::encode(pk))?; + Ok(vec![sign_ingress_with_request_status_query( + auth, + genesis_token_canister_id(), + ROLE_NNS_GTC, + "claim_neurons", + sig, + )?]) + } else { + Err(anyhow!( + "claim-neurons command requires --pem-file or --ledger to be specified" + )) + } + #[cfg(not(feature = "ledger"))] Err(anyhow!( - "claim-neurons command requires a --pem-file to be specified" + "claim-neurons command requires --pem-file to be specified" )) } } diff --git a/src/commands/neuron_manage.rs b/src/commands/neuron_manage.rs index 6a8987b5..f96a5660 100644 --- a/src/commands/neuron_manage.rs +++ b/src/commands/neuron_manage.rs @@ -4,7 +4,7 @@ use crate::lib::{ signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, ParsedNnsAccount, ROLE_NNS_GOVERNANCE, }; -use anyhow::{anyhow, bail, Context}; +use anyhow::{anyhow, bail, ensure, Context}; use candid::{CandidType, Encode, Principal}; use clap::{ArgEnum, Parser}; use ic_base_types::PrincipalId; @@ -124,9 +124,22 @@ pub struct ManageOpts { /// Set whether new maturity should be automatically staked. #[clap(long, arg_enum)] auto_stake_maturity: Option, + + #[clap(from_global)] + ledger: bool, } pub fn exec(auth: &AuthInfo, opts: ManageOpts) -> AnyhowResult> { + if opts.ledger { + ensure!( + opts.add_hot_key.is_none() && opts.remove_hot_key.is_none() && !opts.disburse && opts.disburse_amount.is_none() && opts.disburse_to.is_none() + && !opts.clear_manage_neuron_followees && !opts.join_community_fund && !opts.leave_community_fund + && opts.follow_topic.is_none() && opts.follow_neurons.is_none() && opts.register_vote.is_none() && !opts.reject, + "\ +Cannot use --ledger with these flags. This version of quill only supports the following neuron-manage operations with a Ledger device: +--additional-dissolve-delay-seconds, --start-dissolving, --stop-dissolving, --split, --merge-from-neuron, --spawn, --stake-maturity, --auto-stake-maturity" + ) + } let mut msgs = Vec::new(); let id = Some(NeuronId { diff --git a/src/commands/neuron_stake.rs b/src/commands/neuron_stake.rs index 39bb8155..9b42be73 100644 --- a/src/commands/neuron_stake.rs +++ b/src/commands/neuron_stake.rs @@ -9,7 +9,7 @@ use crate::{ AnyhowResult, AuthInfo, ParsedNnsAccount, ParsedSubaccount, ROLE_NNS_GOVERNANCE, }, }; -use anyhow::anyhow; +use anyhow::{anyhow, ensure}; use candid::{CandidType, Encode, Principal}; use clap::Parser; use ic_nns_constants::GOVERNANCE_CANISTER_ID; @@ -52,9 +52,16 @@ pub struct StakeOpts { /// The subaccount to transfer from. #[clap(long)] from_subaccount: Option, + + #[clap(from_global)] + ledger: bool, } pub fn exec(auth: &AuthInfo, opts: StakeOpts) -> AnyhowResult> { + ensure!( + !opts.ledger, + "Cannot use `--ledger` with this command. This version of Quill does not support staking new neurons with a Ledger device" + ); let (controller, _) = crate::commands::public::get_ids(auth)?; let nonce = match (&opts.nonce, &opts.name) { (Some(nonce), _) => *nonce, diff --git a/src/commands/public.rs b/src/commands/public.rs index 82545429..d5dd25c4 100644 --- a/src/commands/public.rs +++ b/src/commands/public.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "ledger")] +use crate::lib::ledger::LedgerIdentity; use crate::lib::{get_account_id, get_identity, AnyhowResult, AuthInfo}; use anyhow::{anyhow, bail, Context}; use candid::Principal; @@ -15,6 +17,10 @@ pub struct PublicOpts { /// Additionally prints the legacy DFN address for Genesis claims. #[clap(long, conflicts_with = "principal-id")] genesis_dfn: bool, + /// If authenticating with a Ledger device, display the public IDs on the device. + #[cfg_attr(not(feature = "ledger"), clap(hidden = true))] + #[clap(long, requires = "ledgerhq")] + display_on_ledger: bool, } /// Prints the account and the principal ids. @@ -28,6 +34,16 @@ pub fn exec(auth: &AuthInfo, opts: PublicOpts) -> AnyhowResult { }; println!("DFN address: {}", get_dfn(pem)?) } + if opts.display_on_ledger { + #[cfg(feature = "ledger")] + { + LedgerIdentity::new()?.display_pk()?; + } + #[cfg(not(feature = "ledger"))] + { + bail!("This build of quill does not support Ledger functionality."); + } + } Ok(()) } diff --git a/src/commands/request_status.rs b/src/commands/request_status.rs index d4cac81b..a2ab9c53 100644 --- a/src/commands/request_status.rs +++ b/src/commands/request_status.rs @@ -2,7 +2,7 @@ use crate::lib::get_ic_url; use crate::lib::{get_agent, get_idl_string, signing::RequestStatus, AnyhowResult, AuthInfo}; use anyhow::{anyhow, Context}; use candid::Principal; -use ic_agent::agent::{ReplicaV2Transport, Replied, RequestStatusResponse}; +use ic_agent::agent::{Replied, RequestStatusResponse, Transport}; use ic_agent::AgentError::MessageError; use ic_agent::{AgentError, RequestId}; use std::future::Future; @@ -24,7 +24,7 @@ pub async fn submit( if fetch_root_key { agent.fetch_root_key().await?; } - agent.set_transport(ProxySignReplicaV2Transport { + agent.set_transport(ProxySignTransport { req: req.clone(), http_transport: Arc::new( ic_agent::agent::http_transport::ReqwestHttpReplicaV2Transport::create(get_ic_url()) @@ -33,19 +33,10 @@ pub async fn submit( }); let Replied::CallReplied(blob) = async { loop { - match agent - .request_status_raw(&request_id, canister_id, false) - .await? - { + match agent.request_status_raw(&request_id, canister_id).await? { RequestStatusResponse::Replied { reply } => return Ok(reply), - RequestStatusResponse::Rejected { - reject_code, - reject_message, - } => { - return Err(anyhow!(AgentError::ReplicaError { - reject_code, - reject_message, - })) + RequestStatusResponse::Rejected(response) => { + return Err(anyhow!(AgentError::ReplicaError(response))) } RequestStatusResponse::Unknown | RequestStatusResponse::Received @@ -73,18 +64,18 @@ pub async fn submit( .context("Invalid IDL blob.") } -pub(crate) struct ProxySignReplicaV2Transport { +pub(crate) struct ProxySignTransport { req: RequestStatus, - http_transport: Arc, + http_transport: Arc, } -impl ReplicaV2Transport for ProxySignReplicaV2Transport { +impl Transport for ProxySignTransport { fn read_state<'a>( &'a self, _canister_id: Principal, _content: Vec, ) -> Pin, AgentError>> + Send + 'a>> { - async fn run(transport: &ProxySignReplicaV2Transport) -> Result, AgentError> { + async fn run(transport: &ProxySignTransport) -> Result, AgentError> { let canister_id = Principal::from_text(transport.req.canister_id.clone()) .map_err(|err| MessageError(format!("Unable to parse canister_id: {}", err)))?; let envelope = hex::decode(transport.req.content.clone()).map_err(|err| { diff --git a/src/commands/send.rs b/src/commands/send.rs index f0992345..bfd9605f 100644 --- a/src/commands/send.rs +++ b/src/commands/send.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, bail, Context}; use atty::Stream; use candid::{CandidType, Principal}; use clap::Parser; -use ic_agent::agent::ReplicaV2Transport; +use ic_agent::agent::Transport; use ic_agent::{agent::http_transport::ReqwestHttpReplicaV2Transport, RequestId}; use icp_ledger::{Subaccount, Tokens}; use serde::{Deserialize, Serialize}; @@ -173,8 +173,7 @@ async fn send(message: &Ingress, opts: &SendOpts) -> AnyhowResult { match message.call_type.as_str() { "query" => { let response = parse_query_response( - ic_agent::agent::ReplicaV2Transport::query(&transport, canister_id, content) - .await?, + ic_agent::agent::Transport::query(&transport, canister_id, content).await?, canister_id, &role, &method_name, diff --git a/src/commands/sns.rs b/src/commands/sns.rs index 5ecf4d05..dfdfbd9e 100644 --- a/src/commands/sns.rs +++ b/src/commands/sns.rs @@ -5,7 +5,7 @@ use std::{ str::FromStr, }; -use anyhow::Context; +use anyhow::{ensure, Context}; use candid::{Deserialize, Principal}; use clap::{Parser, Subcommand}; use ic_sns_governance::pb::v1::Account as GovAccount; @@ -56,6 +56,8 @@ pub struct SnsOpts { canister_ids_file: Option, #[clap(subcommand)] subcommand: SnsCommand, + #[clap(from_global)] + ledger: bool, } #[derive(Subcommand)] @@ -83,6 +85,13 @@ pub enum SnsCommand { } pub fn dispatch(auth: &AuthInfo, opts: SnsOpts, qr: bool, fetch_root_key: bool) -> AnyhowResult { + if opts.ledger { + ensure!(matches!( + opts.subcommand, + SnsCommand::Balance(_) | SnsCommand::Transfer(_) | SnsCommand::NeuronPermission(_) | SnsCommand::Disburse(_) + | SnsCommand::ConfigureDissolveDelay(_) | SnsCommand::StakeMaturity(_) | SnsCommand::NeuronId(_) + ), "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 .context("Cannot sign message without knowing the SNS canister ids, did you forget `--canister-ids-file `?") .and_then(|file| Ok(serde_json::from_slice::(&fs::read(file)?)?)); diff --git a/src/commands/sns/stake_neuron.rs b/src/commands/sns/stake_neuron.rs index bf9b1cfa..93c5202e 100644 --- a/src/commands/sns/stake_neuron.rs +++ b/src/commands/sns/stake_neuron.rs @@ -1,8 +1,12 @@ 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::{sign_ingress_with_request_status_query, IngressWithRequestId}, - AuthInfo, ParsedSubaccount, ROLE_ICRC1_LEDGER, ROLE_SNS_GOVERNANCE, + signing::IngressWithRequestId, AuthInfo, ParsedSubaccount, ROLE_ICRC1_LEDGER, + ROLE_SNS_GOVERNANCE, }, AnyhowResult, }; @@ -77,7 +81,7 @@ pub fn exec( amount: amount.get_e8s().into(), fee: opts.fee.map(|fee| fee.get_e8s().into()), from_subaccount: opts.from_subaccount.map(|x| x.0 .0), - created_at_time: None, + created_at_time: Some(now_nanos()), to: Account { owner: governance_canister_id, subaccount: Some(neuron_subaccount.0), @@ -105,7 +109,7 @@ pub fn exec( })) })?; - messages.push(sign_ingress_with_request_status_query( + messages.push(sign_staking_ingress_with_request_status_query( auth, governance_canister_id, ROLE_SNS_GOVERNANCE, diff --git a/src/commands/sns/transfer.rs b/src/commands/sns/transfer.rs index 13faa14a..7abbd55d 100644 --- a/src/commands/sns/transfer.rs +++ b/src/commands/sns/transfer.rs @@ -1,10 +1,10 @@ use crate::commands::get_account; use crate::commands::transfer::parse_tokens; +use crate::lib::{now_nanos, ParsedAccount, ROLE_ICRC1_LEDGER}; use crate::lib::{ signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, ParsedSubaccount, }; -use crate::lib::{ParsedAccount, ROLE_ICRC1_LEDGER}; use candid::Encode; use clap::Parser; use icp_ledger::Tokens; @@ -55,7 +55,7 @@ pub fn exec( amount, fee, from_subaccount: opts.from_subaccount.map(|x| x.0 .0), - created_at_time: None, + created_at_time: Some(now_nanos()), to, }; diff --git a/src/commands/transfer.rs b/src/commands/transfer.rs index 613b119c..bfd74114 100644 --- a/src/commands/transfer.rs +++ b/src/commands/transfer.rs @@ -1,10 +1,12 @@ -use crate::commands::send::{Memo, SendArgs}; +use crate::commands::send::{Memo, SendArgs, TimeStamp}; use crate::lib::{ ledger_canister_id, signing::{sign_ingress_with_request_status_query, IngressWithRequestId}, AnyhowResult, AuthInfo, }; -use crate::lib::{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 candid::Encode; use clap::Parser; @@ -47,7 +49,9 @@ pub fn exec(auth: &AuthInfo, opts: TransferOpts) -> AnyhowResult AnyhowResult>> = + Lazy::new(|| Mutex::new(Weak::new())); + +// necessary due to Identity::sign not providing other ways to figure this out +thread_local! { + static NEXT_STAKE: Cell = Cell::new(false); +} + +struct LedgerIdentityInner { + transport: Mutex, +} + +/// An [`Identity`] backed by a Ledger device. +pub struct LedgerIdentity { + inner: Arc, +} + +impl LedgerIdentity { + /// Creates a new ledger-device-backed identity. + pub fn new() -> AnyhowResult { + let mut global = GLOBAL_HANDLE.lock().unwrap(); + if let Some(existing) = global.upgrade() { + Ok(Self { inner: existing }) + } else { + let inner = Arc::new(LedgerIdentityInner { + transport: Mutex::new(TransportNativeHID::new(&HidApi::new().unwrap())?), + }); + *global = Arc::downgrade(&inner); + Ok(Self { inner }) + } + } + /// Within the provided scope, transfers will be marked as 'staking' transactions. + /// The IC app will refuse transfers to the governance canister unless this is used. + pub fn with_staking(f: impl FnOnce() -> T) -> T { + // This is designed the way it is because Ledger signing has two modes and Identity::sign has one. + // Short of re-parsing the transaction to figure out if it's transferring to the governance canister, + // one must use a way of communicating this magic staking flag other than an Identity::sign parameter. + // Global contextual state was judged the simplest way. This value is thread-local and only accessible in a closure, + // so it should affect neither other threads nor other async tasks. + // A scope-guard is used to reset it as destructors are still run during panics. + NEXT_STAKE.with(|next_stake| { + let _guard = scopeguard::guard((), |_| next_stake.set(false)); + next_stake.set(true); + f() + }) + } + /// Gets the version of the IC app. + #[allow(unused)] + pub fn version(&self) -> AnyhowResult { + get_version(&self.inner.transport.lock().unwrap()) + } + /// Displays the principal and legacy account ID on the Ledger, and asks the user to confirm it. + pub fn display_pk(&self) -> AnyhowResult<()> { + let spinner = ProgressBar::new_spinner(); + spinner.set_message("Confirm principal on Ledger device..."); + spinner.enable_steady_tick(Duration::from_millis(100)); + display_pk(&self.inner.transport.lock().unwrap(), &derivation_path())?; + spinner.finish_and_clear(); + Ok(()) + } + /// Gets the public key from the ledger that [`sender`](Self::sender) will return a principal derived from. + pub fn public_key(&self) -> AnyhowResult<(Principal, Vec)> { + get_identity(&self.inner.transport.lock().unwrap(), &derivation_path()) + .map_err(anyhow::Error::msg) + } +} + +impl Identity for LedgerIdentity { + fn sender(&self) -> Result { + let (principal, _) = + get_identity(&self.inner.transport.lock().unwrap(), &derivation_path())?; + Ok(principal) + } + /// Sign a request ID from a content map. + /// + /// The behavior of this function is affected by whether it is in a [`with_staking`](Self::with_staking) scope or not. + #[allow(clippy::bool_to_int_with_if)] + fn sign(&self, content: &EnvelopeContent) -> Result { + let path = derivation_path(); + let next_stake = NEXT_STAKE.with(|next_stake| next_stake.replace(false)); + let transport = self.inner.transport.lock().unwrap(); + let (_, pk) = get_identity(&transport, &path)?; + // The IC ledger app expects to receive the entire envelope, sans signature. + #[derive(Serialize)] + struct Envelope<'a> { + content: &'a EnvelopeContent, + } + let mut blob = vec![]; + let mut serializer = Serializer::new(&mut blob); + serializer.self_describe().map_err(|e| format!("{e}"))?; + Envelope { content } + .serialize(&mut serializer) + .map_err(|e| format!("{e}"))?; + let spinner = ProgressBar::new_spinner(); + let message = match content { + EnvelopeContent::Call { method_name, .. } => format!("`{method_name}` call"), + EnvelopeContent::Query { method_name, .. } => format!("`{method_name}` query call"), + EnvelopeContent::ReadState { .. } => "status check".into(), + }; + spinner.set_message(format!("Confirm {message} on Ledger device...")); + spinner.enable_steady_tick(Duration::from_millis(100)); + let sig = sign_blob( + &transport, + &blob, + // See with_staking + if next_stake { TX_STAKING } else { TX_NORMAL }, + &path, + content, + )?; + spinner.finish_with_message(format!("Confirmed {message} on Ledger device")); + Ok(Signature { + public_key: Some(pk), + signature: Some(sig), + }) + } +} + +fn serialize_path(path: &DerivationPath) -> Vec { + path.as_ref() + .iter() + .flat_map(|x| x.0.to_le_bytes()) + .collect() +} + +fn get_identity( + transport: &TransportNativeHID, + path: &DerivationPath, +) -> Result<(Principal, Vec), String> { + let command = APDUCommand { + cla: CLA, + ins: GET_ADDR_SECP256K1, + p1: P1_ONLY_RETRIEVE, + p2: 0, + data: serialize_path(path), + }; + let response = transport + .exchange(&command) + .map_err(|e| format!("Error communicating with Ledger: {e}"))?; + let response = interpret_response(&response, "fetching principal from Ledger", None)?; + let pk = response[PK_OFFSET..PK_OFFSET + PK_LEN].to_vec(); + let principal = + Principal::try_from_slice(&response[PRINCIPAL_OFFSET..PRINCIPAL_OFFSET + PRINCIPAL_LEN]) + .map_err(|e| format!("Error interpreting principal from Ledger: {e}"))?; + Ok((principal, pk)) +} + +fn interpret_response<'a>( + response: &'a APDUAnswer>, + action: &str, + content: Option<&EnvelopeContent>, +) -> Result<&'a [u8], String> { + if let Ok(errcode) = response.error_code() { + match errcode { + APDUErrorCode::NoError => Ok(response.apdu_data()), + APDUErrorCode::DataInvalid if matches!(content, Some(EnvelopeContent::Call { method_name, .. }) if method_name == "send_dfx") => { + Err(format!("Error {action}: Must use a principal or ICRC-1 account ID, not a legacy account ID")) + } + APDUErrorCode::DataInvalid if matches!(content, + Some(EnvelopeContent::Call { method_name, canister_id, .. } + | EnvelopeContent::Query { method_name, canister_id, .. }) + if !supported_transaction(canister_id, method_name) + ) => { + Err(format!( + "Error {action}: The IC app for Ledger only supports transfers and certain neuron management operations" + )) + } + APDUErrorCode::DataInvalid if matches!(content, Some(EnvelopeContent::Call { method_name, .. }) if method_name == "icrc1_transfer") => { + Err(format!( + "Error {action}: The IC app for Ledger only supports transfers to user principals, not canisters or the anonymous principal" + )) + } + APDUErrorCode::ClaNotSupported => Err(format!("Error {action}: IC app not open on device")), + APDUErrorCode::CommandNotAllowed => Err(format!("Error {action}: Device rejected the message")), + errcode => match std::str::from_utf8(response.apdu_data()) { + Ok(s) if !s.trim().is_empty() => Err(format!("Error {action}: {errcode:?}: {s}")), + _ => Err(format!("Error {action}: {errcode:?}")), + }, + } + } else { + match response.retcode() { + 0x6E01 => Err(format!("Error {action}: IC app not open on device")), + 0x5515 => Err(format!("Error {action}: device is sleeping")), + retcode => Err(format!("Error {action}: {retcode:#X}")), + } + } +} + +pub fn supported_transaction(canister_id: &Principal, method_name: &str) -> bool { + if *canister_id == genesis_token_canister_id() { + method_name == "claim_neurons" + } else if *canister_id == governance_canister_id() { + method_name == "manage_neuron" + || method_name == "manage_neuron_pb" + || method_name == "list_neurons" + || method_name == "list_neurons_pb" + || method_name == "update_node_provider" + } else if *canister_id == ledger_canister_id() { + method_name == "send_pb" || method_name == "icrc1_transfer" + } else { + method_name == "icrc1_transfer" + || method_name == "manage_neuron" + || method_name == "list_neurons" + } +} + +fn sign_blob( + transport: &TransportNativeHID, + blob: &[u8], + txtype: u8, + path: &DerivationPath, + content: &EnvelopeContent, +) -> Result, String> { + sign_chunk( + transport, + PAYLOAD_INIT, + &serialize_path(path), + txtype, + content, + )?; + let chunks = blob.chunks(CHUNK_SIZE); + let end = chunks.len() - 1; + for (i, chunk) in chunks.enumerate() { + let res = sign_chunk( + transport, + if i == end { PAYLOAD_LAST } else { PAYLOAD_ADD }, + chunk, + txtype, + content, + )?; + if i == end { + return Ok(res.ok_or("Error signing message with Ledger: No signature returned")?); + } + } + unreachable!() +} + +fn sign_chunk( + transport: &TransportNativeHID, + kind: u8, + chunk: &[u8], + txtype: u8, + content: &EnvelopeContent, +) -> Result>, String> { + let command = APDUCommand { + cla: CLA, + ins: SIGN_SECP256K1, + p1: kind, + p2: txtype, + data: chunk, + }; + let response = transport + .exchange(&command) + .map_err(|e| format!("Error communicating with Ledger: {e}"))?; + let response = interpret_response(&response, "signing message with Ledger", Some(content))?; + if !response.is_empty() { + Ok(Some(response[SIG_OFFSET..SIG_OFFSET + SIG_LEN].to_vec())) + } else { + Ok(None) + } +} + +fn get_version(transport: &TransportNativeHID) -> AnyhowResult { + let command = APDUCommand { + cla: CLA, + ins: GET_VERSION, + p1: 0, + p2: 0, + data: &[][..], + }; + let response = transport + .exchange(&command) + .context("Error communicating with ledger")?; + let response = interpret_response(&response, "fetching version from Ledger", None) + .map_err(anyhow::Error::msg)?; + Ok(LedgerVersion { + major: response[1], + minor: response[2], + patch: response[3], + }) +} + +fn display_pk(transport: &TransportNativeHID, path: &DerivationPath) -> AnyhowResult<()> { + let command = APDUCommand { + cla: CLA, + ins: GET_ADDR_SECP256K1, + p1: P1_SHOW_ADDRESS_IN_DEVICE, + p2: 0, + data: serialize_path(path), + }; + let response = transport + .exchange(&command) + .context("Error communicating with ledger")?; + interpret_response(&response, "displaying public key on Ledger", None) + .map_err(anyhow::Error::msg)?; + Ok(()) +} + +#[derive(Debug)] +pub struct LedgerVersion { + pub major: u8, + pub minor: u8, + pub patch: u8, +} + +impl fmt::Display for LedgerVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 3199497a..91d3879c 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -1,6 +1,7 @@ //! All the common functionality. use anyhow::{anyhow, bail, ensure, Context}; +use bip32::DerivationPath; use bip39::{Mnemonic, Seed}; use candid::{ parser::typing::{check_prog, TypeEnv}, @@ -29,7 +30,6 @@ 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,6 +38,10 @@ use std::{ path::Path, time::Duration, }; +use std::{str::FromStr, time::SystemTime}; + +#[cfg(feature = "ledger")] +use self::ledger::LedgerIdentity; pub const IC_URL: &str = "https://ic0.app"; @@ -45,6 +49,8 @@ pub fn get_ic_url() -> String { env::var("IC_URL").unwrap_or_else(|_| IC_URL.to_string()) } +#[cfg(feature = "ledger")] +pub mod ledger; pub mod signing; pub type AnyhowResult = anyhow::Result; @@ -93,6 +99,8 @@ pub enum AuthInfo { PemFile(String), // --private-pem file specified #[cfg(feature = "hsm")] Pkcs11Hsm(HSMInfo), + #[cfg(feature = "ledger")] + Ledger, } pub fn ledger_canister_id() -> Principal { @@ -319,6 +327,8 @@ pub fn get_identity(auth: &AuthInfo) -> AnyhowResult> { .context("Unable to use your hardware key")?; Ok(Box::new(identity) as _) } + #[cfg(feature = "ledger")] + AuthInfo::Ledger => Ok(Box::new(LedgerIdentity::new()?)), } } @@ -389,7 +399,7 @@ pub fn mnemonic_to_pem(mnemonic: &Mnemonic) -> AnyhowResult { } let seed = Seed::new(mnemonic, ""); - let ext = bip32::XPrv::derive_from_path(seed, &"m/44'/223'/0'/0/0".parse()?) + let ext = bip32::XPrv::derive_from_path(seed, &derivation_path()) .map_err(|err| anyhow!("{:?}", err)) .context("Failed to derive BIP32 extended private key")?; let secret = ext.private_key(); @@ -407,6 +417,11 @@ pub fn mnemonic_to_pem(mnemonic: &Mnemonic) -> AnyhowResult { Ok(key_pem.replace('\r', "").replace("\n\n", "\n")) } +const DERIVATION_PATH: &str = "m/44'/223'/0'/0/0"; +fn derivation_path() -> DerivationPath { + DERIVATION_PATH.parse().unwrap() +} + pub struct ParsedSubaccount(pub Subaccount); impl FromStr for ParsedSubaccount { @@ -548,6 +563,17 @@ impl ParsedNnsAccount { } } +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 + } +} + #[cfg(test)] mod tests { use super::{ParsedAccount, ParsedSubaccount}; diff --git a/src/lib/signing.rs b/src/lib/signing.rs index 1ebdd247..ae63617a 100644 --- a/src/lib/signing.rs +++ b/src/lib/signing.rs @@ -1,6 +1,6 @@ use crate::lib::get_idl_string; use crate::lib::{AnyhowResult, AuthInfo}; -use anyhow::{anyhow, Context}; +use anyhow::{anyhow, bail, Context}; use candid::Principal; use ic_agent::agent::UpdateBuilder; use ic_agent::RequestId; @@ -9,6 +9,8 @@ use serde_cbor::Value; use std::convert::TryFrom; use std::time::Duration; +#[cfg(feature = "ledger")] +use super::ledger::LedgerIdentity; use super::{get_agent, get_default_role}; #[derive(Debug)] @@ -110,14 +112,16 @@ pub fn sign( method_name: &str, args: Vec, role: &str, + #[allow(unused)] is_staking: bool, ) -> AnyhowResult { let ingress_expiry = Duration::from_secs(5 * 60); - - let signed_update = UpdateBuilder::new(&get_agent(auth)?, canister_id, method_name.to_string()) - .with_arg(args) - .expire_after(ingress_expiry) - .sign()?; - + let agent = get_agent(auth)?; + let signed_update = sign_with(auth, is_staking, || { + UpdateBuilder::new(&agent, canister_id, method_name.to_string()) + .with_arg(args) + .expire_after(ingress_expiry) + .sign() + })?; let content = hex::encode(signed_update.signed_update); let request_id = signed_update.request_id; @@ -132,6 +136,14 @@ pub fn sign( }) } +fn sign_with(auth: &AuthInfo, #[allow(unused)] is_staking: bool, f: impl FnOnce() -> T) -> T { + #[cfg(feature = "ledger")] + if is_staking && matches!(auth, AuthInfo::Ledger) { + return LedgerIdentity::with_staking(f); + } + f() +} + /// Generates a bundle of signed messages (ingress + request status query). pub fn sign_ingress_with_request_status_query( auth: &AuthInfo, @@ -140,7 +152,52 @@ pub fn sign_ingress_with_request_status_query( method_name: &str, args: Vec, ) -> AnyhowResult { - let msg_with_req_id = sign(auth, canister_id, method_name, args, role)?; + sign_ingress_with_request_status_query_internal( + auth, + canister_id, + role, + method_name, + args, + false, + ) +} + +/// Same as [`sign_ingress_with_request_status_query`], but signals that the request is staking. +pub fn sign_staking_ingress_with_request_status_query( + auth: &AuthInfo, + canister_id: Principal, + role: &str, + method_name: &str, + args: Vec, +) -> AnyhowResult { + sign_ingress_with_request_status_query_internal( + auth, + canister_id, + role, + method_name, + args, + true, + ) +} + +fn sign_ingress_with_request_status_query_internal( + auth: &AuthInfo, + canister_id: Principal, + role: &str, + method_name: &str, + args: Vec, + is_staking: bool, +) -> AnyhowResult { + #[cfg(feature = "ledger")] + if matches!(auth, AuthInfo::Ledger) + && !super::ledger::supported_transaction(&canister_id, method_name) + { + bail!( + "Cannot use --ledger with this command. This version of Quill only supports transfers \ + and certain neuron management operations with a Ledger device" + ); + } + let msg_with_req_id = sign(auth, canister_id, method_name, args, role, is_staking)?; let request_id = msg_with_req_id .request_id .context("No request id for transfer call found")?; diff --git a/src/main.rs b/src/main.rs index e6e47336..923a4113 100755 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use crate::lib::AnyhowResult; use anyhow::Context; use bip39::{Language, Mnemonic}; -use clap::{crate_version, Args, Parser}; +use clap::{crate_version, ArgGroup, Args, Parser}; use lib::AuthInfo; mod commands; @@ -22,18 +22,24 @@ pub struct CliOpts { } #[derive(Args)] +#[clap( + group(ArgGroup::new("pkcs11").multiple(true).conflicts_with_all(&["seeded", "ledgerhq"])), + group(ArgGroup::new("seeded").multiple(true).conflicts_with_all(&["pkcs11", "ledgerhq"])), + group(ArgGroup::new("ledgerhq").multiple(true).conflicts_with_all(&["seeded", "pkcs11"])), + group(ArgGroup::new("auth").multiple(true)), +)] struct GlobalOpts { /// Path to your PEM file (use "-" for STDIN) - #[clap(long, group = "auth", global = true)] + #[clap(long, groups = &["seeded", "auth"], global = true)] pem_file: Option, /// Use a hardware key to sign messages. - #[cfg_attr(feature = "hsm", clap(hidden = true))] - #[clap(long, group = "auth", global = true)] + #[cfg_attr(not(feature = "hsm"), clap(hidden = true))] + #[clap(long, groups = &["pkcs11", "auth"], global = true)] hsm: bool, /// Path to the PKCS#11 module to use. - #[cfg_attr(feature = "hsm", clap(hidden = true))] + #[cfg_attr(not(feature = "hsm"), clap(hidden = true))] #[cfg_attr( target_os = "windows", doc = r"Defaults to C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll" @@ -46,23 +52,28 @@ struct GlobalOpts { target_os = "linux", doc = "Defaults to /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so" )] - #[clap(long, global = true)] + #[clap(long, global = true, groups = &["pkcs11", "auth"])] hsm_libpath: Option, /// The slot that the hardware key is in. If OpenSC is installed, `pkcs11-tool --list-slots` - #[cfg_attr(feature = "hsm", clap(hidden = true))] - #[clap(long, global = true)] + #[cfg_attr(not(feature = "hsm"), clap(hidden = true))] + #[clap(long, global = true, groups = &["pkcs11", "auth"])] hsm_slot: Option, /// The ID of the key to use. Consult your hardware key's documentation. - #[cfg_attr(feature = "hsm", clap(hidden = true))] - #[clap(long, global = true)] + #[cfg_attr(not(feature = "hsm"), clap(hidden = true))] + #[clap(long, global = true, groups = &["pkcs11", "auth"])] hsm_id: Option, /// Path to your seed file (use "-" for STDIN) - #[clap(long, global = true)] + #[clap(long, global = true, groups = &["seeded", "auth"])] seed_file: Option, + /// Authenticate using a Ledger hardware wallet. + #[cfg_attr(not(feature = "ledger"), clap(hidden = true))] + #[clap(long, global = true, groups = &["ledgerhq", "auth"])] + ledger: bool, + /// Output the result(s) as UTF-8 QR codes. #[clap(long, global = true)] qr: bool, @@ -91,13 +102,9 @@ fn main() -> AnyhowResult { } fn get_auth(opts: GlobalOpts) -> AnyhowResult { - #[cfg(feature = "hsm")] - { - // Get PEM from the file if provided, or try to convert from the seed file - if opts.hsm - || opts.hsm_libpath.is_some() - || opts.hsm_slot.is_some() - || opts.hsm_id.is_some() + // Get PEM from the file if provided, or try to convert from the seed file + if opts.hsm || opts.hsm_libpath.is_some() || opts.hsm_slot.is_some() || opts.hsm_id.is_some() { + #[cfg(feature = "hsm")] { let mut hsm = lib::HSMInfo::new(); if let Some(path) = opts.hsm_libpath { @@ -110,19 +117,21 @@ fn get_auth(opts: GlobalOpts) -> AnyhowResult { hsm.ident = id; } Ok(AuthInfo::Pkcs11Hsm(hsm)) - } else { - pem_auth(opts) } - } - #[cfg(not(feature = "hsm"))] - { - if opts.hsm - || opts.hsm_libpath.is_some() - || opts.hsm_slot.is_some() - || opts.hsm_id.is_some() + #[cfg(not(feature = "hsm"))] { anyhow::bail!("This build of quill does not support HSM functionality.") } + } else if opts.ledger { + #[cfg(feature = "ledger")] + { + Ok(AuthInfo::Ledger) + } + #[cfg(not(feature = "ledger"))] + { + anyhow::bail!("This build of quill does not support Ledger functionality.") + } + } else { pem_auth(opts) } } @@ -172,8 +181,14 @@ fn read_file(path: impl AsRef, name: &str) -> AnyhowResult { #[cfg(test)] mod tests { - use crate::read_pem; + use crate::{read_pem, CliOpts}; use bip39::{Language, Mnemonic}; + use clap::CommandFactory; + + #[test] + fn check_cli() { + CliOpts::command().debug_assert() + } #[test] fn test_read_pem_none_none() { diff --git a/tests/output-tests/ckbtc.rs b/tests/output/ckbtc.rs similarity index 81% rename from tests/output-tests/ckbtc.rs rename to tests/output/ckbtc.rs index d3cb7112..9d0cdaaa 100644 --- a/tests/output-tests/ckbtc.rs +++ b/tests/output/ckbtc.rs @@ -1,4 +1,9 @@ -use crate::{quill_authed, quill_query, quill_query_authed, quill_send, OutputExt, PRINCIPAL}; +use crate::{ + ledger_compatible, quill_authed, quill_query, quill_query_authed, quill_send, OutputExt, + PRINCIPAL, +}; + +ledger_compatible![balance, withdrawal_address, transfer]; #[test] fn balance() { @@ -19,8 +24,7 @@ fn retrieve_btc() { #[test] fn withdrawal_address() { - quill_authed("ckbtc withdrawal-address") - .diff_s(b"mqygn-kiaaa-aaaar-qaadq-cai-xvrx2hq.2b1ce8130386fc895735f19538973c109bf1de45749657b1039fefd4fd6bd50a"); + quill_authed("ckbtc withdrawal-address").diff("ckbtc/withdrawal_address.txt"); } #[test] diff --git a/tests/output-tests/outputs/account_balance/authed.txt b/tests/output/default/account_balance/authed.txt similarity index 100% rename from tests/output-tests/outputs/account_balance/authed.txt rename to tests/output/default/account_balance/authed.txt diff --git a/tests/output-tests/outputs/account_balance/icrc1.txt b/tests/output/default/account_balance/icrc1.txt similarity index 100% rename from tests/output-tests/outputs/account_balance/icrc1.txt rename to tests/output/default/account_balance/icrc1.txt diff --git a/tests/output-tests/outputs/account_balance/simple.txt b/tests/output/default/account_balance/simple.txt similarity index 100% rename from tests/output-tests/outputs/account_balance/simple.txt rename to tests/output/default/account_balance/simple.txt diff --git a/tests/output-tests/outputs/ckbtc/balance/authed.txt b/tests/output/default/ckbtc/balance/authed.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/balance/authed.txt rename to tests/output/default/ckbtc/balance/authed.txt diff --git a/tests/output-tests/outputs/ckbtc/balance/testnet.txt b/tests/output/default/ckbtc/balance/testnet.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/balance/testnet.txt rename to tests/output/default/ckbtc/balance/testnet.txt diff --git a/tests/output-tests/outputs/ckbtc/retrieve_btc/already_transferred.txt b/tests/output/default/ckbtc/retrieve_btc/already_transferred.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/retrieve_btc/already_transferred.txt rename to tests/output/default/ckbtc/retrieve_btc/already_transferred.txt diff --git a/tests/output-tests/outputs/ckbtc/retrieve_btc/simple.txt b/tests/output/default/ckbtc/retrieve_btc/simple.txt similarity index 93% rename from tests/output-tests/outputs/ckbtc/retrieve_btc/simple.txt rename to tests/output/default/ckbtc/retrieve_btc/simple.txt index 99fc06e5..3c05adb6 100644 --- a/tests/output-tests/outputs/ckbtc/retrieve_btc/simple.txt +++ b/tests/output/default/ckbtc/retrieve_btc/simple.txt @@ -13,7 +13,7 @@ Sending message with fee = null; memo = opt blob "\00\00\00\00\00\00\00\09"; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 314_000_000 : nat; }, ) diff --git a/tests/output-tests/outputs/ckbtc/retrieve_btc/status.txt b/tests/output/default/ckbtc/retrieve_btc/status.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/retrieve_btc/status.txt rename to tests/output/default/ckbtc/retrieve_btc/status.txt diff --git a/tests/output-tests/outputs/ckbtc/transfer/simple.txt b/tests/output/default/ckbtc/transfer/simple.txt similarity index 88% rename from tests/output-tests/outputs/ckbtc/transfer/simple.txt rename to tests/output/default/ckbtc/transfer/simple.txt index 9d8f8c2c..2570b908 100644 --- a/tests/output-tests/outputs/ckbtc/transfer/simple.txt +++ b/tests/output/default/ckbtc/transfer/simple.txt @@ -13,7 +13,7 @@ Sending message with fee = null; memo = opt blob "\00\00\00\00\00\00\00\03"; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 314_000_000 : nat; }, ) diff --git a/tests/output-tests/outputs/ckbtc/update_balance/simple.txt b/tests/output/default/ckbtc/update_balance/simple.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/update_balance/simple.txt rename to tests/output/default/ckbtc/update_balance/simple.txt diff --git a/tests/output-tests/outputs/ckbtc/update_balance/testnet.txt b/tests/output/default/ckbtc/update_balance/testnet.txt similarity index 100% rename from tests/output-tests/outputs/ckbtc/update_balance/testnet.txt rename to tests/output/default/ckbtc/update_balance/testnet.txt diff --git a/tests/output/default/ckbtc/withdrawal_address.txt b/tests/output/default/ckbtc/withdrawal_address.txt new file mode 100644 index 00000000..66a6ff9b --- /dev/null +++ b/tests/output/default/ckbtc/withdrawal_address.txt @@ -0,0 +1 @@ +mqygn-kiaaa-aaaar-qaadq-cai-xvrx2hq.2b1ce8130386fc895735f19538973c109bf1de45749657b1039fefd4fd6bd50a diff --git a/tests/output-tests/outputs/claim_neurons/simple.txt b/tests/output/default/claim_neurons/simple.txt similarity index 100% rename from tests/output-tests/outputs/claim_neurons/simple.txt rename to tests/output/default/claim_neurons/simple.txt diff --git a/tests/output-tests/outputs/get_neuron_info/simple.txt b/tests/output/default/get_neuron_info/simple.txt similarity index 100% rename from tests/output-tests/outputs/get_neuron_info/simple.txt rename to tests/output/default/get_neuron_info/simple.txt diff --git a/tests/output-tests/outputs/get_proposal_info/simple.txt b/tests/output/default/get_proposal_info/simple.txt similarity index 100% rename from tests/output-tests/outputs/get_proposal_info/simple.txt rename to tests/output/default/get_proposal_info/simple.txt diff --git a/tests/output/default/ledger_incompatible/by_command.txt b/tests/output/default/ledger_incompatible/by_command.txt new file mode 100644 index 00000000..95684fb3 --- /dev/null +++ b/tests/output/default/ledger_incompatible/by_command.txt @@ -0,0 +1 @@ +Error: Cannot use `--ledger` with this command. This version of Quill does not support staking new neurons with a Ledger device diff --git a/tests/output/default/ledger_incompatible/by_flag.txt b/tests/output/default/ledger_incompatible/by_flag.txt new file mode 100644 index 00000000..792c63aa --- /dev/null +++ b/tests/output/default/ledger_incompatible/by_flag.txt @@ -0,0 +1,2 @@ +Error: Cannot use --ledger with these flags. This version of quill only supports the following neuron-manage operations with a Ledger device: +--additional-dissolve-delay-seconds, --start-dissolving, --stop-dissolving, --split, --merge-from-neuron, --spawn, --stake-maturity, --auto-stake-maturity diff --git a/tests/output/default/ledger_incompatible/by_function.txt b/tests/output/default/ledger_incompatible/by_function.txt new file mode 100644 index 00000000..9fa3d60d --- /dev/null +++ b/tests/output/default/ledger_incompatible/by_function.txt @@ -0,0 +1 @@ +Error: Cannot use --ledger with this command. This version of Quill only supports transfers and certain neuron management operations with a Ledger device diff --git a/tests/output-tests/outputs/list_neurons/many.txt b/tests/output/default/list_neurons/many.txt similarity index 100% rename from tests/output-tests/outputs/list_neurons/many.txt rename to tests/output/default/list_neurons/many.txt diff --git a/tests/output-tests/outputs/list_neurons/simple.txt b/tests/output/default/list_neurons/simple.txt similarity index 100% rename from tests/output-tests/outputs/list_neurons/simple.txt rename to tests/output/default/list_neurons/simple.txt diff --git a/tests/output-tests/outputs/list_proposals/simple.txt b/tests/output/default/list_proposals/simple.txt similarity index 100% rename from tests/output-tests/outputs/list_proposals/simple.txt rename to tests/output/default/list_proposals/simple.txt diff --git a/tests/output-tests/outputs/neuron_manage/add_hot_key.txt b/tests/output/default/neuron_manage/add_hot_key.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/add_hot_key.txt rename to tests/output/default/neuron_manage/add_hot_key.txt diff --git a/tests/output-tests/outputs/neuron_manage/additional_dissolve_delay_seconds.txt b/tests/output/default/neuron_manage/additional_dissolve_delay_seconds.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/additional_dissolve_delay_seconds.txt rename to tests/output/default/neuron_manage/additional_dissolve_delay_seconds.txt diff --git a/tests/output-tests/outputs/neuron_manage/auto_stake_disable.txt b/tests/output/default/neuron_manage/auto_stake_disable.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/auto_stake_disable.txt rename to tests/output/default/neuron_manage/auto_stake_disable.txt diff --git a/tests/output-tests/outputs/neuron_manage/auto_stake_enable.txt b/tests/output/default/neuron_manage/auto_stake_enable.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/auto_stake_enable.txt rename to tests/output/default/neuron_manage/auto_stake_enable.txt diff --git a/tests/output-tests/outputs/neuron_manage/clear.txt b/tests/output/default/neuron_manage/clear.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/clear.txt rename to tests/output/default/neuron_manage/clear.txt diff --git a/tests/output-tests/outputs/neuron_manage/disburse_somewhat_to_someone.txt b/tests/output/default/neuron_manage/disburse_somewhat_to_someone.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/disburse_somewhat_to_someone.txt rename to tests/output/default/neuron_manage/disburse_somewhat_to_someone.txt diff --git a/tests/output-tests/outputs/neuron_manage/disburse_stop_dissolving.txt b/tests/output/default/neuron_manage/disburse_stop_dissolving.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/disburse_stop_dissolving.txt rename to tests/output/default/neuron_manage/disburse_stop_dissolving.txt diff --git a/tests/output-tests/outputs/neuron_manage/follow.txt b/tests/output/default/neuron_manage/follow.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/follow.txt rename to tests/output/default/neuron_manage/follow.txt diff --git a/tests/output-tests/outputs/neuron_manage/join_community_fund.txt b/tests/output/default/neuron_manage/join_community_fund.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/join_community_fund.txt rename to tests/output/default/neuron_manage/join_community_fund.txt diff --git a/tests/output-tests/outputs/neuron_manage/leave_community_fund.txt b/tests/output/default/neuron_manage/leave_community_fund.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/leave_community_fund.txt rename to tests/output/default/neuron_manage/leave_community_fund.txt diff --git a/tests/output-tests/outputs/neuron_manage/merge.txt b/tests/output/default/neuron_manage/merge.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/merge.txt rename to tests/output/default/neuron_manage/merge.txt diff --git a/tests/output-tests/outputs/neuron_manage/merge_maturity.txt b/tests/output/default/neuron_manage/merge_maturity.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/merge_maturity.txt rename to tests/output/default/neuron_manage/merge_maturity.txt diff --git a/tests/output-tests/outputs/neuron_manage/remove_hot_key_and_dissolve.txt b/tests/output/default/neuron_manage/remove_hot_key_and_dissolve.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/remove_hot_key_and_dissolve.txt rename to tests/output/default/neuron_manage/remove_hot_key_and_dissolve.txt diff --git a/tests/output-tests/outputs/neuron_manage/spawn.txt b/tests/output/default/neuron_manage/spawn.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/spawn.txt rename to tests/output/default/neuron_manage/spawn.txt diff --git a/tests/output-tests/outputs/neuron_manage/split.txt b/tests/output/default/neuron_manage/split.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/split.txt rename to tests/output/default/neuron_manage/split.txt diff --git a/tests/output-tests/outputs/neuron_manage/stake_maturity.txt b/tests/output/default/neuron_manage/stake_maturity.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/stake_maturity.txt rename to tests/output/default/neuron_manage/stake_maturity.txt diff --git a/tests/output/default/neuron_manage/start_dissolving.txt b/tests/output/default/neuron_manage/start_dissolving.txt new file mode 100644 index 00000000..4e5cee0a --- /dev/null +++ b/tests/output/default/neuron_manage/start_dissolving.txt @@ -0,0 +1,17 @@ +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 { StartDissolving = record {} }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/default/neuron_manage/stop_dissolving.txt b/tests/output/default/neuron_manage/stop_dissolving.txt new file mode 100644 index 00000000..fa1c20b1 --- /dev/null +++ b/tests/output/default/neuron_manage/stop_dissolving.txt @@ -0,0 +1,17 @@ +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; + }, +) diff --git a/tests/output-tests/outputs/neuron_manage/vote.txt b/tests/output/default/neuron_manage/vote.txt similarity index 100% rename from tests/output-tests/outputs/neuron_manage/vote.txt rename to tests/output/default/neuron_manage/vote.txt diff --git a/tests/output-tests/outputs/neuron_stake/stake_only.txt b/tests/output/default/neuron_stake/stake_only.txt similarity index 100% rename from tests/output-tests/outputs/neuron_stake/stake_only.txt rename to tests/output/default/neuron_stake/stake_only.txt diff --git a/tests/output-tests/outputs/neuron_stake/with_name.txt b/tests/output/default/neuron_stake/with_name.txt similarity index 89% rename from tests/output-tests/outputs/neuron_stake/with_name.txt rename to tests/output/default/neuron_stake/with_name.txt index 65e27aed..eac5c4af 100644 --- a/tests/output-tests/outputs/neuron_stake/with_name.txt +++ b/tests/output/default/neuron_stake/with_name.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 10_000 : nat64 }; memo = 7_888_422_419_985_231_726 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 1_200_000_000 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/neuron_stake/with_nonce.txt b/tests/output/default/neuron_stake/with_nonce.txt similarity index 90% rename from tests/output-tests/outputs/neuron_stake/with_nonce.txt rename to tests/output/default/neuron_stake/with_nonce.txt index 9bd3e689..4abc5649 100644 --- a/tests/output-tests/outputs/neuron_stake/with_nonce.txt +++ b/tests/output/default/neuron_stake/with_nonce.txt @@ -10,7 +10,9 @@ Sending message with 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 = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 1_200_000_000 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/node_provider/replace.txt b/tests/output/default/node_provider/replace.txt similarity index 100% rename from tests/output-tests/outputs/node_provider/replace.txt rename to tests/output/default/node_provider/replace.txt diff --git a/tests/output-tests/outputs/node_provider/update.txt b/tests/output/default/node_provider/update.txt similarity index 100% rename from tests/output-tests/outputs/node_provider/update.txt rename to tests/output/default/node_provider/update.txt diff --git a/tests/output-tests/outputs/sns/balance/simple.txt b/tests/output/default/sns/balance/simple.txt similarity index 100% rename from tests/output-tests/outputs/sns/balance/simple.txt rename to tests/output/default/sns/balance/simple.txt diff --git a/tests/output-tests/outputs/sns/deployed.txt b/tests/output/default/sns/deployed.txt similarity index 100% rename from tests/output-tests/outputs/sns/deployed.txt rename to tests/output/default/sns/deployed.txt diff --git a/tests/output-tests/outputs/sns/disburse/simple.txt b/tests/output/default/sns/disburse/simple.txt similarity index 100% rename from tests/output-tests/outputs/sns/disburse/simple.txt rename to tests/output/default/sns/disburse/simple.txt diff --git a/tests/output-tests/outputs/sns/disburse/subaccount.txt b/tests/output/default/sns/disburse/subaccount.txt similarity index 100% rename from tests/output-tests/outputs/sns/disburse/subaccount.txt rename to tests/output/default/sns/disburse/subaccount.txt diff --git a/tests/output-tests/outputs/sns/dissolve_delay/add_seconds.txt b/tests/output/default/sns/dissolve_delay/add_seconds.txt similarity index 100% rename from tests/output-tests/outputs/sns/dissolve_delay/add_seconds.txt rename to tests/output/default/sns/dissolve_delay/add_seconds.txt diff --git a/tests/output-tests/outputs/sns/dissolve_delay/start_dissolving.txt b/tests/output/default/sns/dissolve_delay/start_dissolving.txt similarity index 100% rename from tests/output-tests/outputs/sns/dissolve_delay/start_dissolving.txt rename to tests/output/default/sns/dissolve_delay/start_dissolving.txt diff --git a/tests/output-tests/outputs/sns/dissolve_delay/stop_dissolving.txt b/tests/output/default/sns/dissolve_delay/stop_dissolving.txt similarity index 100% rename from tests/output-tests/outputs/sns/dissolve_delay/stop_dissolving.txt rename to tests/output/default/sns/dissolve_delay/stop_dissolving.txt diff --git a/tests/output-tests/outputs/sns/follow/follow.txt b/tests/output/default/sns/follow/follow.txt similarity index 100% rename from tests/output-tests/outputs/sns/follow/follow.txt rename to tests/output/default/sns/follow/follow.txt diff --git a/tests/output-tests/outputs/sns/follow/unfollow.txt b/tests/output/default/sns/follow/unfollow.txt similarity index 100% rename from tests/output-tests/outputs/sns/follow/unfollow.txt rename to tests/output/default/sns/follow/unfollow.txt diff --git a/tests/output-tests/outputs/sns/make_proposal/from_file.txt b/tests/output/default/sns/make_proposal/from_file.txt similarity index 100% rename from tests/output-tests/outputs/sns/make_proposal/from_file.txt rename to tests/output/default/sns/make_proposal/from_file.txt diff --git a/tests/output-tests/outputs/sns/make_proposal/simple.txt b/tests/output/default/sns/make_proposal/simple.txt similarity index 100% rename from tests/output-tests/outputs/sns/make_proposal/simple.txt rename to tests/output/default/sns/make_proposal/simple.txt diff --git a/tests/output-tests/outputs/sns/make_proposal/upgrade.txt b/tests/output/default/sns/make_proposal/upgrade.txt similarity index 100% rename from tests/output-tests/outputs/sns/make_proposal/upgrade.txt rename to tests/output/default/sns/make_proposal/upgrade.txt diff --git a/tests/output-tests/outputs/sns/make_proposal/upgrade_arg.txt b/tests/output/default/sns/make_proposal/upgrade_arg.txt similarity index 100% rename from tests/output-tests/outputs/sns/make_proposal/upgrade_arg.txt rename to tests/output/default/sns/make_proposal/upgrade_arg.txt diff --git a/tests/output-tests/outputs/sns/make_proposal/upgrade_summary_path.txt b/tests/output/default/sns/make_proposal/upgrade_summary_path.txt similarity index 100% rename from tests/output-tests/outputs/sns/make_proposal/upgrade_summary_path.txt rename to tests/output/default/sns/make_proposal/upgrade_summary_path.txt diff --git a/tests/output-tests/outputs/sns/manage_neuron/disburse.txt b/tests/output/default/sns/manage_neuron/disburse.txt similarity index 100% rename from tests/output-tests/outputs/sns/manage_neuron/disburse.txt rename to tests/output/default/sns/manage_neuron/disburse.txt diff --git a/tests/output-tests/outputs/sns/manage_neuron/disburse_subaccount.txt b/tests/output/default/sns/manage_neuron/disburse_subaccount.txt similarity index 100% rename from tests/output-tests/outputs/sns/manage_neuron/disburse_subaccount.txt rename to tests/output/default/sns/manage_neuron/disburse_subaccount.txt diff --git a/tests/output-tests/outputs/sns/manage_neuron/split.txt b/tests/output/default/sns/manage_neuron/split.txt similarity index 100% rename from tests/output-tests/outputs/sns/manage_neuron/split.txt rename to tests/output/default/sns/manage_neuron/split.txt diff --git a/tests/output-tests/outputs/sns/manage_neuron/stake_maturity.txt b/tests/output/default/sns/manage_neuron/stake_maturity.txt similarity index 100% rename from tests/output-tests/outputs/sns/manage_neuron/stake_maturity.txt rename to tests/output/default/sns/manage_neuron/stake_maturity.txt diff --git a/tests/output/default/sns/neuron_id/memo0.txt b/tests/output/default/sns/neuron_id/memo0.txt new file mode 100644 index 00000000..75522f61 --- /dev/null +++ b/tests/output/default/sns/neuron_id/memo0.txt @@ -0,0 +1 @@ +SNS Neuron Id: eaca1f294035a93c9d900cad9af8d7d5735f752b72f83cf1aed1ee3266545226 diff --git a/tests/output-tests/outputs/sns/neuron_permission/add.txt b/tests/output/default/sns/neuron_permission/add.txt similarity index 100% rename from tests/output-tests/outputs/sns/neuron_permission/add.txt rename to tests/output/default/sns/neuron_permission/add.txt diff --git a/tests/output-tests/outputs/sns/neuron_permission/remove.txt b/tests/output/default/sns/neuron_permission/remove.txt similarity index 100% rename from tests/output-tests/outputs/sns/neuron_permission/remove.txt rename to tests/output/default/sns/neuron_permission/remove.txt diff --git a/tests/output-tests/outputs/sns/stake_neuron/memo.txt b/tests/output/default/sns/stake_neuron/memo.txt similarity index 94% rename from tests/output-tests/outputs/sns/stake_neuron/memo.txt rename to tests/output/default/sns/stake_neuron/memo.txt index 29cd3f49..448f18ef 100644 --- a/tests/output-tests/outputs/sns/stake_neuron/memo.txt +++ b/tests/output/default/sns/stake_neuron/memo.txt @@ -13,7 +13,7 @@ Sending message with fee = null; memo = opt blob "\00\00\00\00\00\00\03\09"; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 1_200_000_000 : nat; }, ) diff --git a/tests/output-tests/outputs/sns/stake_neuron/no_amount.txt b/tests/output/default/sns/stake_neuron/no_amount.txt similarity index 100% rename from tests/output-tests/outputs/sns/stake_neuron/no_amount.txt rename to tests/output/default/sns/stake_neuron/no_amount.txt diff --git a/tests/output-tests/outputs/sns/status.txt b/tests/output/default/sns/status.txt similarity index 100% rename from tests/output-tests/outputs/sns/status.txt rename to tests/output/default/sns/status.txt diff --git a/tests/output-tests/outputs/sns/swap/new_ticket.txt b/tests/output/default/sns/swap/new_ticket.txt similarity index 100% rename from tests/output-tests/outputs/sns/swap/new_ticket.txt rename to tests/output/default/sns/swap/new_ticket.txt diff --git a/tests/output-tests/outputs/sns/swap/participation.txt b/tests/output/default/sns/swap/participation.txt similarity index 100% rename from tests/output-tests/outputs/sns/swap/participation.txt rename to tests/output/default/sns/swap/participation.txt diff --git a/tests/output-tests/outputs/sns/swap/pay.txt b/tests/output/default/sns/swap/pay.txt similarity index 100% rename from tests/output-tests/outputs/sns/swap/pay.txt rename to tests/output/default/sns/swap/pay.txt diff --git a/tests/output-tests/outputs/sns/swap/pay_with_confirmation_text.txt b/tests/output/default/sns/swap/pay_with_confirmation_text.txt similarity index 100% rename from tests/output-tests/outputs/sns/swap/pay_with_confirmation_text.txt rename to tests/output/default/sns/swap/pay_with_confirmation_text.txt diff --git a/tests/output-tests/outputs/sns/swap/refund.txt b/tests/output/default/sns/swap/refund.txt similarity index 100% rename from tests/output-tests/outputs/sns/swap/refund.txt rename to tests/output/default/sns/swap/refund.txt diff --git a/tests/output-tests/outputs/sns/transfer/fees_and_memo.txt b/tests/output/default/sns/transfer/fees_and_memo.txt similarity index 89% rename from tests/output-tests/outputs/sns/transfer/fees_and_memo.txt rename to tests/output/default/sns/transfer/fees_and_memo.txt index 51002406..61741672 100644 --- a/tests/output-tests/outputs/sns/transfer/fees_and_memo.txt +++ b/tests/output/default/sns/transfer/fees_and_memo.txt @@ -13,7 +13,7 @@ Sending message with fee = opt (230_000 : nat); memo = opt blob "\00\00\00\00\00\00\03\09"; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 12_304_560_000 : nat; }, ) diff --git a/tests/output-tests/outputs/sns/transfer/simple.txt b/tests/output/default/sns/transfer/simple.txt similarity index 87% rename from tests/output-tests/outputs/sns/transfer/simple.txt rename to tests/output/default/sns/transfer/simple.txt index 9cc126b1..21be9d98 100644 --- a/tests/output-tests/outputs/sns/transfer/simple.txt +++ b/tests/output/default/sns/transfer/simple.txt @@ -13,7 +13,7 @@ Sending message with fee = null; memo = null; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 12_300 : nat; }, ) diff --git a/tests/output-tests/outputs/sns/vote/no.txt b/tests/output/default/sns/vote/no.txt similarity index 100% rename from tests/output-tests/outputs/sns/vote/no.txt rename to tests/output/default/sns/vote/no.txt diff --git a/tests/output-tests/outputs/sns/vote/yes.txt b/tests/output/default/sns/vote/yes.txt similarity index 100% rename from tests/output-tests/outputs/sns/vote/yes.txt rename to tests/output/default/sns/vote/yes.txt diff --git a/tests/output-tests/outputs/transfer/e8s-2.txt b/tests/output/default/transfer/e8s-2.txt similarity index 84% rename from tests/output-tests/outputs/transfer/e8s-2.txt rename to tests/output/default/transfer/e8s-2.txt index f6be0881..bbad9989 100644 --- a/tests/output-tests/outputs/transfer/e8s-2.txt +++ b/tests/output/default/transfer/e8s-2.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 10_000 : nat64 }; memo = 0 : 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 = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 9 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/transfer/e8s.txt b/tests/output/default/transfer/e8s.txt similarity index 81% rename from tests/output-tests/outputs/transfer/e8s.txt rename to tests/output/default/transfer/e8s.txt index 8dfeb369..8408f6ad 100644 --- a/tests/output-tests/outputs/transfer/e8s.txt +++ b/tests/output/default/transfer/e8s.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 10_000 : nat64 }; memo = 0 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 12_345_600 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/transfer/icp-and-e8s.txt b/tests/output/default/transfer/icp-and-e8s.txt similarity index 81% rename from tests/output-tests/outputs/transfer/icp-and-e8s.txt rename to tests/output/default/transfer/icp-and-e8s.txt index 29f29a5a..945fc73d 100644 --- a/tests/output-tests/outputs/transfer/icp-and-e8s.txt +++ b/tests/output/default/transfer/icp-and-e8s.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 10_000 : nat64 }; memo = 0 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 123_456_000 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/transfer/icrc1.txt b/tests/output/default/transfer/icrc1.txt similarity index 90% rename from tests/output-tests/outputs/transfer/icrc1.txt rename to tests/output/default/transfer/icrc1.txt index b207b94f..d85d963f 100644 --- a/tests/output-tests/outputs/transfer/icrc1.txt +++ b/tests/output/default/transfer/icrc1.txt @@ -13,7 +13,7 @@ Sending message with fee = opt (10_000 : nat); memo = opt blob "\00\00\00\00\00\00\00\00"; from_subaccount = null; - created_at_time = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); amount = 1_200_000_000 : nat; }, ) diff --git a/tests/output-tests/outputs/transfer/simple.txt b/tests/output/default/transfer/simple.txt similarity index 81% rename from tests/output-tests/outputs/transfer/simple.txt rename to tests/output/default/transfer/simple.txt index 7984e105..2fd5671b 100644 --- a/tests/output-tests/outputs/transfer/simple.txt +++ b/tests/output/default/transfer/simple.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 10_000 : nat64 }; memo = 0 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 12_300 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/transfer/with-fees-and-memo.txt b/tests/output/default/transfer/with-fees-and-memo.txt similarity index 81% rename from tests/output-tests/outputs/transfer/with-fees-and-memo.txt rename to tests/output/default/transfer/with-fees-and-memo.txt index 5b7bd997..6e3826e4 100644 --- a/tests/output-tests/outputs/transfer/with-fees-and-memo.txt +++ b/tests/output/default/transfer/with-fees-and-memo.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 230_000 : nat64 }; memo = 777 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 12_304_560_000 : nat64 }; }, ) diff --git a/tests/output-tests/outputs/transfer/with-fees.txt b/tests/output/default/transfer/with-fees.txt similarity index 81% rename from tests/output-tests/outputs/transfer/with-fees.txt rename to tests/output/default/transfer/with-fees.txt index 6edbb695..3af196d2 100644 --- a/tests/output-tests/outputs/transfer/with-fees.txt +++ b/tests/output/default/transfer/with-fees.txt @@ -10,7 +10,9 @@ Sending message with fee = record { e8s = 230_000 : nat64 }; memo = 0 : nat64; from_subaccount = null; - created_at_time = null; + created_at_time = opt record { + timestamp_nanos = 1_669_073_904_187_044_208 : nat64; + }; amount = record { e8s = 12_304_560_000 : nat64 }; }, ) diff --git a/tests/output/ledger/account_balance/authed.txt b/tests/output/ledger/account_balance/authed.txt new file mode 100644 index 00000000..cc544adc --- /dev/null +++ b/tests/output/ledger/account_balance/authed.txt @@ -0,0 +1,11 @@ +Sending message with + + Call type: update + Sender: 2vxsx-fae + Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: account_balance_dfx + Arguments: ( + record { + account = "4f3d4b40cdb852732601fccf8bd24dffe44957a647cb867913e982d98cf85676"; + }, +) diff --git a/tests/output/ledger/account_balance/icrc1.txt b/tests/output/ledger/account_balance/icrc1.txt new file mode 100644 index 00000000..9bc90ddd --- /dev/null +++ b/tests/output/ledger/account_balance/icrc1.txt @@ -0,0 +1,12 @@ +Sending message with + + Call type: update + Sender: 2vxsx-fae + Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: icrc1_balance_of + Arguments: ( + record { + owner = principal "bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe"; + 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"; + }, +) diff --git a/tests/output/ledger/account_balance/simple.txt b/tests/output/ledger/account_balance/simple.txt new file mode 100644 index 00000000..f733e141 --- /dev/null +++ b/tests/output/ledger/account_balance/simple.txt @@ -0,0 +1,11 @@ +Sending message with + + Call type: update + Sender: 2vxsx-fae + Canister id: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: account_balance_dfx + Arguments: ( + record { + account = "ec0e2456fb9ff6c80f1d475b301d9b2ab873612f96e7fd74e7c0c0b2d58e6693"; + }, +) diff --git a/tests/output/ledger/ckbtc/balance/authed.txt b/tests/output/ledger/ckbtc/balance/authed.txt new file mode 100644 index 00000000..7999470d --- /dev/null +++ b/tests/output/ledger/ckbtc/balance/authed.txt @@ -0,0 +1,12 @@ +Sending message with + + Call type: update + Sender: 2vxsx-fae + Canister id: mxzaz-hqaaa-aaaar-qaada-cai + Method name: icrc1_balance_of + Arguments: ( + record { + owner = principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }, +) diff --git a/tests/output/ledger/ckbtc/balance/testnet.txt b/tests/output/ledger/ckbtc/balance/testnet.txt new file mode 100644 index 00000000..fae45ecc --- /dev/null +++ b/tests/output/ledger/ckbtc/balance/testnet.txt @@ -0,0 +1,12 @@ +Sending message with + + Call type: update + Sender: 2vxsx-fae + Canister id: mc6ru-gyaaa-aaaar-qaaaq-cai + Method name: icrc1_balance_of + Arguments: ( + record { + owner = principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }, +) diff --git a/tests/output/ledger/ckbtc/transfer/simple.txt b/tests/output/ledger/ckbtc/transfer/simple.txt new file mode 100644 index 00000000..1f1ca58a --- /dev/null +++ b/tests/output/ledger/ckbtc/transfer/simple.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: mxzaz-hqaaa-aaaar-qaada-cai + Method name: icrc1_transfer + Arguments: ( + record { + to = record { + owner = principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }; + fee = null; + memo = opt blob "\00\00\00\00\00\00\00\03"; + from_subaccount = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 314_000_000 : nat; + }, +) diff --git a/tests/output/ledger/ckbtc/withdrawal_address.txt b/tests/output/ledger/ckbtc/withdrawal_address.txt new file mode 100644 index 00000000..932b70f7 --- /dev/null +++ b/tests/output/ledger/ckbtc/withdrawal_address.txt @@ -0,0 +1 @@ +mqygn-kiaaa-aaaar-qaadq-cai-266zvna.29e52cc3d866d95676bc2ed352d1751884eb131e6680c1501a5df2ca4fd1d222 diff --git a/tests/output/ledger/claim_neurons/simple.txt b/tests/output/ledger/claim_neurons/simple.txt new file mode 100644 index 00000000..858c1ead --- /dev/null +++ b/tests/output/ledger/claim_neurons/simple.txt @@ -0,0 +1,9 @@ +Sending message with + + Call type: update + Sender: 5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe + Canister id: renrk-eyaaa-aaaaa-aaada-cai + Method name: claim_neurons + Arguments: ( + "0410d34980a51af89d3331ad5fa80fe30d8868ad87526460b3b3e15596ee58e812422987d8589ba61098264df5bb9c2d3ff6fe061746b4b31a44ec26636632b835", +) diff --git a/tests/output/ledger/list_neurons/many.txt b/tests/output/ledger/list_neurons/many.txt new file mode 100644 index 00000000..a2bbaeca --- /dev/null +++ b/tests/output/ledger/list_neurons/many.txt @@ -0,0 +1,12 @@ +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: list_neurons + Arguments: ( + record { + neuron_ids = vec { 123 : nat64; 456 : nat64; 789 : nat64 }; + include_neurons_readable_by_caller = false; + }, +) diff --git a/tests/output/ledger/list_neurons/simple.txt b/tests/output/ledger/list_neurons/simple.txt new file mode 100644 index 00000000..58eedaaa --- /dev/null +++ b/tests/output/ledger/list_neurons/simple.txt @@ -0,0 +1,7 @@ +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: list_neurons + Arguments: (record { neuron_ids = vec {}; include_neurons_readable_by_caller = true }) diff --git a/tests/output/ledger/neuron_manage/additional_dissolve_delay_seconds.txt b/tests/output/ledger/neuron_manage/additional_dissolve_delay_seconds.txt new file mode 100644 index 00000000..71148b7f --- /dev/null +++ b/tests/output/ledger/neuron_manage/additional_dissolve_delay_seconds.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 { + IncreaseDissolveDelay = record { + additional_dissolve_delay_seconds = 3_600 : nat32; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/auto_stake_disable.txt b/tests/output/ledger/neuron_manage/auto_stake_disable.txt new file mode 100644 index 00000000..48ebfc2c --- /dev/null +++ b/tests/output/ledger/neuron_manage/auto_stake_disable.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 { + ChangeAutoStakeMaturity = record { + requested_setting_for_auto_stake_maturity = false; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/auto_stake_enable.txt b/tests/output/ledger/neuron_manage/auto_stake_enable.txt new file mode 100644 index 00000000..4b00d62b --- /dev/null +++ b/tests/output/ledger/neuron_manage/auto_stake_enable.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 { + ChangeAutoStakeMaturity = record { + requested_setting_for_auto_stake_maturity = true; + } + }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/merge.txt b/tests/output/ledger/neuron_manage/merge.txt new file mode 100644 index 00000000..a9c0f698 --- /dev/null +++ b/tests/output/ledger/neuron_manage/merge.txt @@ -0,0 +1,17 @@ +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 { + Merge = record { + source_neuron_id = opt record { id = 380_519_530_470_538 : nat64 }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/merge_maturity.txt b/tests/output/ledger/neuron_manage/merge_maturity.txt new file mode 100644 index 00000000..6652fb18 --- /dev/null +++ b/tests/output/ledger/neuron_manage/merge_maturity.txt @@ -0,0 +1 @@ +Error: Merging maturity is no longer a supported option. See --stake-maturity. https://wiki.internetcomputer.org/wiki/NNS_neuron_operations_related_to_maturity diff --git a/tests/output/ledger/neuron_manage/spawn.txt b/tests/output/ledger/neuron_manage/spawn.txt new file mode 100644 index 00000000..06111d66 --- /dev/null +++ b/tests/output/ledger/neuron_manage/spawn.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 = null; + new_controller = null; + nonce = null; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/split.txt b/tests/output/ledger/neuron_manage/split.txt new file mode 100644 index 00000000..ee235423 --- /dev/null +++ b/tests/output/ledger/neuron_manage/split.txt @@ -0,0 +1,15 @@ +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 { + Split = record { amount_e8s = 10_000_000_000 : nat64 } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/stake_maturity.txt b/tests/output/ledger/neuron_manage/stake_maturity.txt new file mode 100644 index 00000000..50a3ea41 --- /dev/null +++ b/tests/output/ledger/neuron_manage/stake_maturity.txt @@ -0,0 +1,15 @@ +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 { + StakeMaturity = record { percentage_to_stake = opt (100 : nat32) } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/start_dissolving.txt b/tests/output/ledger/neuron_manage/start_dissolving.txt new file mode 100644 index 00000000..665c3ccf --- /dev/null +++ b/tests/output/ledger/neuron_manage/start_dissolving.txt @@ -0,0 +1,17 @@ +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 { StartDissolving = record {} }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/neuron_manage/stop_dissolving.txt b/tests/output/ledger/neuron_manage/stop_dissolving.txt new file mode 100644 index 00000000..d7e405ff --- /dev/null +++ b/tests/output/ledger/neuron_manage/stop_dissolving.txt @@ -0,0 +1,17 @@ +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 { StopDissolving = record {} }; + } + }; + neuron_id_or_subaccount = null; + }, +) diff --git a/tests/output/ledger/sns/disburse/simple.txt b/tests/output/ledger/sns/disburse/simple.txt new file mode 100644 index 00000000..55e9eb8f --- /dev/null +++ b/tests/output/ledger/sns/disburse/simple.txt @@ -0,0 +1,20 @@ +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 { + 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 { + Disburse = record { + to_account = opt record { + owner = opt principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }; + amount = opt record { e8s = 5_000_000_000 : nat64 }; + } + }; + }, +) diff --git a/tests/output/ledger/sns/disburse/subaccount.txt b/tests/output/ledger/sns/disburse/subaccount.txt new file mode 100644 index 00000000..0b7fb3cc --- /dev/null +++ b/tests/output/ledger/sns/disburse/subaccount.txt @@ -0,0 +1,22 @@ +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 { + 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 { + Disburse = record { + to_account = opt record { + owner = opt principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = opt record { + subaccount = 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\02"; + }; + }; + amount = null; + } + }; + }, +) diff --git a/tests/output/ledger/sns/dissolve_delay/start_dissolving.txt b/tests/output/ledger/sns/dissolve_delay/start_dissolving.txt new file mode 100644 index 00000000..4b0158e6 --- /dev/null +++ b/tests/output/ledger/sns/dissolve_delay/start_dissolving.txt @@ -0,0 +1,16 @@ +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 { + 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 { StartDissolving = record {} }; + } + }; + }, +) diff --git a/tests/output/ledger/sns/dissolve_delay/stop_dissolving.txt b/tests/output/ledger/sns/dissolve_delay/stop_dissolving.txt new file mode 100644 index 00000000..596240af --- /dev/null +++ b/tests/output/ledger/sns/dissolve_delay/stop_dissolving.txt @@ -0,0 +1,16 @@ +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 { + 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 { StopDissolving = record {} }; + } + }; + }, +) diff --git a/tests/output/ledger/sns/manage_neuron/stake_maturity.txt b/tests/output/ledger/sns/manage_neuron/stake_maturity.txt new file mode 100644 index 00000000..1ca80f12 --- /dev/null +++ b/tests/output/ledger/sns/manage_neuron/stake_maturity.txt @@ -0,0 +1,14 @@ +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 { + 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 { + StakeMaturity = record { percentage_to_stake = opt (70 : nat32) } + }; + }, +) diff --git a/tests/output/ledger/sns/neuron_id/memo0.txt b/tests/output/ledger/sns/neuron_id/memo0.txt new file mode 100644 index 00000000..5ebafbf2 --- /dev/null +++ b/tests/output/ledger/sns/neuron_id/memo0.txt @@ -0,0 +1 @@ +SNS Neuron Id: 1e3559a677aaab99ef996824c0c32150babfab37b89c57df9d368209eb0b6214 diff --git a/tests/output/ledger/sns/neuron_permission/add.txt b/tests/output/ledger/sns/neuron_permission/add.txt new file mode 100644 index 00000000..6e63eaad --- /dev/null +++ b/tests/output/ledger/sns/neuron_permission/add.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 { + 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 { + AddNeuronPermissions = record { + permissions_to_add = opt record { + permissions = vec { 3 : int32; 4 : int32 }; + }; + principal_id = opt principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + } + }; + }, +) diff --git a/tests/output/ledger/sns/neuron_permission/remove.txt b/tests/output/ledger/sns/neuron_permission/remove.txt new file mode 100644 index 00000000..a653f324 --- /dev/null +++ b/tests/output/ledger/sns/neuron_permission/remove.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 { + 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 { + RemoveNeuronPermissions = record { + permissions_to_remove = opt record { + permissions = vec { 7 : int32; 8 : int32 }; + }; + principal_id = opt principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + } + }; + }, +) diff --git a/tests/output/ledger/sns/transfer/fees_and_memo.txt b/tests/output/ledger/sns/transfer/fees_and_memo.txt new file mode 100644 index 00000000..c10e25f1 --- /dev/null +++ b/tests/output/ledger/sns/transfer/fees_and_memo.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: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: icrc1_transfer + Arguments: ( + record { + to = record { + owner = principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }; + fee = opt (230_000 : nat); + memo = opt blob "\00\00\00\00\00\00\03\09"; + from_subaccount = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 12_304_560_000 : nat; + }, +) diff --git a/tests/output/ledger/sns/transfer/simple.txt b/tests/output/ledger/sns/transfer/simple.txt new file mode 100644 index 00000000..76ed6ac8 --- /dev/null +++ b/tests/output/ledger/sns/transfer/simple.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: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: icrc1_transfer + Arguments: ( + record { + to = record { + owner = principal "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe"; + subaccount = null; + }; + fee = null; + memo = null; + from_subaccount = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 12_300 : nat; + }, +) diff --git a/tests/output/ledger/transfer/icrc1.txt b/tests/output/ledger/transfer/icrc1.txt new file mode 100644 index 00000000..67f98021 --- /dev/null +++ b/tests/output/ledger/transfer/icrc1.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: ryjl3-tyaaa-aaaaa-aaaba-cai + Method name: icrc1_transfer + Arguments: ( + record { + to = record { + owner = principal "bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe"; + 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"; + }; + fee = opt (10_000 : nat); + memo = opt blob "\00\00\00\00\00\00\00\00"; + from_subaccount = null; + created_at_time = opt (1_669_073_904_187_044_208 : nat64); + amount = 1_200_000_000 : nat; + }, +) diff --git a/tests/output-tests/main.rs b/tests/output/main.rs similarity index 63% rename from tests/output-tests/main.rs rename to tests/output/main.rs index 9e451a69..ed831122 100644 --- a/tests/output-tests/main.rs +++ b/tests/output/main.rs @@ -1,5 +1,9 @@ +use core::fmt; use std::{ - env, fs, + cell::RefCell, + env, + fmt::Display, + fs, path::Path, process::{Command, Output, Stdio}, }; @@ -9,7 +13,8 @@ mod neuron_manage; mod root; mod sns; -const PRINCIPAL: &str = "fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae"; +const PRINCIPAL: PrincipalPlaceholder = PrincipalPlaceholder; +const ACCOUNT_ID: AccountIdPlaceholder = AccountIdPlaceholder; const ALICE: &str = "pnf55-r7gzn-s3oqn-ah2v7-r6b63-a2ma2-wyzhb-dzbwb-sghid-lzcxh-4ae"; #[allow(unused)] const BOB: &str = "jndu2-vwnnt-bpu6t-2jrke-fg3kj-vbrgf-ajecf-gv6ju-onyol-wc3e5-kqe"; @@ -106,7 +111,7 @@ fn asset(asset: &str) -> String { } fn auth_args() -> Vec { - vec!["--pem-file".into(), default_pem().into()] + AUTH_SETTINGS.with(|auth| auth.borrow().args.clone()) } fn sns_args() -> Vec { @@ -117,6 +122,82 @@ fn escape_p(p: &impl AsRef) -> String { shellwords::escape(p.as_ref().to_str().unwrap()) } +#[macro_export] +macro_rules! ledger_compatible { + ($($name:ident),* $(,)?) => { + $( + #[cfg(feature = "ledger")] + mod $name { + #[test] + #[serial_test::serial(ledger)] + #[ignore = "requires a Ledger device"] + fn ledger() { + $crate::with_ledger(|| { + super::$name(); + }) + } + } + )* + } +} + +struct AuthSettings { + args: Vec, + principal: String, + account_id: String, + outputs_dir: String, +} + +impl Default for AuthSettings { + fn default() -> Self { + Self { + args: vec!["--pem-file".into(), default_pem().into()], + principal: "fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae".into(), + account_id: "345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752".into(), + outputs_dir: "default".into(), + } + } +} + +#[cfg(feature = "ledger")] +impl AuthSettings { + fn ledger() -> Self { + Self { + args: vec!["--ledger".into()], + principal: "5upke-tazvi-6ufqc-i3v6r-j4gpu-dpwti-obhal-yb5xj-ue32x-ktkql-rqe".into(), + account_id: "4f3d4b40cdb852732601fccf8bd24dffe44957a647cb867913e982d98cf85676".into(), + outputs_dir: "ledger".into(), + } + } +} + +struct PrincipalPlaceholder; + +impl Display for PrincipalPlaceholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + AUTH_SETTINGS.with(|auth| auth.borrow().principal.fmt(f)) + } +} + +struct AccountIdPlaceholder; + +impl Display for AccountIdPlaceholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + AUTH_SETTINGS.with(|auth| auth.borrow().account_id.fmt(f)) + } +} + +thread_local! { static AUTH_SETTINGS: RefCell = RefCell::default(); } + +#[cfg(feature = "ledger")] +fn with_ledger(f: impl FnOnce()) { + let _guard = scopeguard::guard((), |_| { + AUTH_SETTINGS.with(|auth| *auth.borrow_mut() = AuthSettings::default()) + }); + AUTH_SETTINGS.with(|auth| *auth.borrow_mut() = AuthSettings::ledger()); + f() +} + trait OutputExt { fn assert_success(&self); fn assert_err(&self); @@ -162,12 +243,16 @@ Stderr: } #[track_caller] fn diff(&self, output_file: &str) { - let output_file = format!( - "{}/tests/output-tests/outputs/{output_file}", - env!("CARGO_MANIFEST_DIR") - ); + let output_file = AUTH_SETTINGS.with(|auth| { + format!( + "{}/tests/output/{}/{output_file}", + env!("CARGO_MANIFEST_DIR"), + auth.borrow().outputs_dir, + ) + }); if env::var("FIX_OUTPUTS").is_ok() { self.assert_success(); + fs::create_dir_all(Path::new(&output_file).parent().unwrap()).unwrap(); fs::write(output_file, &self.stdout).unwrap(); } else { let output = std::fs::read(output_file).unwrap(); @@ -193,10 +278,13 @@ Generated output: #[track_caller] fn diff_err(&self, output_file: &str) { self.assert_err(); - let output_file = format!( - "{}/tests/output-tests/outputs/{output_file}", - env!("CARGO_MANIFEST_DIR") - ); + let output_file = AUTH_SETTINGS.with(|auth| { + format!( + "{}/tests/output/{}/{output_file}", + env!("CARGO_MANIFEST_DIR"), + auth.borrow().outputs_dir, + ) + }); if env::var("FIX_OUTPUTS").is_ok() { std::fs::write(output_file, &self.stderr).unwrap(); } else { diff --git a/tests/output-tests/neuron_manage.rs b/tests/output/neuron_manage.rs similarity index 81% rename from tests/output-tests/neuron_manage.rs rename to tests/output/neuron_manage.rs index 190427df..4eca2620 100644 --- a/tests/output-tests/neuron_manage.rs +++ b/tests/output/neuron_manage.rs @@ -1,7 +1,21 @@ -use crate::{quill_send, OutputExt, ALICE, PRINCIPAL}; +use crate::{ledger_compatible, quill_send, OutputExt, ALICE, PRINCIPAL}; const NEURON_ID: &str = "2313380519530470538"; +// uncomment tests on next ledger app update +ledger_compatible![ + // hot_key, + additional_dissolve_delay_seconds, + // disburse, + dissolve, + // follow, + // community_fund, + maturity, + merge, + split, + // vote +]; + #[test] fn hot_key() { quill_send(&format!( @@ -32,6 +46,14 @@ fn disburse() { .diff("neuron_manage/disburse_stop_dissolving.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"); +} + #[test] fn follow() { quill_send(&format!("neuron-manage {NEURON_ID} --follow-topic 0 --follow-neurons 380519530470538 380519530470539")) diff --git a/tests/output-tests/root.rs b/tests/output/root.rs similarity index 81% rename from tests/output-tests/root.rs rename to tests/output/root.rs index a63785a6..b80af1cb 100644 --- a/tests/output-tests/root.rs +++ b/tests/output/root.rs @@ -3,9 +3,20 @@ use std::io::Write; use tempfile::NamedTempFile; use crate::{ - escape_p, quill, quill_authed, quill_query, quill_query_authed, quill_send, OutputExt, + escape_p, ledger_compatible, quill, quill_authed, quill_query, quill_query_authed, quill_send, + OutputExt, ACCOUNT_ID, PRINCIPAL, }; +// Uncomment tests on next ledger app update +ledger_compatible![ + account_balance, + claim_neurons, + list_neurons, + // neuron_stake, + public_ids, + transfer_icrc1, +]; + #[test] fn account_balance() { quill_query("account-balance ec0e2456fb9ff6c80f1d475b301d9b2ab873612f96e7fd74e7c0c0b2d58e6693") @@ -74,9 +85,12 @@ Account id: ffc463646a2c92dce58d1179d26c64d4ccbaf1079a6edc5628cedc0d4b3b1866", #[test] fn public_ids() { quill_authed("public-ids").diff_s( - b"\ -Principal id: fdsgv-62ihb-nbiqv-xgic5-iefsv-3cscz-tmbzv-63qd5-vh43v-dqfrt-pae -Account id: 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752", + format!( + "\ +Principal id: {PRINCIPAL} +Account id: {ACCOUNT_ID}" + ) + .as_ref(), ); quill( "public-ids --principal-id 44mwt-bq3um-tqicz-bwhad-iipx4-6wzex-olvaj-z63bj-wkelv-xoua3-rqe", @@ -119,10 +133,25 @@ fn transfer() { .diff("transfer/e8s-2.txt"); quill_send("transfer 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --amount 1.23456") .diff("transfer/icp-and-e8s.txt"); - quill_send("transfer bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe-ce6fvoi.1 --amount 12") - .diff("transfer/icrc1.txt"); quill_send("transfer 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --amount 123.0456 --fee 0.0023") .diff("transfer/with-fees.txt"); quill_send("transfer 345f723e9e619934daac6ae0f4be13a7b0ba57d6a608e511a00fd0ded5866752 --amount 123.0456 --fee 0.0023 --memo 777") .diff("transfer/with-fees-and-memo.txt"); } + +#[test] +fn transfer_icrc1() { + quill_send("transfer bz3ru-7uwvd-5yubs-mc75n-pbtpy-rz4bh-detlt-qmrls-sprg2-g7vmz-mqe-ce6fvoi.1 --amount 12") + .diff("transfer/icrc1.txt"); +} + +#[test] +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") + .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-tests/sns.rs b/tests/output/sns.rs similarity index 91% rename from tests/output-tests/sns.rs rename to tests/output/sns.rs index 1e289aea..233de83d 100644 --- a/tests/output-tests/sns.rs +++ b/tests/output/sns.rs @@ -1,11 +1,25 @@ use crate::{ - asset, quill, quill_authed, quill_query, quill_sns_query, quill_sns_query_authed, - quill_sns_send, OutputExt, PRINCIPAL, + asset, ledger_compatible, quill, quill_authed, quill_query, quill_sns_query, + quill_sns_query_authed, quill_sns_send, OutputExt, PRINCIPAL, }; const NEURON_ID: &str = "83a7d2b12f654ff58335e5a2512ccae0d7839c744b1807a47c96f5b9f3969069"; const FOLLOWEE: &str = "75c606cac8d0dab0a6f5db99d64c9b5312ed8cca2f971ea0ea960926db530d7f"; +// uncomment tests on next ledger app update +ledger_compatible![ + // follow, + transfer, + neuron_id, + neuron_permission, + dissolve, + disburse, + // make_proposal, + // stake_neuron, + stake_maturity, + // vote, +]; + #[test] fn balance() { quill_sns_query(&format!("sns balance --of {PRINCIPAL}")).diff("sns/balance/simple.txt"); @@ -17,6 +31,10 @@ fn dissolve_delay() { "sns configure-dissolve-delay {NEURON_ID} --additional-dissolve-delay-seconds 1000" )) .diff("sns/dissolve_delay/add_seconds.txt"); +} + +#[test] +fn dissolve() { quill_sns_send(&format!( "sns configure-dissolve-delay {NEURON_ID} --start-dissolving" )) @@ -37,6 +55,12 @@ fn disburse() { .diff("sns/disburse/subaccount.txt"); } +#[test] +fn stake_maturity() { + quill_sns_send(&format!("sns stake-maturity {NEURON_ID} --percentage 70")) + .diff("sns/manage_neuron/stake_maturity.txt"); +} + #[test] fn manage_neuron() { quill_sns_send(&format!( @@ -47,8 +71,6 @@ fn manage_neuron() { "sns disburse-maturity {NEURON_ID} --subaccount 03" )) .diff("sns/manage_neuron/disburse_subaccount.txt"); - quill_sns_send(&format!("sns stake-maturity {NEURON_ID} --percentage 70")) - .diff("sns/manage_neuron/stake_maturity.txt"); quill_sns_send(&format!( "sns split-neuron {NEURON_ID} --memo 47 --amount 230.5" )) @@ -111,8 +133,7 @@ fn make_proposal() { #[test] fn neuron_id() { - quill_authed("sns neuron-id --memo 0") - .diff_s(b"SNS Neuron Id: eaca1f294035a93c9d900cad9af8d7d5735f752b72f83cf1aed1ee3266545226"); + quill_authed("sns neuron-id --memo 0").diff("sns/neuron_id/memo0.txt"); quill("sns neuron-id --memo 0 --principal-id 44mwt-bq3um-tqicz-bwhad-iipx4-6wzex-olvaj-z63bj-wkelv-xoua3-rqe") .diff_s(b"SNS Neuron Id: 785d80b7abaf0d01fdadcef37ecd93cef68db3be7b2d66687bd1d64954c56c55"); }