diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3f9d1d2e6b..36fe7e74789 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -211,6 +211,12 @@ jobs: popd - name: Test getting proposal payloads run: scripts/nns-dapp/test-proposal-payload + - name: Install tools + run: | + # libarchive-zip-perl is needed for crc32, used by test-cmc-notify. + sudo apt-get update -yy && sudo apt-get install -yy libarchive-zip-perl + - name: Test CMC notify fallback mechanism + run: scripts/nns-dapp/test-cmc-notify - name: Verify that arguments are set in index.html run: | for ((i=5; i>0; i--)); do diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ab62a2a9f4f..15bf0b81a94 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -247,12 +247,6 @@ jobs: uses: dfinity/setup-dfx@main - name: Test getting proposal args run: scripts/dfx-nns-proposal-args.test - docker-build-cli-flags: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: docker-build prints help - run: ./scripts/docker-build --help | grep -i usage minor-version-bump-works: runs-on: ubuntu-20.04 steps: @@ -284,77 +278,6 @@ jobs: exit 1 } >&2 # TODO: Verify that the commits contain the expected changes. - download-nns-dapp-ci-wasm: - strategy: - fail-fast: false - matrix: - os: [ubuntu-20.04, macos-13] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Install bash and sha256sum if on Mac - run: | - if command -v brew ; then - brew install bash - brew install coreutils - fi - - name: Download NNS-dapp CI wasm - run: | - MAIN_COMMIT="$(git ls-remote --refs https://github.com/dfinity/nns-dapp.git main | awk '{print $1}')" - scripts/nns-dapp/download-ci-wasm.test --commit "$MAIN_COMMIT" - env: - GH_TOKEN: ${{ github.token }} - canister-ids-tool: - name: Test canister_ids tool - strategy: - fail-fast: false - matrix: - os: [ubuntu-20.04, macos-13] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Install bash if on Mac - run: | - if command -v brew ; then - brew install bash - fi - - run: scripts/canister_ids.test - release-sop: - name: Test release-sop script - strategy: - fail-fast: false - matrix: - os: [ubuntu-20.04, macos-13] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Install bash if on Mac - run: | - if command -v brew ; then - brew install bash - fi - - run: scripts/nns-dapp/release-sop.test - split-changelog: - name: Test split-changelog script - strategy: - fail-fast: false - matrix: - os: [ubuntu-20.04, macos-13] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Install bash if on Mac - run: | - if command -v brew ; then - brew install bash - fi - - run: scripts/nns-dapp/split-changelog.test - unused-i18n: - name: Find unused i18n messages - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - run: scripts/unused-i18n version-match: name: The nns-dapp npm and cargo versions should match runs-on: ubuntu-20.04 @@ -370,12 +293,48 @@ jobs: echo "Frontend: $frontend_version" exit 1 } >&2 - network-config: - runs-on: ubuntu-20.04 + small-tests: + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, macos-13] + runs-on: ${{ matrix.os }} + env: + # Used by download-ci-wasm.test + GH_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v4 - - name: Install sponge - run: sudo apt-get update -yy && sudo apt-get install -yy moreutils && command -v sponge + - name: Install tools on Linux + run: | + if command -v apt-get; then + # libarchive-zip-perl is needed for crc32, used by convert-id.test + sudo apt-get update -yy && sudo apt-get install -yy moreutils libarchive-zip-perl && command -v sponge + fi + - name: Install tools on Mac + run: | + if command -v brew ; then + # coreutils is needed for + # base32, used by convert-id.test, and + # sha256sum, used download-ci-wasm.test + # moreutils is needed for sponge, used by network-config.test + brew install bash coreutils moreutils + fi + - name: docker-build prints help + run: ./scripts/docker-build --help | grep -i usage + - name: Download NNS-dapp CI wasm + run: | + MAIN_COMMIT="$(git ls-remote --refs https://github.com/dfinity/nns-dapp.git main | awk '{print $1}')" + scripts/nns-dapp/download-ci-wasm.test --commit "$MAIN_COMMIT" + - name: Test canister_ids tool + run: scripts/canister_ids.test + - name: Test release-sop script + run: scripts/nns-dapp/release-sop.test + - name: Test split-changelog script + run: scripts/nns-dapp/split-changelog.test + - name: Find unused i18n messages + run: scripts/unused-i18n + - name: Test the ID conversion script + run: scripts/convert-id.test - name: Getting network config works run: scripts/network-config.test checks-pass: @@ -390,15 +349,10 @@ jobs: - release-templating-works - config-check - asset-chunking-works - - docker-build-cli-flags - - download-nns-dapp-ci-wasm - - canister-ids-tool - - release-sop - - split-changelog - minor-version-bump-works - - version-match - - network-config - migration-test-utils-work + - version-match + - small-tests if: ${{ always() }} runs-on: ubuntu-20.04 steps: diff --git a/.github/workflows/update-aggregator.yml b/.github/workflows/update-aggregator.yml index 321be121a53..cdd29920521 100644 --- a/.github/workflows/update-aggregator.yml +++ b/.github/workflows/update-aggregator.yml @@ -58,7 +58,7 @@ jobs: # Note: If there were no changes, this step creates no PR. uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} commit-message: Update aggregator committer: GitHub author: gix-bot diff --git a/.github/workflows/update-didc.yml b/.github/workflows/update-didc.yml index 2a610a47a87..178aac00f25 100644 --- a/.github/workflows/update-didc.yml +++ b/.github/workflows/update-didc.yml @@ -44,7 +44,7 @@ jobs: if: ${{ steps.update.outputs.updated == '1' }} uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} base: main reviewers: mstrasinskis, dskloetd # Note: Please be careful when updating the add-paths field. We have had the snsdemo committed by accident, with a pattern that matches nothing seemingly committing everything. diff --git a/.github/workflows/update-ic-cargo-deps.yaml b/.github/workflows/update-ic-cargo-deps.yaml index e89c2698eac..d564e627a34 100644 --- a/.github/workflows/update-ic-cargo-deps.yaml +++ b/.github/workflows/update-ic-cargo-deps.yaml @@ -25,7 +25,7 @@ jobs: if: ${{ steps.update.outputs.updated == '1' }} uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} base: main reviewers: mstrasinskis, dskloetd, nns-team # Note: Please be careful when updating the add-paths field. We have had the snsdemo committed by accident, with a pattern that matches nothing seemingly committing everything. diff --git a/.github/workflows/update-proposals.yml b/.github/workflows/update-proposals.yml index db58cf6db65..ab03784b88d 100644 --- a/.github/workflows/update-proposals.yml +++ b/.github/workflows/update-proposals.yml @@ -64,7 +64,7 @@ jobs: # Note: If there were no changes, this step creates no PR. uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} commit-message: Update proposals committer: GitHub author: gix-bot diff --git a/.github/workflows/update-rust.yml b/.github/workflows/update-rust.yml index f46e6e8a57b..54348eed325 100644 --- a/.github/workflows/update-rust.yml +++ b/.github/workflows/update-rust.yml @@ -58,7 +58,7 @@ jobs: if: ${{ steps.update.outputs.updated == '1' }} uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} base: main reviewers: mstrasinskis, dskloetd # Note: Please be careful when updating the add-paths field. We have had the snsdemo committed by accident, with a pattern that matches nothing seemingly committing everything. diff --git a/.github/workflows/update-sns-aggregator-response.yml b/.github/workflows/update-sns-aggregator-response.yml index 00d778dd521..3b12bd6bd1d 100644 --- a/.github/workflows/update-sns-aggregator-response.yml +++ b/.github/workflows/update-sns-aggregator-response.yml @@ -23,7 +23,7 @@ jobs: # Note: If there were no changes, this step creates no PR. uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} commit-message: Update SNS aggregator response committer: GitHub author: gix-bot diff --git a/.github/workflows/update-snsdemo.yml b/.github/workflows/update-snsdemo.yml index f0c6b68376f..3f3b93a7970 100644 --- a/.github/workflows/update-snsdemo.yml +++ b/.github/workflows/update-snsdemo.yml @@ -58,7 +58,7 @@ jobs: if: ${{ steps.update.outputs.updated == '1' }} uses: peter-evans/create-pull-request@v4 with: - token: ${{ secrets.GIX_BOT_PAT }} + token: ${{ secrets.GIX_CREATE_PR_PAT }} commit-message: Update snsdemo to ${{ steps.update.outputs.release }} committer: GitHub author: gix-bot diff --git a/CHANGELOG-Nns-Dapp-unreleased.md b/CHANGELOG-Nns-Dapp-unreleased.md index f3c9021e883..4c48503b5ab 100644 --- a/CHANGELOG-Nns-Dapp-unreleased.md +++ b/CHANGELOG-Nns-Dapp-unreleased.md @@ -15,25 +15,24 @@ proposal is successful, the changes it released will be moved from this file to #### Added -* Make neurons table sortable on desktop and mobile. * A short delay before closing the mobile table sorting modal. #### Changed -* Change neuron ID column title to "Neurons". -* Excluded non-displayed empty neurons when loading neurons. -* Transactions to neuron accounts are now displayed as "Sent" instead of "Staked" or "Top-up neuron" if the neuron is no longer displayed because it's disbursed or merged. -* Change the color of the settings icon on the tokens table. +* Reduce the frequency of checking if SNS neurons need to be refreshed. #### Deprecated #### Removed -* Disable sorting the neurons table by neuron ID. +* Remove default topic and proposal status filters. +* Remove old canister creation/top-up mechanism that hasn't been used for 2 years. #### Fixed * Button disable state glitch when voting with neurons where one follows another. +* Fix "the current proposals response is too large" error on proposals page. +* Visibility of "Neuron Management" proposals in actionable list. #### Security @@ -43,6 +42,9 @@ proposal is successful, the changes it released will be moved from this file to #### Added +* Script to convert between ID formats +* Test cycles minting canister notification mechanism of the nns-dapp. + #### Changed #### Deprecated diff --git a/CHANGELOG-Nns-Dapp.md b/CHANGELOG-Nns-Dapp.md index e8927bc5619..bfef99e11f0 100644 --- a/CHANGELOG-Nns-Dapp.md +++ b/CHANGELOG-Nns-Dapp.md @@ -11,6 +11,27 @@ The NNS Dapp is released through proposals in the Network Nervous System. Theref Unreleased changes are added to `CHANGELOG-Nns-Dapp-unreleased.md` and moved here after a successful release. +## Proposal 130986 + +### Application + +#### Added + +* Make neurons table sortable on desktop and mobile. + +#### Changed + +* Change neuron ID column title to "Neurons". +* Excluded non-displayed empty neurons when loading neurons. +* Transactions to neuron accounts are now displayed as "Sent" instead of "Staked" or "Top-up neuron" if the neuron is no longer displayed because it's disbursed or merged. +* Change the color of the settings icon on the tokens table. + +#### Removed + +* Disable sorting the neurons table by neuron ID. + +### Operations + ## Proposal 130768 ### Application diff --git a/Cargo.lock b/Cargo.lock index 189505ca33f..189f40ffe6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,7 +984,7 @@ dependencies = [ [[package]] name = "cycles-minting-canister" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "base64 0.13.1", @@ -1119,7 +1119,7 @@ dependencies = [ [[package]] name = "dfn_candid" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "dfn_core", @@ -1131,7 +1131,7 @@ dependencies = [ [[package]] name = "dfn_core" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "on_wire", @@ -1140,7 +1140,7 @@ dependencies = [ [[package]] name = "dfn_http" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "dfn_candid", @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "dfn_http_metrics" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "dfn_candid", "dfn_core", @@ -1164,7 +1164,7 @@ dependencies = [ [[package]] name = "dfn_protobuf" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "on_wire", "prost", @@ -1372,7 +1372,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fe-derive" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "num-bigint-dig", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "ic-base-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "byte-unit", "bytes", @@ -1888,7 +1888,7 @@ dependencies = [ [[package]] name = "ic-btc-types-internal" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-btc-interface", @@ -1901,7 +1901,7 @@ dependencies = [ [[package]] name = "ic-canister-client-sender" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "ic-crypto-ecdsa-secp256k1", @@ -1914,7 +1914,7 @@ dependencies = [ [[package]] name = "ic-canister-log" version = "0.2.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "serde", ] @@ -1922,7 +1922,7 @@ dependencies = [ [[package]] name = "ic-canister-profiler" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-metrics-encoder", "ic0 0.18.11", @@ -1931,7 +1931,7 @@ dependencies = [ [[package]] name = "ic-canisters-http-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "serde", @@ -1953,12 +1953,12 @@ dependencies = [ [[package]] name = "ic-cdk" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff384f182459bec490a7f50a58bbdf8f7a6934de4dcc259e30c70641bcbcb917" +checksum = "0c44983d6e97135d86132111bae5b86c34c498d96b4f182638bebcc7ac07e03c" dependencies = [ "candid", - "ic-cdk-macros 0.14.0", + "ic-cdk-macros 0.15.0", "ic0 0.23.0", "serde", "serde_bytes", @@ -1994,9 +1994,9 @@ dependencies = [ [[package]] name = "ic-cdk-macros" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01dc6bc425ec048d6ac4137c7c0f2cfbd6f8b0be8efc568feae2b265f566117c" +checksum = "3af44fb4ec3a4b18831c9d3303ca8fa2ace846c4022d50cb8df4122635d3782e" dependencies = [ "candid", "proc-macro2", @@ -2022,12 +2022,12 @@ dependencies = [ [[package]] name = "ic-cdk-timers" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4a42e669e47cf58fc18e071898e67922c81aa480edbf930ed1468dd53f44ee" +checksum = "61fdca8e1d9ffb65ae68019b342c182009de9dc206fd849db0b0e90ee19f6fa4" dependencies = [ "futures", - "ic-cdk 0.14.0", + "ic-cdk 0.15.0", "ic0 0.23.0", "serde", "serde_bytes", @@ -2058,12 +2058,12 @@ dependencies = [ [[package]] name = "ic-constants" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-crypto-ecdsa-secp256k1" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "k256", "lazy_static", @@ -2077,7 +2077,7 @@ dependencies = [ [[package]] name = "ic-crypto-ed25519" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "curve25519-dalek", "ed25519-dalek", @@ -2091,7 +2091,7 @@ dependencies = [ [[package]] name = "ic-crypto-getrandom-for-wasm" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "getrandom", ] @@ -2099,7 +2099,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-basic-sig-der-utils" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "ic-types", @@ -2109,12 +2109,13 @@ dependencies = [ [[package]] name = "ic-crypto-internal-basic-sig-ed25519" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base64 0.13.1", "curve25519-dalek", "ed25519-consensus", "hex", + "ic-crypto-ed25519", "ic-crypto-internal-basic-sig-der-utils", "ic-crypto-internal-seed", "ic-crypto-internal-types", @@ -2131,7 +2132,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-bls12-381-type" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "ic_bls12_381", @@ -2149,7 +2150,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-hmac" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-crypto-internal-sha2", ] @@ -2157,7 +2158,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-multi-sig-bls12381" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base64 0.13.1", "hex", @@ -2176,7 +2177,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-seed" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "ic-crypto-sha2", @@ -2189,7 +2190,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-sha2" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "sha2 0.10.8", ] @@ -2197,7 +2198,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-threshold-sig-bls12381" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base64 0.13.1", "cached", @@ -2223,7 +2224,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-threshold-sig-ecdsa" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "curve25519-dalek", "fe-derive", @@ -2253,7 +2254,7 @@ dependencies = [ [[package]] name = "ic-crypto-internal-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "arrayvec 0.7.4", "hex", @@ -2270,7 +2271,7 @@ dependencies = [ [[package]] name = "ic-crypto-node-key-validation" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "ic-base-types", @@ -2288,7 +2289,7 @@ dependencies = [ [[package]] name = "ic-crypto-secrets-containers" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "serde", "zeroize", @@ -2297,7 +2298,7 @@ dependencies = [ [[package]] name = "ic-crypto-sha2" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-crypto-internal-sha2", ] @@ -2305,7 +2306,7 @@ dependencies = [ [[package]] name = "ic-crypto-tls-cert-validation" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "ic-crypto-internal-basic-sig-ed25519", @@ -2318,7 +2319,7 @@ dependencies = [ [[package]] name = "ic-crypto-tree-hash" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-crypto-internal-types", "ic-crypto-sha2", @@ -2331,7 +2332,7 @@ dependencies = [ [[package]] name = "ic-crypto-utils-basic-sig" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "ic-crypto-ed25519", @@ -2341,7 +2342,7 @@ dependencies = [ [[package]] name = "ic-crypto-utils-ni-dkg" version = "0.8.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-crypto-internal-types", "ic-protobuf", @@ -2351,7 +2352,7 @@ dependencies = [ [[package]] name = "ic-error-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-protobuf", "ic-utils", @@ -2363,7 +2364,7 @@ dependencies = [ [[package]] name = "ic-icrc1" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ciborium", @@ -2385,7 +2386,7 @@ dependencies = [ [[package]] name = "ic-icrc1-index-ng" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ciborium", @@ -2413,7 +2414,7 @@ dependencies = [ [[package]] name = "ic-icrc1-ledger" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2441,7 +2442,7 @@ dependencies = [ [[package]] name = "ic-icrc1-tokens-u64" version = "0.1.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-ledger-core", @@ -2453,7 +2454,7 @@ dependencies = [ [[package]] name = "ic-ledger-canister-core" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2471,7 +2472,7 @@ dependencies = [ [[package]] name = "ic-ledger-core" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-ledger-hash-of", @@ -2483,7 +2484,7 @@ dependencies = [ [[package]] name = "ic-ledger-hash-of" version = "0.1.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "hex", @@ -2493,7 +2494,7 @@ dependencies = [ [[package]] name = "ic-management-canister-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-base-types", @@ -2518,7 +2519,7 @@ checksum = "8b5c7628eac357aecda461130f8074468be5aa4d258a002032d82d817f79f1f8" [[package]] name = "ic-nervous-system-clients" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2541,12 +2542,12 @@ dependencies = [ [[package]] name = "ic-nervous-system-collections-union-multi-map" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-nervous-system-common" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "base64 0.13.1", @@ -2582,12 +2583,12 @@ dependencies = [ [[package]] name = "ic-nervous-system-common-build-metadata" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-nervous-system-common-test-keys" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "ic-canister-client-sender", @@ -2600,7 +2601,7 @@ dependencies = [ [[package]] name = "ic-nervous-system-governance" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "ic-stable-structures", @@ -2612,12 +2613,12 @@ dependencies = [ [[package]] name = "ic-nervous-system-lock" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-nervous-system-proto" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "comparable", @@ -2630,7 +2631,7 @@ dependencies = [ [[package]] name = "ic-nervous-system-proxied-canister-calls-tracker" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", ] @@ -2638,7 +2639,7 @@ dependencies = [ [[package]] name = "ic-nervous-system-root" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "dfn_core", @@ -2654,7 +2655,7 @@ dependencies = [ [[package]] name = "ic-nervous-system-runtime" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2667,12 +2668,12 @@ dependencies = [ [[package]] name = "ic-nervous-system-string" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-neurons-fund" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-nervous-system-common", "lazy_static", @@ -2685,7 +2686,7 @@ dependencies = [ [[package]] name = "ic-nns-common" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "comparable", @@ -2711,7 +2712,7 @@ dependencies = [ [[package]] name = "ic-nns-constants" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-base-types", "maplit", @@ -2720,7 +2721,7 @@ dependencies = [ [[package]] name = "ic-nns-governance" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "build-info", @@ -2783,12 +2784,12 @@ dependencies = [ [[package]] name = "ic-nns-gtc-accounts" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "ic-nns-handler-root-interface" version = "0.1.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2803,7 +2804,7 @@ dependencies = [ [[package]] name = "ic-protobuf" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "bincode", "candid", @@ -2817,7 +2818,7 @@ dependencies = [ [[package]] name = "ic-registry-keys" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-base-types", @@ -2829,7 +2830,7 @@ dependencies = [ [[package]] name = "ic-registry-routing-table" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-base-types", @@ -2840,7 +2841,7 @@ dependencies = [ [[package]] name = "ic-registry-subnet-features" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-management-canister-types", @@ -2851,7 +2852,7 @@ dependencies = [ [[package]] name = "ic-registry-subnet-type" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-protobuf", @@ -2863,7 +2864,7 @@ dependencies = [ [[package]] name = "ic-registry-transport" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "ic-base-types", @@ -2875,7 +2876,7 @@ dependencies = [ [[package]] name = "ic-sns-governance" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "base64 0.13.1", @@ -2932,7 +2933,7 @@ dependencies = [ [[package]] name = "ic-sns-governance-proposal-criticality" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-nervous-system-proto", ] @@ -2940,7 +2941,7 @@ dependencies = [ [[package]] name = "ic-sns-governance-proposals-amount-total-limit" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "ic-sns-governance-token-valuation", "num-traits", @@ -2951,7 +2952,7 @@ dependencies = [ [[package]] name = "ic-sns-governance-token-valuation" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -2972,7 +2973,7 @@ dependencies = [ [[package]] name = "ic-sns-init" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base64 0.13.1", "candid", @@ -2999,7 +3000,7 @@ dependencies = [ [[package]] name = "ic-sns-root" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "build-info", @@ -3028,7 +3029,7 @@ dependencies = [ [[package]] name = "ic-sns-swap" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "build-info", @@ -3066,7 +3067,7 @@ dependencies = [ [[package]] name = "ic-sns-swap-proto-library" version = "0.0.1" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "comparable", @@ -3081,7 +3082,7 @@ dependencies = [ [[package]] name = "ic-sns-wasm" version = "1.0.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -3128,7 +3129,7 @@ dependencies = [ [[package]] name = "ic-types" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base64 0.13.1", "bincode", @@ -3163,7 +3164,7 @@ dependencies = [ [[package]] name = "ic-utils" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "hex", "scoped_threadpool", @@ -3248,7 +3249,7 @@ dependencies = [ [[package]] name = "icp-ledger" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "comparable", @@ -3279,7 +3280,7 @@ dependencies = [ [[package]] name = "icrc-ledger-client" version = "0.1.2" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "async-trait", "candid", @@ -3290,7 +3291,7 @@ dependencies = [ [[package]] name = "icrc-ledger-types" version = "0.1.5" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "base32", "candid", @@ -3759,7 +3760,7 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nns-dapp" -version = "2.0.82" +version = "2.0.83" dependencies = [ "anyhow", "base64 0.22.1", @@ -3772,8 +3773,8 @@ dependencies = [ "futures", "hex", "ic-base-types", - "ic-cdk 0.14.0", - "ic-cdk-macros 0.14.0", + "ic-cdk 0.15.0", + "ic-cdk-macros 0.15.0", "ic-certified-map 0.3.4", "ic-crypto-sha2", "ic-ledger-core", @@ -3909,7 +3910,7 @@ dependencies = [ [[package]] name = "on_wire" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" [[package]] name = "once_cell" @@ -4073,7 +4074,7 @@ dependencies = [ [[package]] name = "phantom_newtype" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "candid", "serde", @@ -4308,7 +4309,7 @@ dependencies = [ [[package]] name = "proposals" -version = "2.0.82" +version = "2.0.83" dependencies = [ "anyhow", "candid", @@ -4319,8 +4320,8 @@ dependencies = [ "hex", "ic-base-types", "ic-btc-interface", - "ic-cdk 0.14.0", - "ic-cdk-macros 0.14.0", + "ic-cdk 0.15.0", + "ic-cdk-macros 0.15.0", "ic-crypto-sha2", "ic-management-canister-types", "ic-nervous-system-common", @@ -4620,7 +4621,7 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "registry-canister" version = "0.9.0" -source = "git+https://github.com/dfinity/ic?rev=release-2024-06-26_23-01-base#2e269c77aa2f6b2353ddad6a4ac3d5ddcac196b1" +source = "git+https://github.com/dfinity/ic?rev=release-2024-07-03_23-01-storage-layer-disabled#5849c6daf2037349bd36dcb6e26ce61c2c6570d0" dependencies = [ "build-info", "build-info-build", @@ -5030,9 +5031,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -5058,9 +5059,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -5080,9 +5081,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -5261,16 +5262,16 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "sns_aggregator" -version = "2.0.82" +version = "2.0.83" dependencies = [ "anyhow", "base64 0.22.1", "candid", "dfn_candid", "dfn_core", - "ic-cdk 0.14.0", - "ic-cdk-macros 0.14.0", - "ic-cdk-timers 0.8.0", + "ic-cdk 0.15.0", + "ic-cdk-macros 0.15.0", + "ic-cdk-timers 0.9.0", "ic-certified-map 0.3.2", "ic-management-canister-types", "ic-nervous-system-common", diff --git a/Cargo.toml b/Cargo.toml index e66bc04ecf3..5ceb3e1c057 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,29 +7,29 @@ members = [ resolver = "2" [workspace.package] -version = "2.0.82" +version = "2.0.83" [workspace.dependencies] -ic-cdk = "0.14.0" -ic-cdk-macros = "0.14.0" +ic-cdk = "0.15.0" +ic-cdk-macros = "0.15.0" -cycles-minting-canister = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -dfn_candid = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -dfn_core = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -dfn_protobuf = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-base-types = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-crypto-sha2 = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-management-canister-types = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-ledger-core = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-nervous-system-root = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-nns-common = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-nns-constants = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-nns-governance = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-protobuf = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -ic-sns-swap = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -icp-ledger = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } -on_wire = { git = "https://github.com/dfinity/ic", rev = "release-2024-06-26_23-01-base" } +cycles-minting-canister = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +dfn_candid = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +dfn_core = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +dfn_protobuf = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-base-types = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-crypto-sha2 = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-management-canister-types = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-ledger-core = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-nervous-system-common = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-nervous-system-root = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-nns-common = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-nns-constants = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-nns-governance = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-protobuf = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +ic-sns-swap = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +icp-ledger = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } +on_wire = { git = "https://github.com/dfinity/ic", rev = "release-2024-07-03_23-01-storage-layer-disabled" } [profile.release] lto = false diff --git a/declarations/used_by_proposals/nns_governance/nns_governance.did b/declarations/used_by_proposals/nns_governance/nns_governance.did index fd506eaa8b0..954a59e6ade 100644 --- a/declarations/used_by_proposals/nns_governance/nns_governance.did +++ b/declarations/used_by_proposals/nns_governance/nns_governance.did @@ -1,4 +1,4 @@ -//! Candid for canister `nns_governance` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `nns_governance` obtained by `scripts/update_ic_commit` from: type AccountIdentifier = record { hash : blob }; type Action = variant { RegisterKnownNeuron : KnownNeuron; @@ -159,7 +159,7 @@ type GlobalTimeOfDay = record { seconds_after_utc_midnight : opt nat64 }; type Governance = record { default_followees : vec record { int32; Followees }; making_sns_proposal : opt MakingSnsProposal; - most_recent_monthly_node_provider_rewards : opt MostRecentMonthlyNodeProviderRewards; + most_recent_monthly_node_provider_rewards : opt MonthlyNodeProviderRewards; maturity_modulation_last_updated_at_timestamp_seconds : opt nat64; wait_for_quiet_threshold_seconds : nat64; metrics : opt GovernanceCachedMetrics; @@ -217,6 +217,7 @@ type GovernanceCachedMetrics = record { }; dissolving_neurons_count_buckets : vec record { nat64; nat64 }; dissolving_neurons_e8s_buckets_ect : vec record { nat64; float64 }; + non_self_authenticating_controller_neuron_subset_metrics : opt NeuronSubsetMetrics; dissolving_neurons_count : nat64; dissolving_neurons_e8s_buckets : vec record { nat64; float64 }; total_staked_maturity_e8s_equivalent_seed : nat64; @@ -318,9 +319,14 @@ type Migrations = record { neuron_indexes_migration : opt Migration; copy_inactive_neurons_to_stable_memory_migration : opt Migration; }; -type MostRecentMonthlyNodeProviderRewards = record { +type MonthlyNodeProviderRewards = record { + minimum_xdr_permyriad_per_icp : opt nat64; + registry_version : opt nat64; + node_providers : vec NodeProvider; timestamp : nat64; rewards : vec RewardNodeProvider; + xdr_conversion_rate : opt XdrConversionRate; + maximum_node_provider_rewards_e8s : opt nat64; }; type Motion = record { motion_text : text }; type NetworkEconomics = record { @@ -400,6 +406,18 @@ type NeuronStakeTransfer = record { transfer_timestamp : nat64; block_height : nat64; }; +type NeuronSubsetMetrics = record { + total_maturity_e8s_equivalent : opt nat64; + maturity_e8s_equivalent_buckets : vec record { nat64; nat64 }; + voting_power_buckets : vec record { nat64; nat64 }; + total_staked_e8s : opt nat64; + count : opt nat64; + total_staked_maturity_e8s_equivalent : opt nat64; + staked_maturity_e8s_equivalent_buckets : vec record { nat64; nat64 }; + staked_e8s_buckets : vec record { nat64; nat64 }; + total_voting_power : opt nat64; + count_buckets : vec record { nat64; nat64 }; +}; type NeuronsFundAuditInfo = record { final_neurons_fund_participation : opt NeuronsFundParticipation; initial_neurons_fund_participation : opt NeuronsFundParticipation; @@ -675,7 +693,7 @@ service : (Governance) -> { get_metrics : () -> (Result_3) query; get_monthly_node_provider_rewards : () -> (Result_4); get_most_recent_monthly_node_provider_rewards : () -> ( - opt MostRecentMonthlyNodeProviderRewards, + opt MonthlyNodeProviderRewards, ) query; get_network_economics_parameters : () -> (NetworkEconomics) query; get_neuron_ids : () -> (vec nat64) query; diff --git a/declarations/used_by_proposals/nns_registry/nns_registry.did b/declarations/used_by_proposals/nns_registry/nns_registry.did index c98cfdb852d..0b2f2ff2c76 100644 --- a/declarations/used_by_proposals/nns_registry/nns_registry.did +++ b/declarations/used_by_proposals/nns_registry/nns_registry.did @@ -1,4 +1,4 @@ -//! Candid for canister `nns_registry` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `nns_registry` obtained by `scripts/update_ic_commit` from: // A brief note about the history of this file: This file used to be // automatically generated, but now, it is hand-crafted, because the // auto-generator has some some pretty degenerate behaviors. The worst of those @@ -232,6 +232,7 @@ type NodeOperatorRecord = record { type NodeProvidersMonthlyXdrRewards = record { rewards : vec record { text; nat64 }; + registry_version: opt nat64; }; type NodeRewardRate = record { diff --git a/declarations/used_by_proposals/sns_wasm/sns_wasm.did b/declarations/used_by_proposals/sns_wasm/sns_wasm.did index 67d3a09f8c6..c1da7a48958 100644 --- a/declarations/used_by_proposals/sns_wasm/sns_wasm.did +++ b/declarations/used_by_proposals/sns_wasm/sns_wasm.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: type AddWasmRequest = record { hash : blob; wasm : opt SnsWasm }; type AddWasmResponse = record { result : opt Result }; type AirdropDistribution = record { airdrop_neurons : vec NeuronDistribution }; diff --git a/declarations/used_by_sns_aggregator/sns_governance/sns_governance.did b/declarations/used_by_sns_aggregator/sns_governance/sns_governance.did index 85094d39469..8491398a6da 100644 --- a/declarations/used_by_sns_aggregator/sns_governance/sns_governance.did +++ b/declarations/used_by_sns_aggregator/sns_governance/sns_governance.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: type Account = record { owner : opt principal; subaccount : opt Subaccount }; type Action = variant { ManageNervousSystemParameters : NervousSystemParameters; diff --git a/declarations/used_by_sns_aggregator/sns_ledger/sns_ledger.did b/declarations/used_by_sns_aggregator/sns_ledger/sns_ledger.did index 7211acd07d4..7d8b0579988 100644 --- a/declarations/used_by_sns_aggregator/sns_ledger/sns_ledger.did +++ b/declarations/used_by_sns_aggregator/sns_ledger/sns_ledger.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: type BlockIndex = nat; type Subaccount = blob; // Number of nanoseconds since the UNIX epoch in UTC timezone. diff --git a/declarations/used_by_sns_aggregator/sns_root/sns_root.did b/declarations/used_by_sns_aggregator/sns_root/sns_root.did index db22bdff089..cc6db81b694 100644 --- a/declarations/used_by_sns_aggregator/sns_root/sns_root.did +++ b/declarations/used_by_sns_aggregator/sns_root/sns_root.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: type CanisterCallError = record { code : opt int32; description : text }; type CanisterIdRecord = record { canister_id : principal }; type CanisterInstallMode = variant { reinstall; upgrade; install }; diff --git a/declarations/used_by_sns_aggregator/sns_swap/sns_swap.did b/declarations/used_by_sns_aggregator/sns_swap/sns_swap.did index 0f1c781d1e0..cfe0a20203a 100644 --- a/declarations/used_by_sns_aggregator/sns_swap/sns_swap.did +++ b/declarations/used_by_sns_aggregator/sns_swap/sns_swap.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: type BuyerState = record { icp : opt TransferableAmount; has_created_neuron_recipes : opt bool; diff --git a/declarations/used_by_sns_aggregator/sns_wasm/sns_wasm.did b/declarations/used_by_sns_aggregator/sns_wasm/sns_wasm.did index 67d3a09f8c6..d9570b2b6e9 100644 --- a/declarations/used_by_sns_aggregator/sns_wasm/sns_wasm.did +++ b/declarations/used_by_sns_aggregator/sns_wasm/sns_wasm.did @@ -1,4 +1,4 @@ -//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: type AddWasmRequest = record { hash : blob; wasm : opt SnsWasm }; type AddWasmResponse = record { result : opt Result }; type AirdropDistribution = record { airdrop_neurons : vec NeuronDistribution }; diff --git a/dfx.json b/dfx.json index faa1ab72f00..8e1bcb0fdfe 100644 --- a/dfx.json +++ b/dfx.json @@ -372,7 +372,7 @@ "ENABLE_CKBTC": true, "ENABLE_CKTESTBTC": false, "ENABLE_PROJECTS_TABLE": false, - "ENABLE_IMPORT_TOKEN": true + "ENABLE_IMPORT_TOKEN": false } } }, @@ -386,9 +386,9 @@ "DIDC_VERSION": "2024-05-14", "POCKETIC_VERSION": "3.0.1", "CARGO_SORT_VERSION": "1.0.9", - "SNSDEMO_RELEASE": "release-2024-07-01", - "IC_COMMIT_FOR_PROPOSALS": "release-2024-06-26_23-01-base", - "IC_COMMIT_FOR_SNS_AGGREGATOR": "release-2024-06-26_23-01-base" + "SNSDEMO_RELEASE": "release-2024-07-10", + "IC_COMMIT_FOR_PROPOSALS": "release-2024-07-10_23-01-base", + "IC_COMMIT_FOR_SNS_AGGREGATOR": "release-2024-07-03_23-01-storage-layer-disabled" }, "packtool": "" } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 888ed9b0d9d..9fb18d0dd8b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dfinity/nns-dapp", - "version": "2.0.82", + "version": "2.0.83", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@dfinity/nns-dapp", - "version": "2.0.82", + "version": "2.0.83", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@dfinity/agent": "^1.3.0", diff --git a/frontend/package.json b/frontend/package.json index 5f598ced9b1..0108cc5681f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@dfinity/nns-dapp", - "version": "2.0.82", + "version": "2.0.83", "private": true, "license": "SEE LICENSE IN LICENSE.md", "scripts": { diff --git a/frontend/src/lib/api/proposals.api.ts b/frontend/src/lib/api/proposals.api.ts index 9ddc6208546..74dae91725a 100644 --- a/frontend/src/lib/api/proposals.api.ts +++ b/frontend/src/lib/api/proposals.api.ts @@ -55,6 +55,9 @@ export const queryProposals = async ({ includeRewardStatus, includeStatus, includeAllManageNeuronProposals: false, + // This flag solves the issue whe the proposal payload being too large. + // (e.g. IC0504: Error from Canister rrkah-fqaaa-aaaaa-aaaaq-cai: Canister violated contract: ic0.msg_reply_data_append: application payload size (3661753) cannot be larger than 3145728.) + omitLargeFields: true, }, certified, }); diff --git a/frontend/src/lib/components/staking/ProjectActionsCell.svelte b/frontend/src/lib/components/staking/ProjectActionsCell.svelte new file mode 100644 index 00000000000..8ca3f334a72 --- /dev/null +++ b/frontend/src/lib/components/staking/ProjectActionsCell.svelte @@ -0,0 +1,23 @@ + + +{#if false} + + {rowData.title} +{/if} + +
+ +
+ + diff --git a/frontend/src/lib/components/staking/ProjectNeuronsCell.svelte b/frontend/src/lib/components/staking/ProjectNeuronsCell.svelte new file mode 100644 index 00000000000..212a7bbf41f --- /dev/null +++ b/frontend/src/lib/components/staking/ProjectNeuronsCell.svelte @@ -0,0 +1,9 @@ + + +
+ {rowData.neuronCount} +
diff --git a/frontend/src/lib/components/staking/ProjectTitleCell.svelte b/frontend/src/lib/components/staking/ProjectTitleCell.svelte new file mode 100644 index 00000000000..cc942d496cc --- /dev/null +++ b/frontend/src/lib/components/staking/ProjectTitleCell.svelte @@ -0,0 +1,31 @@ + + +
+ +
+
{rowData.title}
+
+
+ + diff --git a/frontend/src/lib/components/staking/ProjectsTable.svelte b/frontend/src/lib/components/staking/ProjectsTable.svelte new file mode 100644 index 00000000000..5e774b06105 --- /dev/null +++ b/frontend/src/lib/components/staking/ProjectsTable.svelte @@ -0,0 +1,46 @@ + + + diff --git a/frontend/src/lib/constants/proposals.constants.ts b/frontend/src/lib/constants/proposals.constants.ts index 2274be7904f..a95aad91401 100644 --- a/frontend/src/lib/constants/proposals.constants.ts +++ b/frontend/src/lib/constants/proposals.constants.ts @@ -1,19 +1,12 @@ import type { BasisPoints } from "$lib/types/proposals"; import { ProposalStatus, Topic } from "@dfinity/nns"; -// TODO: suggest to move into the store and add typing -export const DEFAULT_PROPOSALS_FILTERS = { - topics: [ - Topic.NetworkEconomics, - Topic.Governance, - Topic.NodeAdmin, - Topic.ParticipantManagement, - Topic.SubnetManagement, - Topic.NetworkCanisterManagement, - Topic.NodeProviderRewards, - Topic.SnsAndCommunityFund, - ], - status: [ProposalStatus.Open], +export const DEFAULT_PROPOSALS_FILTERS: { + topics: Topic[]; + status: ProposalStatus[]; +} = { + topics: [], + status: [], }; export const DEPRECATED_TOPICS = [Topic.SnsDecentralizationSale]; diff --git a/frontend/src/lib/derived/proposals.derived.ts b/frontend/src/lib/derived/proposals.derived.ts index 1e6abd7342f..1d367b8934f 100644 --- a/frontend/src/lib/derived/proposals.derived.ts +++ b/frontend/src/lib/derived/proposals.derived.ts @@ -4,7 +4,10 @@ import { proposalsFiltersStore, proposalsStore, } from "$lib/stores/proposals.store"; -import { hideProposal } from "$lib/utils/proposals.utils"; +import { + hideProposal, + sortProposalsByIdDescendingOrder, +} from "$lib/utils/proposals.utils"; import type { ProposalInfo } from "@dfinity/nns"; import { derived, type Readable } from "svelte/store"; @@ -21,9 +24,7 @@ import { derived, type Readable } from "svelte/store"; export const sortedProposals: Readable = derived( [proposalsStore], ([{ proposals, certified }]) => ({ - proposals: proposals.sort(({ id: proposalIdA }, { id: proposalIdB }) => - Number((proposalIdB ?? 0n) - (proposalIdA ?? 0n)) - ), + proposals: sortProposalsByIdDescendingOrder(proposals), certified, }) ); diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 2891c4de35d..230b20fea2f 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -236,6 +236,9 @@ "seed": "Seed", "ect": "Early Contributor Token" }, + "staking": { + "nervous_systems": "Nervous Systems" + }, "neurons": { "title": "Neurons", "text": "Earn voting rewards by staking your ICP in neurons. Neurons allow you to participate in governance on the Internet Computer by submitting and voting on Network Nervous System (NNS) proposals.", @@ -1020,6 +1023,7 @@ "hide_zero_balances": "Hide zero balances", "hide_zero_balances_toggle_label": "Switch between showing and hiding tokens with a balance of zero", "zero_balance_hidden": "Tokens with 0 balances are hidden.", - "show_all": "Show all" + "show_all": "Show all", + "import_token": "Import Token" } } diff --git a/frontend/src/lib/pages/SnsNeuronDetail.svelte b/frontend/src/lib/pages/SnsNeuronDetail.svelte index 1efaf1f84e9..6f4b358dc65 100644 --- a/frontend/src/lib/pages/SnsNeuronDetail.svelte +++ b/frontend/src/lib/pages/SnsNeuronDetail.svelte @@ -25,15 +25,16 @@ import { snsTokenSymbolSelectedStore } from "$lib/derived/sns/sns-token-symbol-selected.store"; import SnsNeuronModals from "$lib/modals/sns/neurons/SnsNeuronModals.svelte"; import { loadSnsAccounts } from "$lib/services/sns-accounts.services"; + import { refreshNeuronIfNeeded } from "$lib/services/sns-neurons-check-balances.services"; import { getSnsNeuron } from "$lib/services/sns-neurons.services"; import { loadSnsParameters } from "$lib/services/sns-parameters.services"; import { queuedStore } from "$lib/stores/queued-store"; import { snsParametersStore } from "$lib/stores/sns-parameters.store"; import { toastsError } from "$lib/stores/toasts.store"; import { + SELECTED_SNS_NEURON_CONTEXT_KEY, type SelectedSnsNeuronContext, type SelectedSnsNeuronStore, - SELECTED_SNS_NEURON_CONTEXT_KEY, } from "$lib/types/sns-neuron-detail.context"; import { toTokenAmountV2 } from "$lib/utils/token.utils"; import { Island } from "@dfinity/gix-components"; @@ -41,7 +42,7 @@ import type { SnsNervousSystemParameters } from "@dfinity/sns"; import type { SnsNeuron } from "@dfinity/sns"; import type { Token, TokenAmountV2 } from "@dfinity/utils"; - import { nonNullish, isNullish } from "@dfinity/utils"; + import { isNullish, nonNullish } from "@dfinity/utils"; import { onMount, setContext } from "svelte"; export let neuronId: string | null | undefined; @@ -156,6 +157,28 @@ isNullish($selectedSnsNeuronStore.neuron) || isNullish(parameters) || isNullish(transactionFee); + + const maybeRefreshAndReload = async ({ + rootCanisterId, + neuron, + }: { + rootCanisterId: Principal | undefined; + neuron: SnsNeuron | undefined | null; + }) => { + if ( + await refreshNeuronIfNeeded({ + rootCanisterId, + neuron, + }) + ) { + loadNeuron({ forceFetch: true }); + } + }; + + $: maybeRefreshAndReload({ + rootCanisterId, + neuron: $selectedSnsNeuronStore.neuron, + }); diff --git a/frontend/src/lib/pages/SnsNeurons.svelte b/frontend/src/lib/pages/SnsNeurons.svelte index 9d75b7d7b32..b46c59c2e66 100644 --- a/frontend/src/lib/pages/SnsNeurons.svelte +++ b/frontend/src/lib/pages/SnsNeurons.svelte @@ -9,9 +9,11 @@ } from "$lib/derived/sns/sns-selected-project.derived"; import { definedSnsNeuronStore } from "$lib/derived/sns/sns-sorted-neurons.derived"; import { loadSnsAccounts } from "$lib/services/sns-accounts.services"; + import { claimNextNeuronIfNeeded } from "$lib/services/sns-neurons-check-balances.services"; import { syncSnsNeurons } from "$lib/services/sns-neurons.services"; import { authStore } from "$lib/stores/auth.store"; import { i18n } from "$lib/stores/i18n"; + import { snsNeuronsStore } from "$lib/stores/sns-neurons.store"; import type { TableNeuron } from "$lib/types/neurons-table"; import type { SnsSummary } from "$lib/types/sns"; import { replacePlaceholders } from "$lib/utils/i18n.utils"; @@ -50,6 +52,13 @@ snsNeurons: $definedSnsNeuronStore, }) : []; + + $: claimNextNeuronIfNeeded({ + rootCanisterId: $snsOnlyProjectStore, + neurons: + $snsOnlyProjectStore && + $snsNeuronsStore[$snsOnlyProjectStore.toText()]?.neurons, + }); diff --git a/frontend/src/lib/pages/Tokens.svelte b/frontend/src/lib/pages/Tokens.svelte index a251050356a..6c11737f20d 100644 --- a/frontend/src/lib/pages/Tokens.svelte +++ b/frontend/src/lib/pages/Tokens.svelte @@ -7,9 +7,10 @@ import { i18n } from "$lib/stores/i18n"; import type { UserToken } from "$lib/types/tokens-page"; import { heightTransition } from "$lib/utils/transition.utils"; - import { IconSettings } from "@dfinity/gix-components"; + import { IconPlus, IconSettings } from "@dfinity/gix-components"; import { Popover } from "@dfinity/gix-components"; import { TokenAmountV2 } from "@dfinity/utils"; + import { ENABLE_IMPORT_TOKEN } from "$lib/stores/feature-flags.store"; export let userTokensData: UserToken[]; @@ -39,6 +40,12 @@ const showAll = () => { hideZeroBalancesStore.set("show"); }; + + const importToken = async () => { + // TBD: Implement import token. + }; + + // TODO(Import token): After removing ENABLE_IMPORT_TOKEN combine divs ->
@@ -57,7 +64,30 @@ >
- {#if shouldHideZeroBalances} + {#if $ENABLE_IMPORT_TOKEN} +
+ {#if shouldHideZeroBalances} +
+ {$i18n.tokens.zero_balance_hidden} + +
+ {/if} + + +
+ {:else if shouldHideZeroBalances}
@use "@dfinity/gix-components/dist/styles/mixins/effect"; + @use "@dfinity/gix-components/dist/styles/mixins/media"; .settings-button { --content-color: var(--text-description); @@ -102,6 +133,7 @@ grid-column: 1 / -1; } + // TODO(Import token): Remove after enabling ENABLE_IMPORT_TOKEN .show-all-row { color: var(--text-description); padding: var(--padding-2x); @@ -111,4 +143,43 @@ text-decoration: underline; } } + + .last-row { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--padding-2x); + + padding: calc(3 * var(--padding)) var(--padding-2x); + background: var(--table-row-background); + border-top: 1px solid var(--elements-divider); + text-align: center; + + @include media.min-width(medium) { + flex-direction: row; + justify-content: space-between; + padding: var(--padding-2x); + text-align: left; + gap: var(--padding); + + .show-all-button-container { + // Show-all button should be on right on desktop. + order: 1; + } + } + + .show-all-button-container { + color: var(--text-description); + background: var(--table-row-background); + + button.show-all { + text-decoration: underline; + } + } + + .import-token-button { + gap: var(--padding); + color: var(--primary); + } + } diff --git a/frontend/src/lib/routes/Staking.svelte b/frontend/src/lib/routes/Staking.svelte index 28bcd9a4ea0..75e6eec643e 100644 --- a/frontend/src/lib/routes/Staking.svelte +++ b/frontend/src/lib/routes/Staking.svelte @@ -1,6 +1,8 @@ -
-

STAKING PROJECTS TABLE UNDER CONSTRUCTION

-
+ + + diff --git a/frontend/src/lib/services/actionable-proposals.services.ts b/frontend/src/lib/services/actionable-proposals.services.ts index d6810cb1f27..5ecccf8776d 100644 --- a/frontend/src/lib/services/actionable-proposals.services.ts +++ b/frontend/src/lib/services/actionable-proposals.services.ts @@ -7,12 +7,17 @@ import { getCurrentIdentity } from "$lib/services/auth.services"; import { listNeurons } from "$lib/services/neurons.services"; import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store"; import { definedNeuronsStore, neuronsStore } from "$lib/stores/neurons.store"; -import { lastProposalId } from "$lib/utils/proposals.utils"; -import type { ProposalInfo } from "@dfinity/nns"; +import { + lastProposalId, + sortProposalsByIdDescendingOrder, +} from "$lib/utils/proposals.utils"; import { ProposalRewardStatus, + ProposalStatus, + Topic, votableNeurons, type NeuronInfo, + type ProposalInfo, } from "@dfinity/nns"; import { isNullish } from "@dfinity/utils"; import { get } from "svelte/store"; @@ -27,13 +32,30 @@ export const loadActionableProposals = async (): Promise => { return; } - const proposals = await queryProposals(); + const acceptVotesProposals = await queryProposals({ + includeRewardStatus: [ProposalRewardStatus.AcceptVotes], + }); + // Request Neuron Management proposals that are open and have an ineligible reward + // status because they don't have rewards (not ProposalRewardStatus.AcceptVotes), + // but are still votable. + // Only users which are listed explicitly in the followees of a Neuron Management proposal will get to + // see such a proposal in the query response. So for most users the response will be empty. + const neuronManagementProposals = await queryProposals({ + includeStatus: [ProposalStatus.Open], + includeTopics: [Topic.ManageNeuron], + // Technically, filtering by ProposalRewardStatus.Ineligible isn’t necessary, + // but it ensures that the results are disjoint (acceptVotesProposals and neuronManagementProposals have no common items). + includeRewardStatus: [ProposalRewardStatus.Ineligible], + }); // Filter proposals that have at least one votable neuron - const votableProposals = proposals.filter( - (proposal) => votableNeurons({ neurons, proposal }).length > 0 - ); + const votableProposals = [ + ...acceptVotesProposals, + ...neuronManagementProposals, + ].filter((proposal) => votableNeurons({ neurons, proposal }).length > 0); - actionableNnsProposalsStore.setProposals(votableProposals); + actionableNnsProposalsStore.setProposals( + sortProposalsByIdDescendingOrder(votableProposals) + ); }; const queryNeurons = async (): Promise => { @@ -46,7 +68,15 @@ const queryNeurons = async (): Promise => { }; /// Fetch all (500 max) proposals that are accepting votes. -const queryProposals = async (): Promise => { +const queryProposals = async ({ + includeTopics, + includeRewardStatus, + includeStatus, +}: { + includeTopics?: Topic[]; + includeRewardStatus?: ProposalRewardStatus[]; + includeStatus?: ProposalStatus[]; +}): Promise => { const identity = getCurrentIdentity(); let sortedProposals: ProposalInfo[] = []; for ( @@ -58,14 +88,15 @@ const queryProposals = async (): Promise => { const page = await queryNnsProposals({ beforeProposal: lastProposalId(sortedProposals), identity, - includeRewardStatus: [ProposalRewardStatus.AcceptVotes], certified: false, + includeTopics, + includeRewardStatus, + includeStatus, }); - // Sort proposals by id in descending order to be sure that "lastProposalId" returns correct id. - sortedProposals = [...sortedProposals, ...page].sort( - ({ id: proposalIdA }, { id: proposalIdB }) => - Number((proposalIdB ?? 0n) - (proposalIdA ?? 0n)) - ); + sortedProposals = sortProposalsByIdDescendingOrder([ + ...sortedProposals, + ...page, + ]); if (page.length !== DEFAULT_LIST_PAGINATION_LIMIT) { break; diff --git a/frontend/src/lib/services/actionable-sns-proposals.services.ts b/frontend/src/lib/services/actionable-sns-proposals.services.ts index 8ff9db12b83..7712db681ec 100644 --- a/frontend/src/lib/services/actionable-sns-proposals.services.ts +++ b/frontend/src/lib/services/actionable-sns-proposals.services.ts @@ -1,8 +1,9 @@ -import { queryProposals, querySnsNeurons } from "$lib/api/sns-governance.api"; +import { queryProposals } from "$lib/api/sns-governance.api"; import { MAX_ACTIONABLE_REQUEST_COUNT } from "$lib/constants/constants"; import { DEFAULT_SNS_PROPOSALS_PAGE_SIZE } from "$lib/constants/sns-proposals.constants"; import { snsProjectsCommittedStore } from "$lib/derived/sns/sns-projects.derived"; import { getAuthenticatedIdentity } from "$lib/services/auth.services"; +import { loadSnsNeurons } from "$lib/services/sns-neurons.services"; import { actionableSnsProposalsStore, failedActionableSnsesStore, @@ -18,7 +19,7 @@ import { Principal } from "@dfinity/principal"; import type { SnsNeuron } from "@dfinity/sns"; import { SnsProposalRewardStatus } from "@dfinity/sns"; import type { ProposalData } from "@dfinity/sns/dist/candid/sns_governance"; -import { fromNullable, nonNullish } from "@dfinity/utils"; +import { fromNullable, isNullish } from "@dfinity/utils"; import { get } from "svelte/store"; export const loadActionableSnsProposals = async () => { @@ -57,14 +58,9 @@ export const loadActionableProposalsForSns = async ( return; } - const neurons = - get(snsNeuronsStore)[rootCanisterIdText]?.neurons ?? - // Fetch neurons if they are not in the store, but do not populate the store. - // Otherwise, it will skip calling of the `syncSnsNeurons` function to check neurons stake against the balance of the subaccount. - (await queryNeurons({ - rootCanisterId: rootCanisterIdText, - identity, - })); + const neurons = await queryNeurons({ + rootCanisterId, + }); // It's not possible to filter out votable proposals w/o ballots const votableProposals = includeBallotsByCaller @@ -93,21 +89,15 @@ export const loadActionableProposalsForSns = async ( const queryNeurons = async ({ rootCanisterId, - identity, }: { - rootCanisterId: string; - identity: Identity; + rootCanisterId: Principal; }): Promise => { - const storeNeurons = get(snsNeuronsStore)[rootCanisterId]; - if (nonNullish(storeNeurons?.neurons)) { - return storeNeurons.neurons; + const getStoreNeurons = () => + get(snsNeuronsStore)[rootCanisterId.toText()]?.neurons; + if (isNullish(getStoreNeurons())) { + await loadSnsNeurons({ rootCanisterId, certified: false }); } - - return await querySnsNeurons({ - identity, - rootCanisterId: Principal.fromText(rootCanisterId), - certified: false, - }); + return getStoreNeurons(); }; /** Fetches proposals that accept votes */ diff --git a/frontend/src/lib/services/sns-neurons-check-balances.services.ts b/frontend/src/lib/services/sns-neurons-check-balances.services.ts index f4114bb17ab..a422230c7f1 100644 --- a/frontend/src/lib/services/sns-neurons-check-balances.services.ts +++ b/frontend/src/lib/services/sns-neurons-check-balances.services.ts @@ -2,25 +2,25 @@ import { claimNeuron, getNeuronBalance, getSnsNeuron, - querySnsNeuron, refreshNeuron, } from "$lib/api/sns-governance.api"; -import { MAX_NEURONS_SUBACCOUNTS } from "$lib/constants/sns-neurons.constants"; import { getAuthenticatedIdentity } from "$lib/services/auth.services"; +import { loadSnsParameters } from "$lib/services/sns-parameters.services"; +import { checkedNeuronSubaccountsStore } from "$lib/stores/checked-neurons.store"; import { snsNeuronsStore } from "$lib/stores/sns-neurons.store"; +import { snsParametersStore } from "$lib/stores/sns-parameters.store"; import { getSnsNeuronIdAsHexString, needsRefresh, + nextMemo, subaccountToHexString, } from "$lib/utils/sns-neuron.utils"; -import { AnonymousIdentity, type Identity } from "@dfinity/agent"; +import type { Identity } from "@dfinity/agent"; import type { Principal } from "@dfinity/principal"; -import { - neuronSubaccount, - type SnsNeuron, - type SnsNeuronId, -} from "@dfinity/sns"; -import { fromNullable, isNullish } from "@dfinity/utils"; +import type { SnsNeuron, SnsNeuronId } from "@dfinity/sns"; +import { neuronSubaccount } from "@dfinity/sns"; +import { fromDefinedNullable, fromNullable, isNullish } from "@dfinity/utils"; +import { get } from "svelte/store"; const loadNeuron = async ({ rootCanisterId, @@ -39,6 +39,19 @@ const loadNeuron = async ({ neuronId, certified, }); + if (userHasNoPermissions({ neuron, controller: identity.getPrincipal() })) { + // This neuron does not belong to the user. + // It's possible for an SNS neuron to be transferred to another user + // by removing the permissions for one user and adding permissions for + // another user. For example idgeek.app has a service to sell SNS + // neurons. This service also works with NNS dapp controlled neurons + // because they fully take over your Internet Identity. Since this + // will not change the ID of the neuron, it will still appear in the + // sequence of neuron IDs for the original user. So we must take extra + // care to make sure it does not appear in the UI for the original + // user. + return; + } snsNeuronsStore.addNeurons({ rootCanisterId, certified, @@ -111,35 +124,6 @@ const claimAndLoadNeuron = async ({ }); }; -const findNeuronBySubaccount = async ({ - subaccount, - neurons, - rootCanisterId, - positiveBalance, -}: { - subaccount: Uint8Array | number[]; - neurons: SnsNeuron[]; - rootCanisterId: Principal; - positiveBalance: boolean; -}): Promise => { - let maybeNeuron = neurons.find( - (neuron) => - getSnsNeuronIdAsHexString(neuron) === subaccountToHexString(subaccount) - ); - // At this point we don't know whether it's an unclaimed or transferred neuron. - if (isNullish(maybeNeuron) && positiveBalance) { - const neuronId: SnsNeuronId = { id: subaccount }; - maybeNeuron = await querySnsNeuron({ - identity: new AnonymousIdentity(), - rootCanisterId, - neuronId, - // No need to check with update call, worst case, a neuron will appear in the UI that shouldn't or an extra call to refressh will be made. - certified: false, - }); - } - return maybeNeuron; -}; - /** * Returns true only of neuron is present and the user has no permissions. * @@ -163,204 +147,158 @@ const userHasNoPermissions = ({ }); /** - * Checks subaccounts of identity in order to find neurons that need to be refreshed or claimed. - * - * Neuron subaccount is { sha256(0x0c . “neuron-stake” . P . i) | i ∈ ℕ } - * - * The main property of this is that it is always possible to recompute all the neurons subaccounts of a principal. - * This can be used to make the SNS UI stateless, which then means that the SNS UI can fail at any time - * without losing any important information needed to derive accounts. - * - * Neurons subaccounts of a principal must be used in order of i. - * The first account of P is sha256(0x0c . “neuron-staking” . P . 0), the second one is sha256(0x0c . “neuron-staking” . P . 1) … - * This allows any client to verify the state of neurons subaccounts that have received a transfer without checking all the ns subaccounts. - * - * A stateless SNS UI will perform the following operations every time it starts: - * - It gets the list of neurons from the SNS Governance using list_neurons. This is done via an update call to confirm the correctness of the data. - * - It calculates all its ns subaccounts and then checks their balances via query calls. This is where the ordering of ns subaccounts comes into play: the client just needs to check the ns subaccounts that are either already neuron accounts returned in 1. or have a balance different from 0. - * - The SNS UI notifies the SNS Governance of all the neuron subaccounts that the user has some permission and: - * - Have a balance different from 0 but the list of neurons doesn’t list them as neurons. - * - In this case, the client needs to claim the neuron. - * - Have a balance different from the cached_neuron_stake_e8s returned by the SNS Governance - * - In this case, the client needs to refresh the neuron. - * - * SUMMARY: - * - * ------------------------------------------------------------------- - * | | Balance 0 | Balance > 0 | - * | found neuron (with permission) | check neuron stake vs balance | - * | no neuron | stop checking | claim neuron | - * ------------------------------------------------------------------- - * - * @param {Object} - * @param {Principal} params.rootCanisterId - * @param {SnsNeuron[]} params.neurons neurons to check - * @param {Identity} params.identity - * @returns + * Checks neuron's subaccount and refreshes the neuron if needed. + * Returns true if the neuron was refreshed. */ -const checkNeuronsSubaccounts = async ({ +const checkNeuron = async ({ identity, rootCanisterId, - neurons, - neuronMinimumStake, + neuron, }: { identity: Identity; rootCanisterId: Principal; - neurons: SnsNeuron[]; - neuronMinimumStake: bigint; -}): Promise => { - const visitedSubaccounts = new Set(); - const controller = identity.getPrincipal(); - // Visit subaccounts by order until we find a balance of 0. - // Therefore, we set the initial balance to some non-zero value. - let currentBalance = BigInt(Number.MAX_SAFE_INTEGER); - for (let index = 0; index < MAX_NEURONS_SUBACCOUNTS; index++) { - try { - // In case there is an error getting the balance, the loop stops. - currentBalance = 0n; - const subaccount = neuronSubaccount({ - controller, - index, - }); - // Id of an sns neuron is the subaccount of the sns governance canister where the stake is - const currentNeuronId: SnsNeuronId = { id: subaccount }; - visitedSubaccounts.add(subaccountToHexString(subaccount)); - // We use certified: false for performance reasons. - // A bad actor will only get that we call refresh or claim on a neuron. - currentBalance = await getNeuronBalance({ - rootCanisterId, - neuronId: currentNeuronId, - certified: false, - identity, - }); - const positiveBalance = currentBalance >= neuronMinimumStake; - // At this point we don't know whether it's an unclaimed or transferred neuron. - const neuron = await findNeuronBySubaccount({ - subaccount, - neurons, - rootCanisterId, - positiveBalance, - }); - // Skip claiming or refreshing if the user has no permission. - // This might happen if the user staked the neuron in NNS Dapp and then transferred it. - if (userHasNoPermissions({ neuron, controller })) { - continue; - } - const neuronNotFound = neuron === undefined; - // Subaccount balance >= `neuron_minimum_stake_e8s` but no neuron found, claim it. - if (positiveBalance && neuronNotFound) { - await claimAndLoadNeuron({ - rootCanisterId, - identity, - controller, - memo: BigInt(index), - subaccount, - }); - } - // Neuron found, check if it needs to be refreshed. - if ( - neuron !== undefined && - needsRefresh({ neuron, balanceE8s: currentBalance }) - ) { - // Edge case, a valid neuron should have a neuron id - const neuronId = fromNullable(neuron.id); - if (neuronId !== undefined) { - await refreshNeuron({ rootCanisterId, identity, neuronId }); - await loadNeuron({ - rootCanisterId, - neuronId: neuronId, - certified: true, - identity, - }); - } - } - // If the balance is 0 and there is no neuron in that subaccount, stop checking. - if (currentBalance === 0n && neuronNotFound) { - break; - } - } catch (error) { - // Ignore the error, we do this in the background. - // If this fails the data might be stale but it should refreshed on the next sync. - console.error(error); - } + neuron: SnsNeuron; +}): Promise => { + const neuronId = fromNullable(neuron.id); + if ( + isNullish(neuronId) || + !(await neuronNeedsRefresh({ rootCanisterId, neuron, identity })) + ) { + return false; + } + await refreshNeuron({ rootCanisterId, identity, neuronId }); + await loadNeuron({ + rootCanisterId, + neuronId: neuronId, + certified: true, + identity, + }); + return true; +}; + +// Returns true if the neuron was refreshed. +export const refreshNeuronIfNeeded = async ({ + rootCanisterId, + neuron, +}: { + rootCanisterId: Principal | undefined; + neuron: SnsNeuron | null | undefined; +}): Promise => { + if (isNullish(rootCanisterId) || isNullish(neuron)) { + return false; } - // Not all neurons that the user has some permission need to be created in NNS Dapp. - // Some of those neurons might need a refresh as well. - // That's done in another function, here we only return them. - const unvisitedNeurons = neurons.filter( - ({ id }) => - !visitedSubaccounts.has( - subaccountToHexString(fromNullable(id)?.id ?? new Uint8Array()) - ) + // An SNS neuron's ID is the same as its subaccount. + const subaccountHex = getSnsNeuronIdAsHexString(neuron); + const universeId = rootCanisterId.toText(); + + // We only check neurons to recover from an interrupted stake/top-up. + // Doing this once per neuron per session is often enough. + if ( + !checkedNeuronSubaccountsStore.addSubaccount({ + universeId, + subaccountHex, + }) + ) { + return false; + } + + const identity = await getAuthenticatedIdentity(); + return checkNeuron({ + identity, + rootCanisterId, + neuron, + }); +}; + +const getNeuronMinimumStake = async ({ + rootCanisterId, +}: { + rootCanisterId: Principal; +}): Promise => { + await loadSnsParameters(rootCanisterId); + return fromDefinedNullable( + get(snsParametersStore)?.[rootCanisterId.toText()]?.parameters + ?.neuron_minimum_stake_e8s ); - return unvisitedNeurons; }; -/** - * Checks neurons subaccounts and refreshes neurons that need it. - * - * @param {Object} - * @param {Principal} params.rootCanisterId - * @param {SnsNeuron[]} params.neurons neurons to check - * @param {Identity} params.identity - * @returns - */ -const checkNeurons = async ({ - identity, +const claimNeuronIfNeeded = async ({ rootCanisterId, - neurons, + memo, + subaccount, + identity, }: { - identity: Identity; rootCanisterId: Principal; - neurons: SnsNeuron[]; -}) => { - for (const neuron of neurons) { - const neuronId = fromNullable(neuron.id); - if (neuronId !== undefined) { - if (await neuronNeedsRefresh({ rootCanisterId, neuron, identity })) { - await refreshNeuron({ rootCanisterId, identity, neuronId }); - await loadNeuron({ - rootCanisterId, - neuronId: neuronId, - certified: true, - identity, - }); - } - } + memo: bigint; + subaccount: Uint8Array | number[]; + identity: Identity; +}): Promise => { + // We only check neurons to recover from an interrupted stake/top-up. + // Doing this once per neuron per session is often enough. + if ( + !checkedNeuronSubaccountsStore.addSubaccount({ + universeId: rootCanisterId.toText(), + subaccountHex: subaccountToHexString(subaccount), + }) + ) { + return; } + + const neuronId: SnsNeuronId = { id: subaccount }; + // We use certified: false for performance reasons. + // A bad actor will only get that we call refresh or claim on a neuron. + const neuronAccountBalance = await getNeuronBalance({ + rootCanisterId, + neuronId, + certified: false, + identity, + }); + + if (neuronAccountBalance === 0n) { + return; + } + + const neuronMinimumStake = await getNeuronMinimumStake({ rootCanisterId }); + // Don't claim a neuron if there's not enough balance to stake. + if (neuronAccountBalance < neuronMinimumStake) { + return; + } + + await claimAndLoadNeuron({ + rootCanisterId, + identity, + controller: identity.getPrincipal(), + memo, + subaccount, + }); }; -/** - * Checks a couple of things: - * - Neurons subaccounts balances and refreshes or claims neurons that need it. - * - This follows a new staking neuron algorithm explained in the helper `checkNeuronsSubaccounts` - * - Check the rest of the neurons and refreshes them if needed. - * - * It refetches the neurons that are not in sync with the subaccounts and adds them to the store. - * - * Note: - * `SnsNervousSystemParameters` should be preloaded before calling this function. - * - * @param param0 - * @returns {boolean} - */ -export const checkSnsNeuronBalances = async ({ +export const claimNextNeuronIfNeeded = async ({ rootCanisterId, neurons, - neuronMinimumStake, }: { - rootCanisterId: Principal; - neurons: SnsNeuron[]; - neuronMinimumStake: bigint; + rootCanisterId: Principal | undefined; + neurons: SnsNeuron[] | undefined; }): Promise => { - // TODO: Check neurons controlled by linked HW? + if (isNullish(rootCanisterId)) { + return; + } + if (isNullish(neurons)) { + return; + } const identity = await getAuthenticatedIdentity(); - const unvisitedNeurons = await checkNeuronsSubaccounts({ + const memo = nextMemo({ identity, - rootCanisterId, neurons, - neuronMinimumStake, }); - - await checkNeurons({ identity, rootCanisterId, neurons: unvisitedNeurons }); + const subaccount = neuronSubaccount({ + controller: identity.getPrincipal(), + index: Number(memo), + }); + await claimNeuronIfNeeded({ + rootCanisterId, + memo, + subaccount, + identity, + }); }; diff --git a/frontend/src/lib/services/sns-neurons.services.ts b/frontend/src/lib/services/sns-neurons.services.ts index a5b3127b0e8..fad7343b212 100644 --- a/frontend/src/lib/services/sns-neurons.services.ts +++ b/frontend/src/lib/services/sns-neurons.services.ts @@ -7,7 +7,6 @@ import { getSnsNeuron as getSnsNeuronApi, querySnsNeuron, querySnsNeurons, - refreshNeuron, removeNeuronPermissions, setDissolveDelay, setFollowees, @@ -30,14 +29,10 @@ import { snsNeuronsStore, type ProjectNeuronStore, } from "$lib/stores/sns-neurons.store"; -import { snsParametersStore } from "$lib/stores/sns-parameters.store"; import { toastsError, toastsSuccess } from "$lib/stores/toasts.store"; import type { Account } from "$lib/types/account"; import { nowInSeconds } from "$lib/utils/date.utils"; -import { - isForceCallStrategy, - notForceCallStrategy, -} from "$lib/utils/env.utils"; +import { notForceCallStrategy } from "$lib/utils/env.utils"; import { toToastError } from "$lib/utils/error.utils"; import { ledgerErrorToToastError } from "$lib/utils/sns-ledger.utils"; import { @@ -54,49 +49,31 @@ import type { Identity } from "@dfinity/agent"; import { decodeIcrcAccount } from "@dfinity/ledger-icrc"; import type { E8s } from "@dfinity/nns"; import { Principal } from "@dfinity/principal"; -import type { - SnsNervousSystemParameters, - SnsNeuron, - SnsNeuronId, -} from "@dfinity/sns"; +import type { SnsNeuron, SnsNeuronId } from "@dfinity/sns"; import { arrayOfNumberToUint8Array, assertNonNullish, fromDefinedNullable, fromNullable, - isNullish, nonNullish, } from "@dfinity/utils"; import { get } from "svelte/store"; import { getAuthenticatedIdentity } from "./auth.services"; import { loadSnsAccounts } from "./sns-accounts.services"; -import { - checkSnsNeuronBalances, - neuronNeedsRefresh, -} from "./sns-neurons-check-balances.services"; import { queryAndUpdate } from "./utils.services"; /** - * Loads sns neurons in store and checks neurons's stake against the balance of the subaccount. + * Loads sns neurons in store. * (Loads sns parameters when not already in the store) * - * On update, it will check whether there are neurons that need to be refreshed or claimed. - * A neuron needs to be refreshed if the balance of the subaccount doesn't match the stake of the neuron. - * A neuron needs to be claimed if there is a subaccount with balance and no neuron. - * * @param {Principal} rootCanisterId * @returns {void} */ export const syncSnsNeurons = async ( rootCanisterId: Principal ): Promise => { - const snsParameters = () => - get(snsParametersStore)?.[rootCanisterId.toText()] - ?.parameters as SnsNervousSystemParameters; - // Load SNS parameters if not in store - const snsParametersRequest = isNullish(snsParameters()) - ? loadSnsParameters(rootCanisterId) - : Promise.resolve(); + const snsParametersRequest = loadSnsParameters(rootCanisterId); + const syncSnsNeuronsRequest = queryAndUpdate({ strategy: FORCE_CALL_STRATEGY, request: ({ certified, identity }) => @@ -111,21 +88,6 @@ export const syncSnsNeurons = async ( neurons, certified, }); - - if (certified || isForceCallStrategy()) { - // be sure that the parameters are loaded - await snsParametersRequest; - const neuronMinimumStake = fromNullable( - snsParameters()?.neuron_minimum_stake_e8s - ); - assertNonNullish(neuronMinimumStake, "neuron_minimum_stake_e8s"); - - checkSnsNeuronBalances({ - rootCanisterId, - neurons, - neuronMinimumStake, - }); - } }, onError: ({ error: err, certified }) => { console.error(err); @@ -150,7 +112,7 @@ export const syncSnsNeurons = async ( return Promise.all([snsParametersRequest, syncSnsNeuronsRequest]).then(); }; -export const loadNeurons = async ({ +export const loadSnsNeurons = async ({ rootCanisterId, certified, }: { @@ -242,24 +204,6 @@ export const getSnsNeuron = async ({ }), onLoad: async ({ response: neuron, certified }) => { onLoad({ neuron, certified }); - - if (certified) { - // Check that the neuron's stake is in sync with the subaccount's balance - const neuronId = fromNullable(neuron.id); - if (neuronId !== undefined) { - const identity = await getSnsNeuronIdentity(); - if (await neuronNeedsRefresh({ rootCanisterId, neuron, identity })) { - await refreshNeuron({ rootCanisterId, identity, neuronId }); - const updatedNeuron = await getSnsNeuronApi({ - identity, - rootCanisterId, - neuronId, - certified, - }); - onLoad({ neuron: updatedNeuron, certified }); - } - } - } }, onError: ({ certified, error }) => { onError?.({ certified, error }); @@ -373,7 +317,7 @@ export const splitNeuron = async ({ // TODO: Get identity depending on account to support HW accounts const identity = await getSnsNeuronIdentity(); // reload neurons (should be actual for nextMemo calculation) - await loadNeurons({ + await loadSnsNeurons({ rootCanisterId, certified: true, }); @@ -583,7 +527,7 @@ export const stakeNeuron = async ({ }); await Promise.all([ loadSnsAccounts({ rootCanisterId }), - loadNeurons({ rootCanisterId, certified: true }), + loadSnsNeurons({ rootCanisterId, certified: true }), ]); return { success: true }; } catch (err) { diff --git a/frontend/src/lib/stores/checked-neurons.store.ts b/frontend/src/lib/stores/checked-neurons.store.ts new file mode 100644 index 00000000000..a36c6f182d8 --- /dev/null +++ b/frontend/src/lib/stores/checked-neurons.store.ts @@ -0,0 +1,56 @@ +import { writable } from "svelte/store"; + +interface UniverseCheckedNeuronSubaccountStore { + [subaccountHex: string]: true; +} + +interface CheckedNeuronSubaccountsStore { + [universeId: string]: UniverseCheckedNeuronSubaccountStore; +} + +// Staking or topping up a neuron is a 2-step process. If the process gets +// interrupted, we can check the balance of a neuron's subaccount to see if the +// process needs to be resumed. Since this is just a fallback mechanism, we +// don't need to do it more than once per neuron per session. This store keeps +// track of the neurons that have already been checked this session. +const initCheckedNeuronSubaccountsStore = () => { + const { subscribe, update, set } = writable( + {} + ); + + return { + subscribe, + + // Returns true if the subaccount was added this time and false if it was + // already in the store. + addSubaccount({ + universeId, + subaccountHex, + }: { + universeId: string; + subaccountHex: string; + }): boolean { + let result = true; + update((currentState: CheckedNeuronSubaccountsStore) => { + const isPresent = currentState[universeId]?.[subaccountHex] ?? false; + result = !isPresent; + return isPresent + ? currentState + : { + ...currentState, + [universeId]: { + ...currentState[universeId], + [subaccountHex]: true, + }, + }; + }); + return result; + }, + reset() { + set({}); + }, + }; +}; + +export const checkedNeuronSubaccountsStore = + initCheckedNeuronSubaccountsStore(); diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 2dee1e92d03..fc1129de41f 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -247,6 +247,10 @@ interface I18nNeuron_types { ect: string; } +interface I18nStaking { + nervous_systems: string; +} + interface I18nNeurons { title: string; text: string; @@ -1079,6 +1083,7 @@ interface I18nTokens { hide_zero_balances_toggle_label: string; zero_balance_hidden: string; show_all: string; + import_token: string; } interface I18nNeuron_state { @@ -1311,6 +1316,7 @@ interface I18n { auth: I18nAuth; accounts: I18nAccounts; neuron_types: I18nNeuron_types; + staking: I18nStaking; neurons: I18nNeurons; new_followee: I18nNew_followee; follow_neurons: I18nFollow_neurons; diff --git a/frontend/src/lib/types/staking.ts b/frontend/src/lib/types/staking.ts index 5a61805f5cb..cc5012942da 100644 --- a/frontend/src/lib/types/staking.ts +++ b/frontend/src/lib/types/staking.ts @@ -1,6 +1,11 @@ +import type { ResponsiveTableColumn } from "$lib/types/responsive-table"; + export type TableProject = { rowHref?: string; domKey: string; title: string; logo: string; + neuronCount: number; }; + +export type ProjectsTableColumn = ResponsiveTableColumn; diff --git a/frontend/src/lib/utils/proposals.utils.ts b/frontend/src/lib/utils/proposals.utils.ts index b285837b0b6..568ca85e64a 100644 --- a/frontend/src/lib/utils/proposals.utils.ts +++ b/frontend/src/lib/utils/proposals.utils.ts @@ -192,6 +192,7 @@ export const proposalIdSet = (proposals: ProposalInfo[]): Set => * Compares proposals by "id" */ export const concatenateUniqueProposals = ({ + // TODO(max): replace with unnamed parameters because it's now being used in multiple places oldProposals, newProposals, }: { @@ -620,3 +621,17 @@ export const navigationIdComparator = ({ if (a.proposalId > b.proposalId) return -1; return 0; }; + +/** + * Sort NNS proposals by IDs in descending order. + */ +export const sortProposalsByIdDescendingOrder = ( + proposals: ProposalInfo[] +): ProposalInfo[] => + [...proposals].sort((a, b) => { + const idA = a.id ?? 0n; + const idB = b.id ?? 0n; + if (idB > idA) return 1; + if (idB < idA) return -1; + return 0; + }); diff --git a/frontend/src/lib/utils/staking.utils.ts b/frontend/src/lib/utils/staking.utils.ts index 65c21e4e2a3..97d0f2a2f56 100644 --- a/frontend/src/lib/utils/staking.utils.ts +++ b/frontend/src/lib/utils/staking.utils.ts @@ -1,16 +1,34 @@ +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; import type { TableProject } from "$lib/types/staking"; import type { Universe } from "$lib/types/universe"; import { buildNeuronsUrl } from "$lib/utils/navigation.utils"; +import { hasValidStake } from "$lib/utils/sns-neuron.utils"; +import type { NeuronInfo } from "@dfinity/nns"; +import type { SnsNeuron } from "@dfinity/sns"; +// Will filter out neurons without stake from snsNeurons, but assumes neurons +// without stake have already been filtered out from definedNnsNeurons export const getTableProjects = ({ universes, + definedNnsNeurons, + snsNeurons, }: { universes: Universe[]; + definedNnsNeurons: NeuronInfo[]; + snsNeurons: { [rootCanisterId: string]: { neurons: SnsNeuron[] } }; }): TableProject[] => { - return universes.map((universe) => ({ - rowHref: buildNeuronsUrl({ universe: universe.canisterId }), - domKey: universe.canisterId, - title: universe.title, - logo: universe.logo, - })); + return universes.map((universe) => { + const neuronCount = + universe.canisterId === OWN_CANISTER_ID_TEXT + ? definedNnsNeurons.length + : snsNeurons[universe.canisterId]?.neurons.filter(hasValidStake) + .length ?? 0; + return { + rowHref: buildNeuronsUrl({ universe: universe.canisterId }), + domKey: universe.canisterId, + title: universe.title, + logo: universe.logo, + neuronCount, + }; + }); }; diff --git a/frontend/src/tests/e2e/sns-governance.spec.ts b/frontend/src/tests/e2e/sns-governance.spec.ts index 99cd1bf8161..2ff282a6880 100644 --- a/frontend/src/tests/e2e/sns-governance.spec.ts +++ b/frontend/src/tests/e2e/sns-governance.spec.ts @@ -21,8 +21,8 @@ test("Test SNS governance", async ({ page, context }) => { const snsUniverseRow = snsUniverseRows[0]; const snsProjectName = await snsUniverseRow.getProjectName(); - // Our test SNS project names are always 5 uppercase letters. - expect(snsProjectName).toMatch(/[A-Z]{5}/); + // Our first test SNS project is always named "Alfa Centauri". + expect(snsProjectName).toBe("Alfa Centauri"); step("Acquire tokens"); const askedAmount = 20; @@ -40,7 +40,7 @@ test("Test SNS governance", async ({ page, context }) => { expect( await appPo.getNeuronsPo().getSnsNeuronsPo().getEmptyMessage() ).toEqual( - `You have no ${snsProjectName} neurons. Create a neuron by staking ${snsProjectName} to vote on ${snsProjectName} proposals.` + "You have no Alfa Centauri neurons. Create a neuron by staking ALF to vote on Alfa Centauri proposals." ); const stake = 5; diff --git a/frontend/src/tests/lib/api/proposals.api.spec.ts b/frontend/src/tests/lib/api/proposals.api.spec.ts index 1c8fbdd5539..3ab882c0670 100644 --- a/frontend/src/tests/lib/api/proposals.api.spec.ts +++ b/frontend/src/tests/lib/api/proposals.api.spec.ts @@ -79,6 +79,7 @@ describe("proposals-api", () => { includeStatus: defaultIncludeStatus, includeAllManageNeuronProposals: false, limit: 100, + omitLargeFields: true, }, }); }); @@ -99,6 +100,7 @@ describe("proposals-api", () => { includeStatus: [], includeAllManageNeuronProposals: false, limit: 100, + omitLargeFields: true, }, }); }); diff --git a/frontend/src/tests/lib/components/proposal-detail/VotingCard/SnsVotingCard.spec.ts b/frontend/src/tests/lib/components/proposal-detail/VotingCard/SnsVotingCard.spec.ts index a65ab2ef458..a3c524a46f7 100644 --- a/frontend/src/tests/lib/components/proposal-detail/VotingCard/SnsVotingCard.spec.ts +++ b/frontend/src/tests/lib/components/proposal-detail/VotingCard/SnsVotingCard.spec.ts @@ -297,6 +297,8 @@ describe("SnsVotingCard", () => { ...createMockSnsNeuron({ id: [3], state: NeuronState.Unspecified, + // Neuron is ineligible because it has no vote permission + permissions: [], }), }, ], diff --git a/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts b/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts new file mode 100644 index 00000000000..14d43cd3e1f --- /dev/null +++ b/frontend/src/tests/lib/components/staking/ProjectsTable.spec.ts @@ -0,0 +1,205 @@ +import ProjectsTable from "$lib/components/staking/ProjectsTable.svelte"; +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; +import { AppPath } from "$lib/constants/routes.constants"; +import { neuronsStore } from "$lib/stores/neurons.store"; +import { snsNeuronsStore } from "$lib/stores/sns-neurons.store"; +import { page } from "$mocks/$app/stores"; +import { mockNeuron } from "$tests/mocks/neurons.mock"; +import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock"; +import { principal } from "$tests/mocks/sns-projects.mock"; +import { ProjectsTablePo } from "$tests/page-objects/ProjectsTable.page-object"; +import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; +import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils"; +import { render } from "$tests/utils/svelte.test-utils"; +import { runResolvedPromises } from "$tests/utils/timers.test-utils"; + +describe("ProjectsTable", () => { + const snsTitle = "SNS-1"; + const snsCanisterId = principal(1111); + + const renderComponent = () => { + const { container } = render(ProjectsTable); + return ProjectsTablePo.under(new JestPageObjectElement(container)); + }; + + beforeEach(() => { + neuronsStore.reset(); + snsNeuronsStore.reset(); + resetSnsProjects(); + + page.mock({ + routeId: AppPath.Staking, + }); + setSnsProjects([ + { + projectName: snsTitle, + rootCanisterId: snsCanisterId, + }, + ]); + }); + + it("should render desktop headers", async () => { + const po = renderComponent(); + expect(await po.getDesktopColumnHeaders()).toEqual([ + "Nervous Systems", + "Neurons", + "", // No header for actions column. + ]); + }); + + it("should render mobile headers", async () => { + const po = renderComponent(); + expect(await po.getMobileColumnHeaders()).toEqual([ + "Nervous Systems", + "", // No header for actions column. + ]); + }); + + it("should render cell alignment classes", async () => { + const po = renderComponent(); + const rows = await po.getRows(); + expect(await rows[0].getCellAlignments()).toEqual([ + "desktop-align-left", // Nervous Systems + "desktop-align-right", // Neurons + "desktop-align-right", // Actions + ]); + }); + + it("should use correct template columns", async () => { + const po = renderComponent(); + + expect(await po.getDesktopGridTemplateColumns()).toBe( + [ + "1fr", // Nerous Systems + "1fr", // Neurons + "max-content", // Actions + ].join(" ") + ); + expect(await po.getMobileGridTemplateAreas()).toBe( + '"first-cell last-cell" "cell-0 cell-0"' + ); + }); + + it("should render neurons URL", async () => { + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getHref()).toBe( + `/neurons/?u=${OWN_CANISTER_ID_TEXT}` + ); + expect(await rowPos[1].getHref()).toBe(`/neurons/?u=${snsCanisterId}`); + }); + + it("should render project title", async () => { + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getProjectTitle()).toBe("Internet Computer"); + expect(await rowPos[1].getProjectTitle()).toBe(snsTitle); + }); + + describe("neuron counts", () => { + const nnsNeuronWithStake = { + ...mockNeuron, + fullNeuron: { + ...mockNeuron.fullNeuron, + cachedNeuronStake: 100_000_000n, + }, + }; + + const nnsNeuronWithoutStake = { + ...mockNeuron, + fullNeuron: { + ...mockNeuron.fullNeuron, + cachedNeuronStake: 0n, + maturityE8sEquivalent: 0n, + }, + }; + + const snsNeuronWithStake = createMockSnsNeuron({ + stake: 100_000_000n, + id: [1, 1, 3], + }); + + const snsNeuronWithoutStake = createMockSnsNeuron({ + stake: 0n, + maturity: 0n, + id: [7, 7, 9], + }); + + it("should render NNS neurons count", async () => { + neuronsStore.setNeurons({ + neurons: [nnsNeuronWithStake, nnsNeuronWithStake], + certified: true, + }); + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getNeuronCount()).toBe("2"); + expect(await rowPos[1].getNeuronCount()).toBe("0"); + }); + + it("should render SNS neurons count", async () => { + snsNeuronsStore.setNeurons({ + rootCanisterId: snsCanisterId, + neurons: [snsNeuronWithStake, snsNeuronWithStake, snsNeuronWithStake], + certified: true, + }); + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getNeuronCount()).toBe("0"); + expect(await rowPos[1].getNeuronCount()).toBe("3"); + }); + + it("should filter NNS neurons without stake", async () => { + neuronsStore.setNeurons({ + neurons: [nnsNeuronWithStake, nnsNeuronWithoutStake], + certified: true, + }); + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getNeuronCount()).toBe("1"); + expect(await rowPos[1].getNeuronCount()).toBe("0"); + }); + + it("should filter SNS neurons without stake", async () => { + snsNeuronsStore.setNeurons({ + rootCanisterId: snsCanisterId, + neurons: [ + snsNeuronWithStake, + snsNeuronWithoutStake, + snsNeuronWithStake, + ], + certified: true, + }); + const po = renderComponent(); + const rowPos = await po.getProjectsTableRowPos(); + expect(rowPos).toHaveLength(2); + expect(await rowPos[0].getNeuronCount()).toBe("0"); + expect(await rowPos[1].getNeuronCount()).toBe("2"); + }); + }); + + it("should update table when universes store changes", async () => { + const po = renderComponent(); + + await runResolvedPromises(); + expect(await po.getProjectsTableRowPos()).toHaveLength(2); + + setSnsProjects([ + { + projectName: snsTitle, + rootCanisterId: snsCanisterId, + }, + { + projectName: "Another SNS", + rootCanisterId: principal(2222), + }, + ]); + + await runResolvedPromises(); + expect(await po.getProjectsTableRowPos()).toHaveLength(3); + }); +}); diff --git a/frontend/src/tests/lib/pages/SnsNeuronDetail.spec.ts b/frontend/src/tests/lib/pages/SnsNeuronDetail.spec.ts index c820cb1b5b6..d76e4233ad9 100644 --- a/frontend/src/tests/lib/pages/SnsNeuronDetail.spec.ts +++ b/frontend/src/tests/lib/pages/SnsNeuronDetail.spec.ts @@ -8,6 +8,7 @@ import { } from "$lib/constants/sns-neurons.constants"; import { pageStore } from "$lib/derived/page.derived"; import SnsNeuronDetail from "$lib/pages/SnsNeuronDetail.svelte"; +import * as checkNeuronsService from "$lib/services/sns-neurons-check-balances.services"; import { authStore } from "$lib/stores/auth.store"; import { icrcAccountsStore } from "$lib/stores/icrc-accounts.store"; import { snsFunctionsStore } from "$lib/stores/sns-functions.store"; @@ -131,6 +132,52 @@ describe("SnsNeuronDetail", () => { expect(await po.getFollowingCardPo().isPresent()).toBe(true); expect(await po.getHotkeysCardPo().isPresent()).toBe(true); }); + + it("should reload neuron if refreshed", async () => { + let resolveRefreshNeuronIfNeeded; + const spyRefreshNeuronIfNeeded = vi + .spyOn(checkNeuronsService, "refreshNeuronIfNeeded") + .mockImplementation( + () => + new Promise((resolve) => { + resolveRefreshNeuronIfNeeded = resolve; + }) + ); + + await renderComponent({ + neuronId: validNeuronIdAsHexString, + }); + + expect(spyRefreshNeuronIfNeeded).toBeCalled(); + expect(snsGovernanceApi.getSnsNeuron).toBeCalledTimes(2); + + resolveRefreshNeuronIfNeeded(true); + await runResolvedPromises(); + expect(snsGovernanceApi.getSnsNeuron).toBeCalledTimes(4); + }); + + it("should not reload neuron if not refreshed", async () => { + let resolveRefreshNeuronIfNeeded; + const spyRefreshNeuronIfNeeded = vi + .spyOn(checkNeuronsService, "refreshNeuronIfNeeded") + .mockImplementation( + () => + new Promise((resolve) => { + resolveRefreshNeuronIfNeeded = resolve; + }) + ); + + await renderComponent({ + neuronId: validNeuronIdAsHexString, + }); + + expect(spyRefreshNeuronIfNeeded).toBeCalled(); + expect(snsGovernanceApi.getSnsNeuron).toBeCalledTimes(2); + + resolveRefreshNeuronIfNeeded(false); + await runResolvedPromises(); + expect(snsGovernanceApi.getSnsNeuron).toBeCalledTimes(2); + }); }); describe("increase stake functionality", () => { diff --git a/frontend/src/tests/lib/pages/SnsNeurons.spec.ts b/frontend/src/tests/lib/pages/SnsNeurons.spec.ts index beac218e537..946ebad5cb7 100644 --- a/frontend/src/tests/lib/pages/SnsNeurons.spec.ts +++ b/frontend/src/tests/lib/pages/SnsNeurons.spec.ts @@ -1,10 +1,12 @@ import * as icrcLedgerApi from "$lib/api/icrc-ledger.api"; import * as snsGovernanceApi from "$lib/api/sns-governance.api"; import SnsNeurons from "$lib/pages/SnsNeurons.svelte"; +import { checkedNeuronSubaccountsStore } from "$lib/stores/checked-neurons.store"; import { overrideFeatureFlagsStore } from "$lib/stores/feature-flags.store"; import { snsParametersStore } from "$lib/stores/sns-parameters.store"; +import { enumValues } from "$lib/utils/enum.utils"; import { page } from "$mocks/$app/stores"; -import { resetIdentity } from "$tests/mocks/auth.store.mock"; +import { mockIdentity, resetIdentity } from "$tests/mocks/auth.store.mock"; import { mockSnsMainAccount } from "$tests/mocks/sns-accounts.mock"; import { createMockSnsNeuron, @@ -15,8 +17,13 @@ import { SnsNeuronsPo } from "$tests/page-objects/SnsNeurons.page-object"; import { JestPageObjectElement } from "$tests/page-objects/jest.page-object"; import { setSnsProjects } from "$tests/utils/sns.test-utils"; import { runResolvedPromises } from "$tests/utils/timers.test-utils"; -import type { SnsNeuron } from "@dfinity/sns"; -import { SnsSwapLifecycle } from "@dfinity/sns"; +import { + SnsNeuronPermissionType, + SnsSwapLifecycle, + neuronSubaccount, + type SnsNeuron, + type SnsNeuronId, +} from "@dfinity/sns"; import { render } from "@testing-library/svelte"; vi.mock("$lib/api/sns-governance.api"); @@ -44,20 +51,10 @@ describe("SnsNeurons", () => { source_nns_neuron_id: [123n], }; const projectName = "Tetris"; - const getNeuronBalanceMock = async ({ neuronId }) => { - if (neuronId === neuron1.id[0]) { - return neuron1Stake; - } - if (neuronId === disbursedNeuron.id[0]) { - return disbursedNeuronStake; - } - if (neuronId === neuronNF.id[0]) { - return neuronNFStake; - } - }; beforeEach(() => { vi.clearAllMocks(); + checkedNeuronSubaccountsStore.reset(); overrideFeatureFlagsStore.reset(); page.mock({ data: { universe: rootCanisterId.toText() } }); resetIdentity(); @@ -77,6 +74,7 @@ describe("SnsNeurons", () => { projectName, }, ]); + vi.spyOn(snsGovernanceApi, "getNeuronBalance").mockResolvedValue(0n); }); const renderComponent = async () => { @@ -114,9 +112,6 @@ describe("SnsNeurons", () => { neuron1, neuronNF, ]); - vi.spyOn(snsGovernanceApi, "getNeuronBalance").mockImplementation( - getNeuronBalanceMock - ); }); it("should render the neurons table", async () => { @@ -149,6 +144,73 @@ describe("SnsNeurons", () => { const rows = await po.getNeuronsTablePo().getNeuronsTableRowPos(); expect(rows).toHaveLength(2); }); + + it("should claim unclaimed neuron", async () => { + const unclaimedNeuronIndex1 = 0; + const unclaimedNeuronIndex2 = 1; + const unclaimedNeuronSubaccount1 = neuronSubaccount({ + controller: mockIdentity.getPrincipal(), + index: unclaimedNeuronIndex1, + }); + const unclaimedNeuronSubaccount2 = neuronSubaccount({ + controller: mockIdentity.getPrincipal(), + index: unclaimedNeuronIndex2, + }); + const unclaimedNeuronId1: SnsNeuronId = { + id: unclaimedNeuronSubaccount1, + }; + const unclaimedNeuronId2: SnsNeuronId = { + id: unclaimedNeuronSubaccount2, + }; + const unclaimedNeuron1 = createMockSnsNeuron({ + id: Array.from(unclaimedNeuronId1.id), + permissions: [ + { + principal: [mockIdentity.getPrincipal()], + permission_type: Int32Array.from( + enumValues(SnsNeuronPermissionType) + ), + }, + ], + }); + + const spyGetNeuronBalance = vi + .spyOn(snsGovernanceApi, "getNeuronBalance") + .mockResolvedValueOnce(100_000_000n) + .mockResolvedValueOnce(0n); + const spyClaimNeuron = vi + .spyOn(snsGovernanceApi, "claimNeuron") + .mockResolvedValue(unclaimedNeuronId1); + vi.spyOn(snsGovernanceApi, "getSnsNeuron").mockResolvedValue( + unclaimedNeuron1 + ); + + expect(spyGetNeuronBalance).toBeCalledTimes(0); + + await renderComponent(); + + expect(spyGetNeuronBalance).toBeCalledTimes(2); + expect(spyGetNeuronBalance).toBeCalledWith({ + rootCanisterId, + neuronId: unclaimedNeuronId1, + certified: false, + identity: mockIdentity, + }); + expect(spyGetNeuronBalance).toBeCalledWith({ + rootCanisterId, + neuronId: unclaimedNeuronId2, + certified: false, + identity: mockIdentity, + }); + expect(spyClaimNeuron).toBeCalledTimes(1); + expect(spyClaimNeuron).toBeCalledWith({ + rootCanisterId, + subaccount: unclaimedNeuronSubaccount1, + memo: BigInt(unclaimedNeuronIndex1), + controller: mockIdentity.getPrincipal(), + identity: mockIdentity, + }); + }); }); describe("no neurons", () => { diff --git a/frontend/src/tests/lib/pages/Tokens.spec.ts b/frontend/src/tests/lib/pages/Tokens.spec.ts index 5cba7122496..ea857a73dc0 100644 --- a/frontend/src/tests/lib/pages/Tokens.spec.ts +++ b/frontend/src/tests/lib/pages/Tokens.spec.ts @@ -190,4 +190,38 @@ describe("Tokens page", () => { "Zero balance", ]); }); + + describe("when import token feature flag is enabled", () => { + beforeEach(() => { + overrideFeatureFlagsStore.setFlag("ENABLE_IMPORT_TOKEN", true); + }); + + it("should show import token button", async () => { + const po = renderPage([positiveBalance, zeroBalance]); + expect(await po.getImportTokenButtonPo().isPresent()).toBe(true); + }); + + it("should show import token and show all buttons", async () => { + const po = renderPage([positiveBalance, zeroBalance]); + + expect(await po.getShowAllButtonPo().isPresent()).toBe(false); + + await po.getSettingsButtonPo().click(); + await po.getHideZeroBalancesTogglePo().getTogglePo().toggle(); + + expect(await po.getShowAllButtonPo().isPresent()).toBe(true); + expect(await po.getImportTokenButtonPo().isPresent()).toBe(true); + }); + }); + + describe("when import token feature flag is disabled", () => { + beforeEach(() => { + overrideFeatureFlagsStore.setFlag("ENABLE_IMPORT_TOKEN", false); + }); + + it("should not show import token button", async () => { + const po = renderPage([positiveBalance, zeroBalance]); + expect(await po.getImportTokenButtonPo().isPresent()).toBe(false); + }); + }); }); diff --git a/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts b/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts index b9f49c71109..6f18b26be17 100644 --- a/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts +++ b/frontend/src/tests/lib/services/actionable-proposals.services.spec.ts @@ -11,8 +11,14 @@ import { import { mockNeuron } from "$tests/mocks/neurons.mock"; import { mockProposalInfo } from "$tests/mocks/proposal.mock"; import { silentConsoleErrors } from "$tests/utils/utils.test-utils"; -import type { NeuronInfo, ProposalInfo } from "@dfinity/nns"; -import { ProposalRewardStatus, Vote } from "@dfinity/nns"; +import { + ProposalRewardStatus, + ProposalStatus, + Topic, + Vote, + type NeuronInfo, + type ProposalInfo, +} from "@dfinity/nns"; import { get } from "svelte/store"; describe("actionable-proposals.services", () => { @@ -21,6 +27,32 @@ describe("actionable-proposals.services", () => { }); describe("updateActionableProposals", () => { + const expectedQueryManageNeuronProposalsParams = { + identity: mockIdentity, + beforeProposal: undefined, + certified: false, + includeStatus: [ProposalStatus.Open], + includeTopics: [Topic.ManageNeuron], + includeRewardStatus: [ProposalRewardStatus.Ineligible], + }; + const callLoadActionableProposals = async ({ + queryProposalsResponses, + expectedQueryProposalsParams = [], + }: { + queryProposalsResponses: ProposalInfo[][]; + expectedQueryProposalsParams?: Array; + }) => { + spyQueryProposals = vi.spyOn(api, "queryProposals"); + for (const response of queryProposalsResponses) { + spyQueryProposals.mockResolvedValueOnce(response); + } + + await loadActionableProposals(); + + for (const params of expectedQueryProposalsParams) { + expect(spyQueryProposals).toHaveBeenCalledWith(params); + } + }; const votedProposalId = 1234n; const neuronId = 0n; const neuron1: NeuronInfo = { @@ -60,8 +92,13 @@ describe("actionable-proposals.services", () => { ); spyQueryProposals = vi .spyOn(api, "queryProposals") - .mockImplementation(() => - Promise.resolve([votableProposal, votedProposal]) + .mockImplementation((requestData) => + requestData.includeRewardStatus?.includes( + ProposalRewardStatus.AcceptVotes + ) + ? Promise.resolve([votableProposal, votedProposal]) + : // Return nothing for Topic.ManageNeuron proposals + Promise.resolve([]) ); spyQueryNeurons = vi .spyOn(governanceApi, "queryNeurons") @@ -98,7 +135,7 @@ describe("actionable-proposals.services", () => { await loadActionableProposals(); - expect(spyQueryProposals).toHaveBeenCalledTimes(1); + expect(spyQueryProposals).toHaveBeenCalledTimes(2); expect(spyQueryProposals).toHaveBeenCalledWith( expect.objectContaining({ beforeProposal: undefined, @@ -106,6 +143,9 @@ describe("actionable-proposals.services", () => { includeRewardStatus: [ProposalRewardStatus.AcceptVotes], }) ); + expect(spyQueryProposals).toHaveBeenCalledWith( + expectedQueryManageNeuronProposalsParams + ); }); it("should query list proposals using multiple calls", async () => { @@ -120,7 +160,7 @@ describe("actionable-proposals.services", () => { await loadActionableProposals(); - expect(spyQueryProposals).toHaveBeenCalledTimes(2); + expect(spyQueryProposals).toHaveBeenCalledTimes(3); expect(spyQueryProposals).toHaveBeenCalledWith({ identity: mockIdentity, beforeProposal: undefined, @@ -134,6 +174,9 @@ describe("actionable-proposals.services", () => { certified: false, includeRewardStatus: [ProposalRewardStatus.AcceptVotes], }); + expect(spyQueryProposals).toHaveBeenCalledWith( + expectedQueryManageNeuronProposalsParams + ); expect(get(actionableNnsProposalsStore)?.proposals?.length).toEqual(101); expect(get(actionableNnsProposalsStore)?.proposals).toEqual([ ...firstResponseProposals, @@ -155,7 +198,7 @@ describe("actionable-proposals.services", () => { await loadActionableProposals(); - expect(spyQueryProposals).toHaveBeenCalledTimes(5); + expect(spyQueryProposals).toHaveBeenCalledTimes(6); // expect an error message expect(spyConsoleError).toHaveBeenCalledTimes(1); expect(spyConsoleError).toHaveBeenCalledWith( @@ -179,5 +222,128 @@ describe("actionable-proposals.services", () => { proposals: [votableProposal], }); }); + + describe("ManageNeurons proposals", () => { + const proposal0 = { + ...mockProposalInfo, + id: 0n, + } as ProposalInfo; + const proposal1 = { + ...mockProposalInfo, + id: 1n, + } as ProposalInfo; + const proposal2 = { + ...mockProposalInfo, + id: 2n, + } as ProposalInfo; + + it("should query list proposals also with ManageNeurons payload", async () => { + await callLoadActionableProposals({ + queryProposalsResponses: [[], [proposal1, proposal0]], + expectedQueryProposalsParams: [ + { + identity: mockIdentity, + beforeProposal: undefined, + certified: false, + includeRewardStatus: [ProposalRewardStatus.AcceptVotes], + }, + expectedQueryManageNeuronProposalsParams, + ], + }); + + expect(spyQueryProposals).toHaveBeenCalledTimes(2); + }); + + it("should combine and sort votable AcceptedRewards with votable ManageNeurons proposals", async () => { + await callLoadActionableProposals({ + queryProposalsResponses: [[proposal1], [proposal2, proposal0]], + }); + + expect(spyQueryProposals).toHaveBeenCalledTimes(2); + + const storeProposals = get(actionableNnsProposalsStore)?.proposals; + expect(storeProposals.length).toEqual(3); + expect(storeProposals).toEqual([proposal2, proposal1, proposal0]); + }); + + it("should request ManageNeurons proposals using multiple calls", async () => { + const firstResponseProposals = fiveHundredsProposal.slice(0, 100); + const secondResponseProposals = [fiveHundredsProposal[100]]; + + await callLoadActionableProposals({ + queryProposalsResponses: [ + [], + firstResponseProposals, + secondResponseProposals, + ], + expectedQueryProposalsParams: [ + { + identity: mockIdentity, + beforeProposal: undefined, + certified: false, + includeRewardStatus: [ProposalRewardStatus.AcceptVotes], + }, + expectedQueryManageNeuronProposalsParams, + { + ...expectedQueryManageNeuronProposalsParams, + beforeProposal: + firstResponseProposals[firstResponseProposals.length - 1].id, + }, + ], + }); + + expect(spyQueryProposals).toHaveBeenCalledTimes(3); + expect(get(actionableNnsProposalsStore)?.proposals?.length).toEqual( + 101 + ); + expect(get(actionableNnsProposalsStore)?.proposals).toEqual([ + ...firstResponseProposals, + ...secondResponseProposals, + ]); + }); + + it("should log an error when request count limit reached", async () => { + spyConsoleError = silentConsoleErrors(); + expect(spyConsoleError).not.toHaveBeenCalled(); + + await callLoadActionableProposals({ + queryProposalsResponses: [ + [], + fiveHundredsProposal.slice(0, 100), + fiveHundredsProposal.slice(100, 200), + fiveHundredsProposal.slice(200, 300), + fiveHundredsProposal.slice(300, 400), + fiveHundredsProposal.slice(400, 500), + ], + }); + + expect(spyQueryProposals).toHaveBeenCalledTimes(6); + // expect an error message + expect(spyConsoleError).toHaveBeenCalledTimes(1); + expect(spyConsoleError).toHaveBeenCalledWith( + "Max actionable pages loaded" + ); + expect(get(actionableNnsProposalsStore)?.proposals?.length).toEqual( + 500 + ); + expect(get(actionableNnsProposalsStore)?.proposals).toEqual( + fiveHundredsProposal + ); + }); + + it("should update actionable nns proposals store only with votable Neuron Management proposals", async () => { + expect(get(actionableNnsProposalsStore)).toEqual({ + proposals: undefined, + }); + + await callLoadActionableProposals({ + queryProposalsResponses: [[], [votableProposal, votedProposal]], + }); + + expect(get(actionableNnsProposalsStore)).toEqual({ + proposals: [votableProposal], + }); + }); + }); }); }); diff --git a/frontend/src/tests/lib/services/actionable-sns-proposals.services.spec.ts b/frontend/src/tests/lib/services/actionable-sns-proposals.services.spec.ts index d02d40dac23..289325590ca 100644 --- a/frontend/src/tests/lib/services/actionable-sns-proposals.services.spec.ts +++ b/frontend/src/tests/lib/services/actionable-sns-proposals.services.spec.ts @@ -6,6 +6,7 @@ import { failedActionableSnsesStore, } from "$lib/stores/actionable-sns-proposals.store"; import { authStore } from "$lib/stores/auth.store"; +import { snsNeuronsStore } from "$lib/stores/sns-neurons.store"; import { enumValues } from "$lib/utils/enum.utils"; import { getSnsNeuronIdAsHexString } from "$lib/utils/sns-neuron.utils"; import { snsProposalId } from "$lib/utils/sns-proposals.utils"; @@ -39,6 +40,7 @@ describe("actionable-sns-proposals.services", () => { beforeEach(() => { vi.restoreAllMocks(); failedActionableSnsesStore.resetForTesting(); + snsNeuronsStore.reset(); }); describe("loadActionableProposalsForSns", () => { @@ -166,6 +168,8 @@ describe("actionable-sns-proposals.services", () => { mockSnsProjectsCommittedStore([rootCanisterId1, rootCanisterId2]); expect(spyQuerySnsNeurons).not.toHaveBeenCalled(); + expect(get(snsNeuronsStore)).toEqual({}); + await loadActionableSnsProposals(); expect(spyQuerySnsNeurons).toHaveBeenCalledTimes(2); @@ -179,6 +183,17 @@ describe("actionable-sns-proposals.services", () => { rootCanisterId: rootCanisterId2, certified: false, }); + + expect(get(snsNeuronsStore)).toEqual({ + [rootCanisterId1.toText()]: { + certified: false, + neurons: [neuron], + }, + [rootCanisterId2.toText()]: { + certified: false, + neurons: [neuron], + }, + }); }); it("should query proposals per sns with accept rewards status only", async () => { diff --git a/frontend/src/tests/lib/services/sns-neurons-check-balances.services.spec.ts b/frontend/src/tests/lib/services/sns-neurons-check-balances.services.spec.ts index dd864a3d9f6..53c8d06dd44 100644 --- a/frontend/src/tests/lib/services/sns-neurons-check-balances.services.spec.ts +++ b/frontend/src/tests/lib/services/sns-neurons-check-balances.services.spec.ts @@ -1,9 +1,12 @@ import * as governanceApi from "$lib/api/sns-governance.api"; import { - checkSnsNeuronBalances, + claimNextNeuronIfNeeded, neuronNeedsRefresh, + refreshNeuronIfNeeded, } from "$lib/services/sns-neurons-check-balances.services"; +import { checkedNeuronSubaccountsStore } from "$lib/stores/checked-neurons.store"; import { snsNeuronsStore } from "$lib/stores/sns-neurons.store"; +import { snsParametersStore } from "$lib/stores/sns-parameters.store"; import { enumValues } from "$lib/utils/enum.utils"; import { subaccountToHexString } from "$lib/utils/sns-neuron.utils"; import { @@ -11,7 +14,10 @@ import { mockPrincipal, resetIdentity, } from "$tests/mocks/auth.store.mock"; -import { mockSnsNeuron } from "$tests/mocks/sns-neurons.mock"; +import { + mockSnsNeuron, + snsNervousSystemParametersMock, +} from "$tests/mocks/sns-neurons.mock"; import { principal } from "$tests/mocks/sns-projects.mock"; import { SnsNeuronPermissionType, @@ -58,147 +64,230 @@ describe("sns-neurons-check-balances-services", () => { }; beforeEach(() => { + vi.restoreAllMocks(); resetIdentity(); + snsNeuronsStore.reset(); + checkedNeuronSubaccountsStore.reset(); + snsParametersStore.reset(); }); - describe("checkSnsNeuronBalances", () => { - beforeEach(() => { - vi.clearAllMocks(); - snsNeuronsStore.reset(); - vi.spyOn(console, "error").mockImplementation(() => undefined); - }); - - it("should check balance and not refresh when balance matches stake", async () => { - const spyQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockResolvedValue(userNeuron); + describe("neuronNeedsRefresh", () => { + it("should query the balance and return true when balance does not match stake", async () => { const spyNeuronBalance = vi .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(userNeuron.cached_neuron_stake_e8s) - ) - .mockImplementation(() => Promise.resolve(0n)); - await checkSnsNeuronBalances({ + .mockImplementation(() => + Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s + 10_000n) + ); + const res = await neuronNeedsRefresh({ rootCanisterId: mockPrincipal, - neurons: [userNeuron], - neuronMinimumStake: 100_000_000n, + neuron: mockSnsNeuron, + identity: mockIdentity, }); - await waitFor(() => expect(spyNeuronBalance).toBeCalled()); - expect(spyQuery).not.toBeCalled(); + expect(res).toBe(true); }); - it("should check balance and refresh when balance does not match stake and load the updated neuron in the store", async () => { - const stake = userNeuron.cached_neuron_stake_e8s + 10_000n; - const updatedNeuron = { - ...userNeuron, - cached_neuron_stake_e8s: stake, - }; - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockResolvedValue(updatedNeuron); + it("should query the balance and return false when balance matches stake", async () => { const spyNeuronBalance = vi .spyOn(governanceApi, "getNeuronBalance") - .mockResolvedValueOnce(stake) - .mockResolvedValue(0n); - const spyRefreshNeuron = vi + .mockImplementation(() => + Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) + ); + const res = await neuronNeedsRefresh({ + rootCanisterId: mockPrincipal, + neuron: mockSnsNeuron, + identity: mockIdentity, + }); + await waitFor(() => expect(spyNeuronBalance).toBeCalled()); + expect(res).toBe(false); + }); + }); + + describe("refreshNeuronIfNeeded", () => { + let spyNeuronBalance; + let spyRefreshNeuron; + + beforeEach(() => { + spyRefreshNeuron = vi .spyOn(governanceApi, "refreshNeuron") .mockResolvedValue(undefined); + }); - expect(spyRefreshNeuron).not.toBeCalled(); - expect(spyNeuronQuery).not.toBeCalled(); - expect(spyNeuronBalance).not.toBeCalled(); + it("should not refresh neuron when balance matches stake", async () => { + spyNeuronBalance = vi + .spyOn(governanceApi, "getNeuronBalance") + .mockImplementation(() => + Promise.resolve(userNeuron.cached_neuron_stake_e8s) + ); + expect( + await refreshNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neuron: userNeuron, + }) + ).toBe(false); - await checkSnsNeuronBalances({ + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNeuronBalance).toBeCalledWith({ rootCanisterId: mockPrincipal, - neurons: [userNeuron], - neuronMinimumStake: 100_000_000n, + neuronId: neuronId, + certified: false, + identity: mockIdentity, }); + expect(spyRefreshNeuron).not.toBeCalled(); + }); - await waitFor(() => expect(spyRefreshNeuron).toBeCalled()); - expect(spyNeuronQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); + it("should refresh neuron when balance does not match stake", async () => { + const rootCanisterId = mockPrincipal; + vi.spyOn(governanceApi, "getSnsNeuron").mockResolvedValue(userNeuron); + spyNeuronBalance = vi + .spyOn(governanceApi, "getNeuronBalance") + .mockImplementation(() => + Promise.resolve(userNeuron.cached_neuron_stake_e8s + 100_000_000n) + ); + expect( + await refreshNeuronIfNeeded({ + rootCanisterId, + neuron: userNeuron, + }) + ).toBe(true); - const store = get(snsNeuronsStore); - expect(store[mockPrincipal.toText()].neurons).toEqual([updatedNeuron]); + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNeuronBalance).toBeCalledWith({ + rootCanisterId, + neuronId: neuronId, + certified: false, + identity: mockIdentity, + }); + expect(spyRefreshNeuron).toBeCalledTimes(1); + expect(spyRefreshNeuron).toBeCalledWith({ + rootCanisterId, + neuronId, + identity: mockIdentity, + }); }); - it("should check balance and refresh when balance is 0 and does not match stake and load the updated neuron in the store", async () => { - const updatedNeuron = { - ...userNeuron, - cached_neuron_stake_e8s: 0n, - }; - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockResolvedValue(updatedNeuron); - const spyNeuronBalance = vi + it("should check neuron only once", async () => { + spyNeuronBalance = vi .spyOn(governanceApi, "getNeuronBalance") - .mockResolvedValue(0n); - const spyRefreshNeuron = vi - .spyOn(governanceApi, "refreshNeuron") - .mockResolvedValue(undefined); + .mockImplementation(() => + Promise.resolve(userNeuron.cached_neuron_stake_e8s) + ); + expect( + await refreshNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neuron: userNeuron, + }) + ).toBe(false); + expect( + await refreshNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neuron: userNeuron, + }) + ).toBe(false); + expect( + await refreshNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neuron: userNeuron, + }) + ).toBe(false); + expect(spyNeuronBalance).toBeCalledTimes(1); expect(spyRefreshNeuron).not.toBeCalled(); - expect(spyNeuronQuery).not.toBeCalled(); - expect(spyNeuronBalance).not.toBeCalled(); + }); + }); + + describe("claimNextNeuronIfNeeded", () => { + let spyNeuronBalance; + let spyNervousSystemParameters; + let spyClaimNeuron; + let spyGetSnsNeuron; + + beforeEach(() => { + spyNeuronBalance = vi.spyOn(governanceApi, "getNeuronBalance"); + spyNervousSystemParameters = vi.spyOn( + governanceApi, + "nervousSystemParameters" + ); + spyClaimNeuron = vi.spyOn(governanceApi, "claimNeuron"); + spyGetSnsNeuron = vi.spyOn(governanceApi, "getSnsNeuron"); + }); + + it("should not load sns params for zero balance", async () => { + const neuronAccountBalance = 0n; + + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + spyNervousSystemParameters.mockResolvedValue( + snsNervousSystemParametersMock + ); + spyClaimNeuron.mockRejectedValue("Neuron does not exist"); + + expect(spyNeuronBalance).toBeCalledTimes(0); + + await claimNextNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neurons: [], + }); - await checkSnsNeuronBalances({ + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNeuronBalance).toBeCalledWith({ rootCanisterId: mockPrincipal, - neurons: [userNeuron], - neuronMinimumStake: 100_000_000n, + neuronId: neuronId, + certified: false, + identity: mockIdentity, + }); + expect(spyNervousSystemParameters).not.toBeCalled(); + expect(spyClaimNeuron).not.toBeCalled(); + }); + + it("should not load sns parameters if already loaded", async () => { + const neuronAccountBalance = 1_000_000n; + + snsParametersStore.setParameters({ + rootCanisterId: mockPrincipal, + parameters: snsNervousSystemParametersMock, + certified: true, }); - await waitFor(() => expect(spyRefreshNeuron).toBeCalled()); - expect(spyNeuronQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + + await claimNextNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neurons: [], + }); - const store = get(snsNeuronsStore); - expect(store[mockPrincipal.toText()].neurons).toEqual([updatedNeuron]); + expect(spyNervousSystemParameters).not.toBeCalled(); }); - it("should not refresh nor load neurons where the user is not the controller anymore", async () => { - const cachedStakeMapper = { - [subaccountToHexString(subaccountTransferredNeuron)]: - transferredNeuron.cached_neuron_stake_e8s, - [subaccountToHexString(subaccount)]: userNeuron.cached_neuron_stake_e8s, - }; - // Only the transferred neuron will be fetched because the other is passed as a parameter. - const spyNeuronQuery = vi - .spyOn(governanceApi, "querySnsNeuron") - .mockResolvedValue(transferredNeuron); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementation(async ({ neuronId }) => { - return cachedStakeMapper[subaccountToHexString(neuronId.id)] ?? 0n; - }); - const spyRefreshNeuron = vi - .spyOn(governanceApi, "refreshNeuron") - .mockResolvedValue(undefined); + it("should not claim neuron if balance too small", async () => { + const neuronMinimumStake = 100_000_000n; + const neuronAccountBalance = 1_000_000n; - expect(spyRefreshNeuron).not.toBeCalled(); - expect(spyNeuronQuery).not.toBeCalled(); - expect(spyNeuronBalance).not.toBeCalled(); + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + spyNervousSystemParameters.mockResolvedValue({ + ...snsNervousSystemParametersMock, + neuron_minimum_stake_e8s: [neuronMinimumStake], + }); + spyClaimNeuron.mockRejectedValue("Neuron does not exist"); + + expect(spyNeuronBalance).toBeCalledTimes(0); + expect(spyNervousSystemParameters).toBeCalledTimes(0); - await checkSnsNeuronBalances({ + await claimNextNeuronIfNeeded({ rootCanisterId: mockPrincipal, - neurons: [userNeuron], - neuronMinimumStake: 100_000_000n, + neurons: [], }); - expect(spyRefreshNeuron).not.toBeCalled(); - // One for the subaccount with balance found. - expect(spyNeuronQuery).toBeCalledTimes(1); - // Three times: one for each subaccount with balance and one more to check that there are no more neurons with balance. - expect(spyNeuronBalance).toBeCalledTimes(3); - // No new neurons will be loaded because the user is not the controller anymore. - expect(get(snsNeuronsStore)[mockPrincipal.toText()]).toBeUndefined(); + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNervousSystemParameters).toBeCalledTimes(2); + expect(spyClaimNeuron).not.toBeCalled(); }); - it("should claim a neuron where the user is the controller after finding a neuron that is not the controller of", async () => { - const unclaimedNeuronBalance = 500_000_000n; + it("should claim and load neuron with sufficient balance", async () => { + const neuronMinimumStake = 100_000_000n; + const neuronAccountBalance = neuronMinimumStake; const subaccountUnclaimedNeuron = neuronSubaccount({ controller: mockIdentity.getPrincipal(), - index: 2, + index: 0, }); const unclaimedNeuronId: SnsNeuronId = { id: subaccountUnclaimedNeuron, @@ -206,138 +295,201 @@ describe("sns-neurons-check-balances-services", () => { const unclaimedNeuron: SnsNeuron = { ...userNeuron, id: [unclaimedNeuronId] as [SnsNeuronId], - cached_neuron_stake_e8s: unclaimedNeuronBalance, - }; - const cachedStakeMapper = { - [subaccountToHexString(subaccountTransferredNeuron)]: - transferredNeuron.cached_neuron_stake_e8s, - [subaccountToHexString(subaccount)]: userNeuron.cached_neuron_stake_e8s, - [subaccountToHexString(subaccountUnclaimedNeuron)]: - unclaimedNeuron.cached_neuron_stake_e8s, + cached_neuron_stake_e8s: neuronAccountBalance, }; - const queryNeuronMapper = { - [subaccountToHexString(subaccountTransferredNeuron)]: transferredNeuron, - }; - // Only the transferred neuron will be fetched because the other is passed as a parameter. - const spyNeuronQuery = vi - .spyOn(governanceApi, "querySnsNeuron") - .mockImplementation( - async ({ neuronId }) => - queryNeuronMapper[subaccountToHexString(neuronId.id)] ?? undefined - ); - const spyNeuronGetter = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockResolvedValue(unclaimedNeuron); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementation(async ({ neuronId }) => { - return cachedStakeMapper[subaccountToHexString(neuronId.id)] ?? 0n; - }); - const spyClaimNeuron = vi - .spyOn(governanceApi, "claimNeuron") - .mockResolvedValue(unclaimedNeuronId); - expect(spyClaimNeuron).not.toBeCalled(); - expect(spyNeuronGetter).not.toBeCalled(); - expect(spyNeuronQuery).not.toBeCalled(); - expect(spyNeuronBalance).not.toBeCalled(); - await checkSnsNeuronBalances({ + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + spyNervousSystemParameters.mockResolvedValue({ + ...snsNervousSystemParametersMock, + neuron_minimum_stake_e8s: [neuronMinimumStake], + }); + spyClaimNeuron.mockResolvedValue(unclaimedNeuronId); + spyGetSnsNeuron.mockResolvedValue(unclaimedNeuron); + + expect(spyNeuronBalance).toBeCalledTimes(0); + expect(spyNervousSystemParameters).toBeCalledTimes(0); + expect(spyClaimNeuron).toBeCalledTimes(0); + expect( + get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons + ).toBeUndefined(); + + await claimNextNeuronIfNeeded({ rootCanisterId: mockPrincipal, - neurons: [userNeuron], - neuronMinimumStake: 100_000_000n, + neurons: [], }); + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNervousSystemParameters).toBeCalledTimes(2); expect(spyClaimNeuron).toBeCalledTimes(1); - // One for each subaccount with balance found and the neuron was not present in the parameters. - expect(spyNeuronQuery).toBeCalledTimes(2); - expect(spyNeuronGetter).toBeCalledTimes(1); - // Four times: one for each subaccount with balance and one more to check that there are no more neurons with balance. - expect(spyNeuronBalance).toBeCalledTimes(4); - // The unclaimed neurons will be loaded. + expect(spyClaimNeuron).toBeCalledWith({ + controller: mockIdentity.getPrincipal(), + identity: mockIdentity, + memo: 0n, + rootCanisterId: mockPrincipal, + subaccount: subaccountUnclaimedNeuron, + }); expect(get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons).toEqual([ unclaimedNeuron, ]); }); - it("should refresh neurons not created in NNS Dapp and load them in store", async () => { - const nonNnsDappSubaccount = neuronSubaccount({ - controller: principal(0), - index: 0, - }); - const neuronId: SnsNeuronId = { id: nonNnsDappSubaccount }; - const nonNnsDappNeuron: SnsNeuron = { - ...userNeuron, - id: [neuronId] as [SnsNeuronId], - }; - const stake = nonNnsDappNeuron.cached_neuron_stake_e8s + 200_000_000n; - const updatedNeuron = { - ...nonNnsDappNeuron, - cached_neuron_stake_e8s: stake, - }; - const cachedStakeMapper = { - [subaccountToHexString(nonNnsDappSubaccount)]: stake, - [subaccountToHexString(subaccount)]: userNeuron.cached_neuron_stake_e8s, - }; - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockResolvedValue(updatedNeuron); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementation( - async ({ neuronId }) => - cachedStakeMapper[subaccountToHexString(neuronId.id)] ?? 0n - ); - const spyRefreshNeuron = vi - .spyOn(governanceApi, "refreshNeuron") - .mockResolvedValue(undefined); + it("should populate checkedNeuronSubaccountsStore", async () => { + const neuronAccountBalance = 0n; - expect(spyRefreshNeuron).not.toBeCalled(); - expect(spyNeuronQuery).not.toBeCalled(); - expect(spyNeuronBalance).not.toBeCalled(); - await checkSnsNeuronBalances({ + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + + expect(get(checkedNeuronSubaccountsStore)).toEqual({}); + + await claimNextNeuronIfNeeded({ rootCanisterId: mockPrincipal, - neurons: [userNeuron, nonNnsDappNeuron], - neuronMinimumStake: 100_000_000n, + neurons: [], }); - expect(spyRefreshNeuron).toBeCalledTimes(1); - expect(spyNeuronQuery).toBeCalledTimes(1); - expect(spyNeuronBalance).toBeCalledTimes(3); + expect(get(checkedNeuronSubaccountsStore)).toEqual({ + [mockPrincipal.toText()]: { + [subaccountToHexString(neuronId.id)]: true, + }, + }); + }); + + it("should not load balance if neuron is already checked", async () => { + checkedNeuronSubaccountsStore.addSubaccount({ + universeId: mockPrincipal.toText(), + subaccountHex: subaccountToHexString(neuronId.id), + }); + + await claimNextNeuronIfNeeded({ + rootCanisterId: mockPrincipal, + neurons: [], + }); - const store = get(snsNeuronsStore); - expect(store[mockPrincipal.toText()].neurons).toEqual([updatedNeuron]); + expect(spyNeuronBalance).not.toBeCalled(); }); - }); - describe("neuronNeedsRefresh", () => { - it("should query the balance and return true when balance does not match stake", async () => { - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementation(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s + 10_000n) - ); - const res = await neuronNeedsRefresh({ + it("should claim next neuron after existing neurons", async () => { + const neuronMinimumStake = 100_000_000n; + const neuronAccountBalance = neuronMinimumStake; + + const createNeuron = (index: number): SnsNeuron => { + const subaccount = neuronSubaccount({ + controller: mockIdentity.getPrincipal(), + index, + }); + return { + ...userNeuron, + id: [{ id: subaccount }], + cached_neuron_stake_e8s: neuronAccountBalance, + }; + }; + const existingNeurons = [ + createNeuron(0), + createNeuron(1), + createNeuron(2), + ]; + const unclaimedNeuron = createNeuron(3); + const unclaimedNeuronId = unclaimedNeuron.id[0]; + const unclaimedSubaccount = unclaimedNeuronId.id; + + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + spyNervousSystemParameters.mockResolvedValue( + snsNervousSystemParametersMock + ); + spyClaimNeuron.mockResolvedValue(unclaimedNeuronId); + spyGetSnsNeuron.mockResolvedValue(unclaimedNeuron); + + expect(spyNeuronBalance).toBeCalledTimes(0); + expect(spyNervousSystemParameters).toBeCalledTimes(0); + expect(spyClaimNeuron).toBeCalledTimes(0); + expect( + get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons + ).toBeUndefined(); + await claimNextNeuronIfNeeded({ rootCanisterId: mockPrincipal, - neuron: mockSnsNeuron, + neurons: existingNeurons, + }); + + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNeuronBalance).toBeCalledWith({ + rootCanisterId: mockPrincipal, + neuronId: unclaimedNeuronId, + certified: false, identity: mockIdentity, }); - await waitFor(() => expect(spyNeuronBalance).toBeCalled()); - expect(res).toBe(true); + expect(spyNervousSystemParameters).toBeCalledTimes(2); + expect(spyClaimNeuron).toBeCalledTimes(1); + expect(spyClaimNeuron).toBeCalledWith({ + controller: mockIdentity.getPrincipal(), + identity: mockIdentity, + memo: 3n, + rootCanisterId: mockPrincipal, + subaccount: unclaimedSubaccount, + }); + expect(get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons).toEqual([ + unclaimedNeuron, + ]); + expect(get(checkedNeuronSubaccountsStore)).toEqual({ + [mockPrincipal.toText()]: { + [subaccountToHexString(unclaimedSubaccount)]: true, + }, + }); }); - it("should query the balance and return false when balance matches stake", async () => { - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementation(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) - ); - const res = await neuronNeedsRefresh({ + it("should not add neuron to store if no permissions", async () => { + // It's possible to transfer a neuron to another user. In this case the + // permissions of the original user will have been removed. Such a neuron + // should no longer appear in the store (and thus UI) of the original + // user. + const neuronMinimumStake = 100_000_000n; + const neuronAccountBalance = neuronMinimumStake; + const subaccountUnclaimedNeuron = neuronSubaccount({ + controller: mockIdentity.getPrincipal(), + index: 0, + }); + const unclaimedNeuronId: SnsNeuronId = { + id: subaccountUnclaimedNeuron, + }; + const unclaimedNeuron: SnsNeuron = { + // The current user does not have any permissions because this neuron + // no longer belongs to this user. + ...transferredNeuron, + id: [unclaimedNeuronId] as [SnsNeuronId], + cached_neuron_stake_e8s: neuronAccountBalance, + }; + + spyNeuronBalance.mockResolvedValue(neuronAccountBalance); + spyNervousSystemParameters.mockResolvedValue({ + ...snsNervousSystemParametersMock, + neuron_minimum_stake_e8s: [neuronMinimumStake], + }); + spyClaimNeuron.mockResolvedValue(unclaimedNeuronId); + spyGetSnsNeuron.mockResolvedValue(unclaimedNeuron); + + expect(spyNeuronBalance).toBeCalledTimes(0); + expect(spyNervousSystemParameters).toBeCalledTimes(0); + expect(spyClaimNeuron).toBeCalledTimes(0); + expect( + get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons + ).toBeUndefined(); + + await claimNextNeuronIfNeeded({ rootCanisterId: mockPrincipal, - neuron: mockSnsNeuron, + neurons: [], + }); + + expect(spyNeuronBalance).toBeCalledTimes(1); + expect(spyNervousSystemParameters).toBeCalledTimes(2); + expect(spyClaimNeuron).toBeCalledTimes(1); + expect(spyClaimNeuron).toBeCalledWith({ + controller: mockIdentity.getPrincipal(), identity: mockIdentity, + memo: 0n, + rootCanisterId: mockPrincipal, + subaccount: subaccountUnclaimedNeuron, }); - await waitFor(() => expect(spyNeuronBalance).toBeCalled()); - expect(res).toBe(false); + // Neuron should not have been added to the store because it does not belong to the user. + expect( + get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons + ).toBeUndefined(); }); }); }); diff --git a/frontend/src/tests/lib/services/sns-neurons.services.spec.ts b/frontend/src/tests/lib/services/sns-neurons.services.spec.ts index f5f15f64586..68faaaa130a 100644 --- a/frontend/src/tests/lib/services/sns-neurons.services.spec.ts +++ b/frontend/src/tests/lib/services/sns-neurons.services.spec.ts @@ -37,10 +37,7 @@ import { snsNervousSystemParametersMock, } from "$tests/mocks/sns-neurons.mock"; import { mockSnsToken, mockTokenStore } from "$tests/mocks/sns-projects.mock"; -import { - mockedConstants, - resetMockedConstants, -} from "$tests/utils/mockable-constants.test-utils"; +import { resetMockedConstants } from "$tests/utils/mockable-constants.test-utils"; import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils"; import { decodeIcrcAccount } from "@dfinity/ledger-icrc"; import { Principal } from "@dfinity/principal"; @@ -55,7 +52,6 @@ import { fromDefinedNullable, fromNullable, } from "@dfinity/utils"; -import { waitFor } from "@testing-library/svelte"; import { tick } from "svelte"; import { get } from "svelte/store"; import type { SpyInstance } from "vitest"; @@ -67,7 +63,7 @@ const { removeHotkey, splitNeuron, stakeNeuron, - loadNeurons, + loadSnsNeurons, addFollowee, } = services; @@ -100,15 +96,6 @@ describe("sns-neurons-services", () => { }, ], }; - const nextSubaccount = neuronSubaccount({ - controller: mockIdentity.getPrincipal(), - index: 1, - }); - const nextNeuronId: SnsNeuronId = { id: nextSubaccount }; - const nextNeuron: SnsNeuron = { - ...neuron, - id: [nextNeuronId] as [SnsNeuronId], - }; beforeEach(() => { vi.clearAllMocks(); @@ -138,12 +125,6 @@ describe("sns-neurons-services", () => { const spyQuery = vi .spyOn(governanceApi, "querySnsNeurons") .mockImplementation(() => Promise.resolve([neuron])); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) - ) - .mockImplementation(() => Promise.resolve(0n)); const spyOnNervousSystemParameters = vi .spyOn(governanceApi, "nervousSystemParameters") .mockResolvedValue(snsNervousSystemParametersMock); @@ -160,7 +141,6 @@ describe("sns-neurons-services", () => { expect(store[mockPrincipal.toText()]?.neurons).toHaveLength(1); expect(spyOnNervousSystemParameters).toBeCalled(); expect(spyQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); }); }); @@ -177,12 +157,6 @@ describe("sns-neurons-services", () => { const spyQuery = vi .spyOn(governanceApi, "querySnsNeurons") .mockImplementation(() => Promise.resolve([neuron])); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) - ) - .mockImplementation(() => Promise.resolve(0n)); // expect parameters to be in store expect( @@ -196,114 +170,9 @@ describe("sns-neurons-services", () => { expect(store[mockPrincipal.toText()]?.neurons).toHaveLength(1); expect(spyQuery).toBeCalled(); expect(spyOnNervousSystemParameters).not.toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); }); }); - it("should refresh and refetch the neuron if balance doesn't match", async () => { - const updatedNeuron: SnsNeuron = { - ...neuron, - cached_neuron_stake_e8s: neuron.cached_neuron_stake_e8s + 100_000_000n, - }; - const spyQuery = vi - .spyOn(governanceApi, "querySnsNeurons") - .mockImplementation(() => Promise.resolve([neuron])); - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockImplementation(() => Promise.resolve(updatedNeuron)); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(updatedNeuron.cached_neuron_stake_e8s) - ) - .mockImplementation(() => Promise.resolve(0n)); - const spyRefreshNeuron = vi - .spyOn(governanceApi, "refreshNeuron") - .mockImplementation(() => Promise.resolve(undefined)); - await syncSnsNeurons(mockPrincipal); - - // Checking the neuron balances is done in the background. - // The `await` on `syncSnsNeurons` is not enough to wait for the balance check. - await waitFor(() => - expect(get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons).toEqual([ - updatedNeuron, - ]) - ); - expect(spyQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); - expect(spyRefreshNeuron).toBeCalled(); - expect(spyNeuronQuery).toBeCalled(); - }); - - it("should claim neuron if find a subaccount without neuron", async () => { - // Neuron has not been claimed yet. - vi.spyOn(governanceApi, "querySnsNeuron").mockResolvedValue(undefined); - const spyQuery = vi - .spyOn(governanceApi, "querySnsNeurons") - .mockImplementation(() => Promise.resolve([neuron])); - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockImplementation(() => Promise.resolve(nextNeuron)); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(neuron.cached_neuron_stake_e8s) - ) - .mockResolvedValueOnce(nextNeuron.cached_neuron_stake_e8s) - .mockImplementation(() => Promise.resolve(0n)); - const spyClaimNeuron = vi - .spyOn(governanceApi, "claimNeuron") - .mockImplementation(() => Promise.resolve(undefined)); - await syncSnsNeurons(mockPrincipal); - - // Checking the neuron balances is done in the background. - // The `await` on `syncSnsNeurons` is not enough to wait for the balance check. - await waitFor(() => - expect( - get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons - ).toHaveLength(2) - ); - expect(spyQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); - expect(spyClaimNeuron).toBeCalled(); - expect(spyNeuronQuery).toBeCalled(); - }); - - it("should claim neuron if find a subaccount without neuron for query calls if FORCE_CALL_STRATEGY is query", async () => { - mockedConstants.FORCE_CALL_STRATEGY = "query"; - // Neuron has not been claimed yet. - vi.spyOn(governanceApi, "querySnsNeuron").mockResolvedValue(undefined); - const spyQuery = vi - .spyOn(governanceApi, "querySnsNeurons") - .mockImplementation(() => Promise.resolve([neuron])); - const spyNeuronQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - .mockImplementation(() => Promise.resolve(nextNeuron)); - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) - ) - .mockResolvedValueOnce(nextNeuron.cached_neuron_stake_e8s) - .mockImplementation(() => Promise.resolve(0n)); - const spyClaimNeuron = vi - .spyOn(governanceApi, "claimNeuron") - .mockImplementation(() => Promise.resolve(undefined)); - await syncSnsNeurons(mockPrincipal); - - // Checking the neuron balances is done in the background. - // The `await` on `syncSnsNeurons` is not enough to wait for the balance check. - await waitFor(() => - expect( - get(snsNeuronsStore)[mockPrincipal.toText()]?.neurons - ).toHaveLength(2) - ); - expect(spyQuery).toBeCalled(); - expect(spyNeuronBalance).toBeCalled(); - expect(spyClaimNeuron).toBeCalled(); - expect(spyNeuronQuery).toBeCalled(); - }); - it("should empty store if update call fails", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); @@ -325,7 +194,7 @@ describe("sns-neurons-services", () => { }); }); - describe("loadNeurons", () => { + describe("loadSnsNeurons", () => { beforeEach(() => { snsNeuronsStore.reset(); vi.spyOn(console, "error").mockImplementation(() => undefined); @@ -335,7 +204,7 @@ describe("sns-neurons-services", () => { const spyQuery = vi .spyOn(governanceApi, "querySnsNeurons") .mockImplementation(() => Promise.resolve([neuron])); - await loadNeurons({ rootCanisterId: mockPrincipal, certified: true }); + await loadSnsNeurons({ rootCanisterId: mockPrincipal, certified: true }); await tick(); const store = get(snsNeuronsStore); @@ -360,12 +229,6 @@ describe("sns-neurons-services", () => { ...mockSnsNeuron, id: [neuronId] as [SnsNeuronId], }; - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => - Promise.resolve(mockSnsNeuron.cached_neuron_stake_e8s) - ) - .mockImplementation(() => Promise.resolve(0n)); const spyQuery = vi .spyOn(governanceApi, "getSnsNeuron") .mockImplementation(() => Promise.resolve(neuron)); @@ -379,53 +242,6 @@ describe("sns-neurons-services", () => { expect(spyQuery).toBeCalled(); expect(neuronToLoad).toEqual(neuron); if (certified) { - await waitFor(() => expect(spyNeuronBalance).toBeCalled()); - done(); - } - }; - getSnsNeuron({ - neuronIdHex: bytesToHexString( - Array.from(mockSnsNeuron.id[0]?.id as Uint8Array) - ), - rootCanisterId: mockPrincipal, - onLoad, - }); - })); - - it("should refresh neuron if balance does not match and load again", () => - new Promise((done) => { - const stake = neuron.cached_neuron_stake_e8s + 10_000n; - const updatedNeuron = { - ...neuron, - cached_neuron_stake_e8s: stake, - }; - const spyNeuronBalance = vi - .spyOn(governanceApi, "getNeuronBalance") - .mockImplementationOnce(() => Promise.resolve(stake)) - .mockImplementation(() => Promise.resolve(0n)); - const spyQuery = vi - .spyOn(governanceApi, "getSnsNeuron") - // First is the query call and returns old neuron - .mockImplementationOnce(() => Promise.resolve(neuron)) - // Second is the update call and returns old neuron. It will be checked. - .mockImplementationOnce(() => Promise.resolve(neuron)) - // After refreshing we get the updated neuron. - .mockImplementation(() => Promise.resolve(updatedNeuron)); - const spyRefreshNeuron = vi - .spyOn(governanceApi, "refreshNeuron") - .mockImplementation(() => Promise.resolve(undefined)); - const onLoad = ({ - neuron: neuronToLoad, - }: { - neuron: SnsNeuron; - certified: boolean; - }) => { - // Wait until we get the updated neuron to finish the test. - if (neuronToLoad.cached_neuron_stake_e8s === stake) { - expect(spyQuery).toBeCalledTimes(3); - expect(spyNeuronBalance).toBeCalledTimes(1); - expect(spyRefreshNeuron).toBeCalledTimes(1); - expect(neuronToLoad).toEqual(updatedNeuron); done(); } }; diff --git a/frontend/src/tests/lib/services/vote-registration.services.spec.ts b/frontend/src/tests/lib/services/vote-registration.services.spec.ts index 0e977f12f30..3564b2a13f1 100644 --- a/frontend/src/tests/lib/services/vote-registration.services.spec.ts +++ b/frontend/src/tests/lib/services/vote-registration.services.spec.ts @@ -315,7 +315,7 @@ describe("vote-registration-services", () => { await runResolvedPromises(); expect(get(actionableNnsProposalsStore)).toEqual({ - proposals: [votableProposal], + proposals: [votableProposal, votableProposal], }); }); diff --git a/frontend/src/tests/lib/stores/checked-neurons.store.spec.ts b/frontend/src/tests/lib/stores/checked-neurons.store.spec.ts new file mode 100644 index 00000000000..dc1b23eeb31 --- /dev/null +++ b/frontend/src/tests/lib/stores/checked-neurons.store.spec.ts @@ -0,0 +1,52 @@ +import { checkedNeuronSubaccountsStore } from "$lib/stores/checked-neurons.store"; +import { get } from "svelte/store"; + +describe("checked-neurons.store", () => { + describe("checkedNeuronSubaccountsStore", () => { + beforeEach(() => { + checkedNeuronSubaccountsStore.reset(); + }); + + it("should initially be empty", () => { + expect(get(checkedNeuronSubaccountsStore)).toEqual({}); + }); + + it("should add a subaccount", () => { + expect(get(checkedNeuronSubaccountsStore)).toEqual({}); + const universeId = "abc-efg-cai"; + const subaccountHex = "12ab90"; + checkedNeuronSubaccountsStore.addSubaccount({ + universeId, + subaccountHex, + }); + expect(get(checkedNeuronSubaccountsStore)).toEqual({ + [universeId]: { + [subaccountHex]: true, + }, + }); + }); + + it("should return whether a subaccount was added", () => { + const universeId = "abc-efg-cai"; + const subaccountHex = "12ab90"; + expect( + checkedNeuronSubaccountsStore.addSubaccount({ + universeId, + subaccountHex, + }) + ).toBe(true); + expect( + checkedNeuronSubaccountsStore.addSubaccount({ + universeId, + subaccountHex, + }) + ).toBe(false); + expect( + checkedNeuronSubaccountsStore.addSubaccount({ + universeId, + subaccountHex, + }) + ).toBe(false); + }); + }); +}); diff --git a/frontend/src/tests/lib/utils/proposals.utils.spec.ts b/frontend/src/tests/lib/utils/proposals.utils.spec.ts index e1fa381e3b4..3e23380ac6d 100644 --- a/frontend/src/tests/lib/utils/proposals.utils.spec.ts +++ b/frontend/src/tests/lib/utils/proposals.utils.spec.ts @@ -23,6 +23,7 @@ import { replaceAndConcatenateProposals, replaceProposals, selectedNeuronsVotingPower, + sortProposalsByIdDescendingOrder, } from "$lib/utils/proposals.utils"; import en from "$tests/mocks/i18n.mock"; import { mockNeuron } from "$tests/mocks/neurons.mock"; @@ -1080,4 +1081,23 @@ describe("proposals-utils", () => { ).toBe(0); }); }); + + describe("sortProposalsByIdDescendingOrder", () => { + const proposal0 = { ...mockProposalInfo, id: 0n } as ProposalInfo; + const proposal1 = { ...mockProposalInfo, id: 1n } as ProposalInfo; + const proposal2 = { ...mockProposalInfo, id: 2n } as ProposalInfo; + + it("should sort proposals", () => { + expect( + sortProposalsByIdDescendingOrder([proposal1, proposal0, proposal2]) + ).toEqual([proposal2, proposal1, proposal0]); + expect( + sortProposalsByIdDescendingOrder([proposal2, proposal1, proposal0]) + ).toEqual([proposal2, proposal1, proposal0]); + expect(sortProposalsByIdDescendingOrder([proposal0])).toEqual([ + proposal0, + ]); + expect(sortProposalsByIdDescendingOrder([])).toEqual([]); + }); + }); }); diff --git a/frontend/src/tests/lib/utils/staking.utils.spec.ts b/frontend/src/tests/lib/utils/staking.utils.spec.ts index 8c04559fb12..621e03a0c13 100644 --- a/frontend/src/tests/lib/utils/staking.utils.spec.ts +++ b/frontend/src/tests/lib/utils/staking.utils.spec.ts @@ -2,40 +2,122 @@ import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg"; import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; import type { Universe } from "$lib/types/universe"; import { getTableProjects } from "$lib/utils/staking.utils"; +import { mockNeuron } from "$tests/mocks/neurons.mock"; +import { createMockSnsNeuron } from "$tests/mocks/sns-neurons.mock"; import { principal } from "$tests/mocks/sns-projects.mock"; describe("staking.utils", () => { describe("getTableProjects", () => { const universeId2 = principal(2).toText(); + const nnsUniverse = { + canisterId: OWN_CANISTER_ID_TEXT, + title: "Internet Computer", + logo: IC_LOGO_ROUNDED, + }; + + const snsUniverse = { + canisterId: universeId2, + title: "title2", + logo: "logo2", + }; + + const defaultExpectedNnsTableProject = { + rowHref: `/neurons/?u=${OWN_CANISTER_ID_TEXT}`, + domKey: OWN_CANISTER_ID_TEXT, + title: "Internet Computer", + logo: IC_LOGO_ROUNDED, + neuronCount: 0, + }; + + const defaultExpectedSnsTableProject = { + rowHref: `/neurons/?u=${universeId2}`, + domKey: universeId2, + title: "title2", + logo: "logo2", + neuronCount: 0, + }; + + const snsNeuronWithStake = createMockSnsNeuron({ + stake: 100_000_000n, + id: [1, 1, 3], + }); + + const snsNeuronWithoutStake = createMockSnsNeuron({ + stake: 0n, + maturity: 0n, + id: [7, 7, 9], + }); + it("should return an array of TableProject objects", () => { - const universes: Universe[] = [ - { - canisterId: OWN_CANISTER_ID_TEXT, - title: "Internet Computer", - logo: IC_LOGO_ROUNDED, - }, + const universes: Universe[] = [nnsUniverse, snsUniverse]; + + const tableProjects = getTableProjects({ + universes, + definedNnsNeurons: [], + snsNeurons: {}, + }); + + expect(tableProjects).toEqual([ + defaultExpectedNnsTableProject, + defaultExpectedSnsTableProject, + ]); + }); + + it("should include number of NNS neurons", () => { + const tableProjects = getTableProjects({ + universes: [nnsUniverse], + definedNnsNeurons: [mockNeuron, mockNeuron, mockNeuron], + snsNeurons: {}, + }); + + expect(tableProjects).toEqual([ { - canisterId: universeId2, - title: "title2", - logo: "logo2", + ...defaultExpectedNnsTableProject, + neuronCount: 3, }, - ]; + ]); + }); - const tableProjects = getTableProjects({ universes }); + it("should include number of SNS neurons", () => { + const tableProjects = getTableProjects({ + universes: [snsUniverse], + definedNnsNeurons: [], + snsNeurons: { + [universeId2]: { + neurons: [snsNeuronWithStake, snsNeuronWithStake], + }, + }, + }); expect(tableProjects).toEqual([ { - rowHref: `/neurons/?u=${OWN_CANISTER_ID_TEXT}`, - domKey: OWN_CANISTER_ID_TEXT, - title: "Internet Computer", - logo: IC_LOGO_ROUNDED, + ...defaultExpectedSnsTableProject, + neuronCount: 2, }, + ]); + }); + + it("should filter SNS neurons without stake", () => { + const tableProjects = getTableProjects({ + universes: [snsUniverse], + definedNnsNeurons: [], + snsNeurons: { + [universeId2]: { + neurons: [ + snsNeuronWithStake, + snsNeuronWithoutStake, + snsNeuronWithoutStake, + snsNeuronWithoutStake, + ], + }, + }, + }); + + expect(tableProjects).toEqual([ { - rowHref: `/neurons/?u=${universeId2}`, - domKey: universeId2, - title: "title2", - logo: "logo2", + ...defaultExpectedSnsTableProject, + neuronCount: 1, }, ]); }); diff --git a/frontend/src/tests/mocks/staking.mock.ts b/frontend/src/tests/mocks/staking.mock.ts new file mode 100644 index 00000000000..00b42ae80e8 --- /dev/null +++ b/frontend/src/tests/mocks/staking.mock.ts @@ -0,0 +1,11 @@ +import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg"; +import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; +import type { TableProject } from "$lib/types/staking"; + +export const mockTableProject: TableProject = { + rowHref: `/neurons/?u=${OWN_CANISTER_ID_TEXT}`, + domKey: OWN_CANISTER_ID_TEXT, + title: "Internet Computer", + logo: IC_LOGO_ROUNDED, + neuronCount: 0, +}; diff --git a/frontend/src/tests/page-objects/ProjectNeuronsCell.page-object.ts b/frontend/src/tests/page-objects/ProjectNeuronsCell.page-object.ts new file mode 100644 index 00000000000..09effac2aff --- /dev/null +++ b/frontend/src/tests/page-objects/ProjectNeuronsCell.page-object.ts @@ -0,0 +1,14 @@ +import { BasePageObject } from "$tests/page-objects/base.page-object"; +import type { PageObjectElement } from "$tests/types/page-object.types"; + +export class ProjectNeuronsCellPo extends BasePageObject { + private static readonly TID = "project-neurons-cell-component"; + + static under(element: PageObjectElement): ProjectNeuronsCellPo { + return new ProjectNeuronsCellPo(element.byTestId(ProjectNeuronsCellPo.TID)); + } + + async getNeuronCount(): Promise { + return await this.getText(); + } +} diff --git a/frontend/src/tests/page-objects/ProjectTitleCell.page-object.ts b/frontend/src/tests/page-objects/ProjectTitleCell.page-object.ts new file mode 100644 index 00000000000..045cf7cd84a --- /dev/null +++ b/frontend/src/tests/page-objects/ProjectTitleCell.page-object.ts @@ -0,0 +1,14 @@ +import { BasePageObject } from "$tests/page-objects/base.page-object"; +import type { PageObjectElement } from "$tests/types/page-object.types"; + +export class ProjectTitleCellPo extends BasePageObject { + private static readonly TID = "project-title-cell-component"; + + static under(element: PageObjectElement): ProjectTitleCellPo { + return new ProjectTitleCellPo(element.byTestId(ProjectTitleCellPo.TID)); + } + + getProjectTitle(): Promise { + return this.getText("project-title"); + } +} diff --git a/frontend/src/tests/page-objects/ProjectsTable.page-object.ts b/frontend/src/tests/page-objects/ProjectsTable.page-object.ts new file mode 100644 index 00000000000..784da9d3120 --- /dev/null +++ b/frontend/src/tests/page-objects/ProjectsTable.page-object.ts @@ -0,0 +1,15 @@ +import { ProjectsTableRowPo } from "$tests/page-objects/ProjectsTableRow.page-object"; +import { ResponsiveTablePo } from "$tests/page-objects/ResponsiveTable.page-object"; +import type { PageObjectElement } from "$tests/types/page-object.types"; + +export class ProjectsTablePo extends ResponsiveTablePo { + private static readonly TID = "projects-table-component"; + + static under(element: PageObjectElement): ProjectsTablePo { + return new ProjectsTablePo(element.byTestId(ProjectsTablePo.TID)); + } + + getProjectsTableRowPos(): Promise { + return ProjectsTableRowPo.allUnder(this.root); + } +} diff --git a/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts b/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts new file mode 100644 index 00000000000..65b5c0e0966 --- /dev/null +++ b/frontend/src/tests/page-objects/ProjectsTableRow.page-object.ts @@ -0,0 +1,34 @@ +import { ProjectNeuronsCellPo } from "$tests/page-objects/ProjectNeuronsCell.page-object"; +import { ProjectTitleCellPo } from "$tests/page-objects/ProjectTitleCell.page-object"; +import { ResponsiveTableRowPo } from "$tests/page-objects/ResponsiveTableRow.page-object"; +import type { PageObjectElement } from "$tests/types/page-object.types"; + +export class ProjectsTableRowPo extends ResponsiveTableRowPo { + static under(element: PageObjectElement): ProjectsTableRowPo { + return new ProjectsTableRowPo(element.byTestId(ResponsiveTableRowPo.TID)); + } + + static async allUnder( + element: PageObjectElement + ): Promise { + return Array.from(await element.allByTestId(ResponsiveTableRowPo.TID)).map( + (el) => new ProjectsTableRowPo(el) + ); + } + + getProjectTitleCellPo(): ProjectTitleCellPo { + return ProjectTitleCellPo.under(this.root); + } + + getProjectNeuronsCellPo(): ProjectNeuronsCellPo { + return ProjectNeuronsCellPo.under(this.root); + } + + getProjectTitle(): Promise { + return this.getProjectTitleCellPo().getProjectTitle(); + } + + getNeuronCount(): Promise { + return this.getProjectNeuronsCellPo().getNeuronCount(); + } +} diff --git a/frontend/src/tests/page-objects/TokensPage.page-object.ts b/frontend/src/tests/page-objects/TokensPage.page-object.ts index bd88efc05e9..dde2dca7da7 100644 --- a/frontend/src/tests/page-objects/TokensPage.page-object.ts +++ b/frontend/src/tests/page-objects/TokensPage.page-object.ts @@ -33,6 +33,10 @@ export class TokensPagePo extends BasePageObject { return this.getButton("show-all-button"); } + getImportTokenButtonPo(): ButtonPo { + return this.getButton("import-token-button"); + } + hasTokensTable(): Promise { return this.getTokensTable().isPresent(); } diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json index 8322314323e..94e184119bf 100644 --- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json +++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-0.json @@ -282,7 +282,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 799999990788594000, + "icrc1_total_supply": 799999990040394000, "swap_params": { "params": { "min_participant_icp_e8s": 10000000, @@ -1071,7 +1071,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 10090978434179058, + "icrc1_total_supply": 10091603124720208, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -1786,7 +1786,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 602127334453937, + "icrc1_total_supply": 602216880812473, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -2183,7 +2183,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 101426624568104800, + "icrc1_total_supply": 101505465079652240, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -2832,7 +2832,7 @@ ] ], "icrc1_fee": [100000000], - "icrc1_total_supply": 924127113826937500, + "icrc1_total_supply": 924139678039848100, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json index d244c799932..ba2e71a9cbc 100644 --- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json +++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-1.json @@ -261,6 +261,12 @@ } }, "icrc1_metadata": [ + [ + "icrc1:logo", + { + "Text": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAAAXNSR0IArs4c6QAAIABJREFUeF7tnQ+oJcWd73/VdYQrTOAGDMzCyBthwpuwCY6YRcUsUaKrbpyNZsz63mbX/DFRxzXEGONqdp//3m50J0YN0TgaMdHdhJVodOMER1Q0bILKRlQSiMEB5+EsGYhLBnLBC57qelPjuXrnzjn3dJ+u7q4/nwOD4K2u+v0+39/p/p6q6m4lfCAAgZgIzInIRq31/7DWbhCRddba9Uqp9SKyVim1ts1krLV7lFJ7y7J0/90jIruVUruNMb8RkV0istjm+PQNAQj4I6D8dUVPEIBAAwIbtNbHWWuPV0p9UETcv0GD/kI6dCgiv7DWvqiU+pkx5rmRWQgpRmKBQHYEMADZSU7CPRFYo7U+zVp7uoicrpRa11McQQ7rZhZEZKdSaqcx5jERWQgyUIKCQEIEMAAJiUkqQRDYUBTFuSJyjlJqUxARRR6EmznYv+zxb2VZ3u+WHCJPh/AhEAwBDEAwUhBIbAS01lv2X5A+vX96+8zYYk8k3h0i8j1jzIOJ5EMaEOiUAAagU9wMFisBrfVHReRSETkl1hwyiXuniNxpjHk4k3xJEwIzE8AAzIyOAxMmcERRFF9WSrkLvtt1zydeAgvW2tvKsvyGiLwebxpEDgH/BDAA/pnSY3wE1hdFcc3+29ncdD6f9Am4ZYPr2E+QvtBkuDoBDAAVkiOB+aIobth/P/tFOSZPzgcTsNZuL8vyqv1LPPtgA4GcCGAAclI741yLorhQKXWjiMxnjIHUpxPYZ629sizLO6c3pQUE4iaAAYhbP6KfTGCt1voOETkLSBBoQOBhY8xWEdnboA8OhUCQBDAAQcpCUDMSOGn/I3K/u//WMPdYXD4Q8E3APfb4M8Ph8GnfHdMfBPoggAHogzpjeiOgtf6YuxecqX1vSOmoGgH3pMK/4XbDarBoFSYBDECYuhDVKgRGF/1/4xY9yiQQAu4FSM4MPBBIPIQBgUoEMACVMNEoAAJuev8hfukHoAQhrEZgnzFm8/4HRv0MTBAInQAGIHSF8o7PbeR7ZPRmvLxJkH2MBH4xMgNsIIxRvQxixgBkIHJsKRZFcQf36MemGvFOIXCbMeYLUIJASAQwACGpkXcsp2itHxWRQd4YyD5xAotKqbOHw6F7ZwEfCPRKAAPQK/7sBx+M1vV5m172pZAlAPeMgbOzzJykgyCAAQhChuyCcBv6HufXfna6k/B4AovGmJNF5FkAQaBLAhiALmlnPtbo+ftXZo6B9CEwkYC19qayLL8CIgh0QQAD0AXlvMdw0/zPsJM/7yIg+3oErLUvlmV5goi4ZwzwgUArBDAArWClUxHZoLX+JQ/roRYg0IiAWx44RkRebtQLB0NgDAEMAGXhlYDW2m3oc/fu84EABPwSOJtHD/sFmntvGIDcK8BT/kVR/K1S6jZP3dENBCAwgYC19tKyLL8JIAg0JYABaEow8+OLorhWKXVN5hhIHwKdE7DW3liW5VWdD8yAyRDAACQjZbeJDAaDW9wvkW5HZTQIQGAlAaXUrcPh8EuQgUBdAhiAusQyb8+FP/MCIP1gCWAEgpUm2MAwAMFKE1ZgTPWHpQfRQGASAWvtdWVZXgshCEwjgAGYRijzv2utzxeRuzPHQPoQiI4AmwWjk6zzgDEAnSOPZkD3uN6noomWQCEAgUkENhtjdoAHAisJYACoiZUE3AN8fs1z+ikMCKRFwBhzpIjsSSsrsmlCAAPQhF5ax7pH9v5ORObTSotsIACBZQT2GmOO4hHD1IQjgAGgDmT0Zr5TQAEBCGRDYIcxZnM22ZLoWAIYgIwLoyiKC5VS2zNGQOoQyJqAtfaSsixvzxpCxsljAPIUf93+3f2v5Zk6WUMAAisJsD8gz5rAAGSme1EUryilNmSWNulCAAJTCFhrd5Vl+V5A5UMAA5CJ1kVRfFkpdVMm6ZImBCAwIwGeHzAjuAgPwwBEKFrNkOe11r+veQzNIQCBzAkYY94jIq9njiHp9DEACcurtX5ERM5MOEVSgwAE2iXwhDHm1HaHoPe+CGAA+iLf7ribtNYvtDsEvUMAArkQMMYcIyIv5pJvLnliABJTuiiKXyulNiaWFulAAAI9E7DWvlyW5ft6DoPhPRLAAHiE2WdXg8HgJGstz+7vUwTGhkAGBJRSJw+Hw6czSDX5FDEACUhcFMWrSqn1CaRCChCAQAQErLW7y7J0jxTmEzEBDEDE4onI8VrrZ+JOgeghAIFYCRhjThCRZ2ONP/e4MQCRVsDown98pOETNgQgkAgBa+2LZVm6TYJ8IiOAAYhMMBHhMb7xaUbEEEieAI8Tjk9iDEBEmmmtv7P/fd6fiyhkQoUABPIisN0YszWvlOPNFgMQiXZa6zdEZC6ScAkTAhDIl8CiMebwfNOPJ3MMQOBaDQaD0621jwYeJuFBAAIQOIiAUurU4XD4BFjCJYABCFcb0Vr/h4h8KOAQCQ0CEIDAagSeNsacDKIwCWAAwtRloLV+M8zQiAoCEIBAPQKjJYHFekfRum0CGIC2CdfsfzAYnGKtfbzmYTSHAAQgEDQBlgTCkwcDEJAmWmu31n96QCERCgQgAAGfBHYaY87w2SF9zU4AAzA7O69HssvfK046gwAEwiXAXQKBaIMB6F8IHuzTvwZEAAEIdEyABwd1DHzMcBiAHjUoiuJCpdT2HkNgaAhAAAK9EbDWbi3LknNgTwpgAHoCr7V2r+49qafhGRYCEIBAKASeMMacGkowOcWBAehBbdb7e4DOkBCAQMgE2BfQgzoYgG6hz40u/t2OymgQgAAEIiDA8wK6FQkD0B3v92utf9ndcIwEAQhAID4Cxhj3auEX44s8vogxAB1oVhTFF5VSt3YwFENAAAIQiJ6Atfbysiy/EX0igSeAAWhZIK31IyJyZsvD0D0EIACB1Ajw0KCWFcUAtAi4KIpXlVLrWxyCriEAAQgkS8Bau7ssy6OSTbDnxDAALQnATv+WwNItBCCQGwHuEGhJcQxAC2C11raFbukSAhCAQLYEjDFcrzyrD1C/QLnNzy9PeoMABCDwNgFuE/RbDBgAfzzXa61f9dcdPUEAAhCAwEoCxhi3J2A3ZJoTwAA0Z+h64B5/PxzpBQIQgMBUAsaYD4jIr6Y2pMGqBDAADQtkMBicbq19tGE3HA4BCEAAAjUIKKXOGA6HO2scQtMVBDAADUpCa32OiPywQRccCgEIQAACsxP4hDHmgdkPz/tIDMCM+hdF8XdKqRtnPJzDIAABCEDAAwFr7T+UZflPHrrKrgsMwAySF0VxjVLq2hkO5RAIQAACEPBMwFp7bVmW13nuNvnuMAA1JebiXxMYzSEAAQh0QAATUB8yBqAGs6IoblFKXVrjEJpCAAIQgEBHBNxL14bD4Zc6Gi76YTAAFSXk4l8RFM0gAAEI9EgAE1AdPgagAium/StAogkEIACBQAiwHFBNCAzAFE5c/KsVEq0gAAEIhEQAEzBdDQzAKoyKovh7pdQ/TsdICwhAAAIQCI2AtfbKsiz/ObS4QokHAzBBCR7yE0qJEgcEIACBRgR4WNAEfBiAMWB4vG+jLxsHQwACEAiKAI8NHi8HBuBQLrzYJ6ivLsFAAAIQaE6AFwgdyhADcDATXunb/HtGDxCAAASCJMCrhA+WBQPwDo85rfUbQVYtQUEAAhCAgBcCxpjDRWTRS2eRd4IBGAmotbaRa0n4EIAABCBQgYAxhmufiABBRLj4V/jG0AQCEIBAQgQwARgAd/F30/5zCdU1qUAAAhCAwHQCi6PlgOktE22R9QxAURSvKqXWJ6otaUEAAhCAwCoErLW7y7I8KldI2RoArfWjInJ6rsKTNwQgAAEIHCCwwxizOUcWWRqAoii+rJS6KUfByRkCEIAABA4mYK39UlmWt+bGJUcDsElr/UJuQpMvBCAAAQhMJpDjg4JyMwDc688ZAAIQgAAExhLI7RkBWRkAbvfjWw8BCEAAAqsRyOn2wGwMALf78aWHAAQgAIEKBLK5PTALA6C1flxETqkgPE0gAAEIQAACTxtjTk4dQ/IGoCiKC5VS21MXkvwgAAEIQMAfAWvt1rIsk752pG4A1mmtX/NXEvQEAQhAAAK5EDDGHCkie1LNN2kDwKa/VMuWvCAAAQh0QyDlTYHJGgA2/XXz5WAUCEAAAokTSHZTYJIGgMf8Jv51JD0IQAAC3RLYaYw5o9sh2x8tOQMwGAxOsda6Xf98IAABCEAAAl4IKKVOHQ6HT3jpLJBOUjMAPOkvkMIiDAhAAAKpETDGHCYiw1TySsoAsOkvlbIkDwhAAAJhEkhpU2AyBkBr/ZSInBRmyRAVBCAAAQgkQuBnxpg/TSGXJAwA6/4plCI5QAACEIiDgFLqjOFwuDOOaCdHmYQBYOo/9jIkfghAAAJxEUhhKSB6A8D9/nF9aYgWAhCAQCIEon8+QNQGQGt9h4hclEgxkQYEIAABCMRF4G5jzOfjCvmdaGM2ADznP9aqI24IQAACiRCI+X0B0RoA1v0T+faQBgQgAIHICcS6HyBKA1AUxQtKqU2R1wzhQwACEIBAGgSeNcacEFsqMRqA47XWz8QGmnghAAEIQCBdAiMD8GxMGUZnAJj6j6m8iBUCEIBAPgRiWwqIygAURfGqUmp9PuVEphCAAAQgEAsBa+3usiyPiiXeaAzAYDA4yVrrHvfLBwIQgAAEIBAkAaXUycPh8Okgg1sRVDQGgKn/GMqJGCEAAQhAIJalgCgMQFEUv1ZKbaSsIAABCEAAAqETsNa+XJbl+0KPMwYDsElr/ULoIIkPAhCAAAQgsETAGHOMiLwYMpHgDQBT/yGXD7FBAAIQgMAkAqEvBQRtALTWj4vIKZQXBCAAAQhAIEICO4wxm0ONO2QDcITW+nehgiMuCEAAAhCAwDQCxph3i8i+ae36+HuwBoCp/z7KgTEhAAEIQMA3gVCXAoI0AEVRfFEpdatvEegPAhCAAAQg0DUBa+3lZVl+o+txp40XpAHg1/802fg7BCAAAQjERCDEWYDgDEBRFK8opTbEJCyxQgACEIAABFYjYK3dVZble0OiFJoBWKe1fi0kQMQCAQhAAAIQ8EHAGHOkiOzx0ZePPoIyAEz9+5CUPiAAAQhAIFQCIS0FBGMAiqL4W6XUbaGKRlwQgAAEIACBpgSstVvLstzetB8fxwdjAPj170POdPuYm5uTj3/843LaaafJiSeeKOvX81boWdRes2aNLC4ujj3U/e3111+XwWAwS9dZH7Nr1y557rnn5LHHHpMf//jHsrCwkDUPkl+dQCizAEEYAK31IyJyJkUDgSUCW7Zska9+9aty9NFHA8UzgUkX+OFw6HkkunvppZfka1/7mjz44IPAgMByAk8YY07tG0kIBmBOa/1G3yAYv18C7qL0zW9+Uy688MJ+A8lgdDeL8uSTTx6U6Uc/+lH593//9wyy7zfFe+65Ry6++GLBbPWrQwijG2MOE5FeXXfvBkBr/VsRWRuCIMTQPYHLLrtMtm3b1v3AGY/42c9+Vu67776DCJx33nniLk58uiPgZrio/e54BzjSvtFjgnsLrW8DwG1/vUnf38Du1777BerW8vl0TwAD0D3z1UZ8/vnnD3wXmBUIS5cuojHGuOcC7OpirHFj9GoA2PjXl+z9jOsu/L/61a9kwwae89SPAm+NigHok/7ksffu3XvguzFpk2aYURNVQwLD0VJAw25mO7w3A6C1dpv+3OY/PhkQcL9y2NAXhtAYgDB0mBSF2zh47LHHhh0k0XkjoJQ6eTgcPu2twxod9WkAbI04aRopAbfOef3110cafZphYwDi0NV9b/juxKFV0yj7ui2wFwPA2/6alkv4x7t7yvftC/IV2OHDazlCDEDLgD13v9qzGzwPRXf9Efi8MeburofvxQCw9t+1zN2O5+57vuKKK7odlNEqE8AAVEYVTEP3nbr66quDiYdA/BPoYxagcwNQFMW1Sqlr/OOjxxAIuCfJzc/PhxAKMUwggAGIszTcjNoRRxwRZ/BEPZWAtfa6siyvndrQY4PODQC//j2qF1BXTPkHJMaUUDAA8Wg1LlJnsHnUcNwaToq+61mATg3AYDC4xVp7aZrS5ZvVhz/84UOeLJcvjfAzxwCEr9G0CD/ykY/IT3/602nN+HtkBJRStw6Hwy91FXanBoBf/13J2t04F1xwgXz729/ubkBGakwAA9AYYRAduEcK33XXXUHEQhD+CHQ5C9CZAeDXv78CCaUntymJjUmhqFE9DgxAdVaht+RWwdAVqh9fl7MAnRkAfv3XL4SQj+DiH7I6q8eGAYhXu3GRYwLS0tNl09UsQCcGoCiKG5RSV6YnU54ZMe0ft+4YgLj1Gxc9ywFpadrVHQGdGAB+/adTnGz4i19LDED8Go7LgI2BaenaxSxA6waAp/6lU5Tc6peGlhiANHQclwW3CKajrbX2krIsb28zo9YNAL/+25Sv2755XWm3vNsaDQPQFtkw+nVv3eSTBoG2ZwFaNQBa67NE5KE0pMg7C57wl47+GIB0tByXCU8MTErfzcaYHW1l1LYB4I1/bSnXYb9ul7F7qx+fNAhgANLQcbUstm3bxnc2EZnbnAVo0wBs1Fr/OhENsk1jbm6Ox44mpj4GIDFBJ6TDfoA0dDbGvFdEdrWRTWsGQGv9hojMtRE0fXZHgHX/7lh3NRIGoCvS/Y/DfoD+NfAQwaIx5nAP/RzSRVsGYG5kANqImT47IsDDfjoC3fEwGICOgfc4nPsOu1cJ84mbgDHmMBEZ+s6iFQNQFMULSqlNvoOlv24J8Ou/W95djYYB6Ip0GOMwCxCGDg2j+IUx5k8a9tHNDAC3/vmWqfv+nn/+eTn66KO7H5gRWyeAAWgdcVADvPTSS3LssccGFRPB1CfQxmZA7zMAg8Hg69bay+unxxGhEGDjXyhKtBMHBqAdriH36r7TzOiFrND02JRSNw6Hw6umt6zewrsB4Nd/dfihttyzZ4+sXbs21PCIqyEBDEBDgBEevmvXLtm4cWOEkRPycgK+ZwF8G4DjtdbPIFm8BNx64eLiYrwJEPlUAhiAqYiSbMAsQPyyKqVOHg6HT/vKxKsB4NY/X7L0189zzz3HemF/+DsZGQPQCebgBvn5z38u7mVefKImMBzdEeAlCd8GgCf/eZGlv05YJ+yPfVcjYwC6Ih3eONwREJ4mdSPyeUugNwOgtXbP/HfP/ucTKYErrriCe4Yj1a5O2BiAOrTSauu+4zfffHNaSeWXzQ5jzGYfafs0APz696FIj33w679H+B0OjQHoEHaAQzELEKAoNUPytRnQiwEYDAanW2sfrZkDzQMiwOa/gMRoORQMQMuAA++ezYCBC1QhPKXUqcPh8IkKTVdt4sUAsPmvqQz9H3/nnXfK+eef338gRNA6AQxA64iDHuCuu+6Siy++OOgYCW4qAS+bAX0ZAKb/p+oVdgOm/8PWx2d0GACfNOPsi2WAOHVbHrWPZYDGBkBr/S0RuSR+nHlngAHIR38MQD5aT8oUA5BEDWw3xmxtkokPA8Cv/yYKBHDsli1b5P777w8gEkLoggAGoAvKYY9x7rnnyoMPPhh2kEQ3lUDTWYCmBmCt1vq3U6OkQdAEePFP0PJ4Dw4D4B1pdB3ygqDoJBsbsDHmj0Rk76zZNDIAWuv/FJEPzjo4x4VBgOn/MHToKgoMQFekwx6HZYCw9akYXaPXBDc1AEz/V1Qp5GYYgJDV8R8bBsA/0xh7xADEqNqhMTdZBpjZAAwGgw9Za/8jDYT5ZrFmzRrZt29fvgAyzBwDkKHoY1J2331e/BV/LTR5QdDMBkBr/XsRmY8fX94Z/NVf/ZXcd999eUPILHsMQGaCT0j3vPPOkx/84AfAiJ/APmPMu2dJo4kBYPp/FuKBHXPvvffKJz/5ycCiIpw2CWAA2qQbT9/f//735VOf+lQ8ARPpRAKzLgPMZAC01ueIyA/RI34CL7/8smzYsCH+RMigMgEMQGVUSTfcvXs33/10FD7bGPNw3XRmNQBviMhc3cFoHx4BNgCGp0nbEWEA2iYcT/9sBIxHqymRLhpjDq+bzawGgOn/uqQDbY8BCFSYFsPCALQIN7KuMQCRCbZKuLMsA9Q2AFrrs0TkoXSw5Z0JBiA//TEA+Wk+KWMMQFK1UHsZYBYD8AcRWZMUtoyTwQDkJz4GID/NMQBZaF77boBZDADT/wnVEgYgITErpoIBqAgqg2bMAKQlct1lgFoGYDAYnGStfSotZHlngwHIT38MQH6aMwOQh+Z1HwpUywBorV8VkfV5oMwjSwxAHjovzxIDkJ/mGIA8NLfW7i7L8qiq2dY1AEz/VyUbSTsMQCRCeQwTA+ARZuRdsQQQuYBjwq+zDFDHAPDq3/RqRTAACYo6JSUMQH6aMwOQj+Z1XhFc2QBord2tf+4WQD4JEcAAJCRmxVQwABVBZdCMGYAkRX7YGHN2lczqGACm/6sQjawNBiAywTyEiwHwADGRLjAAiQi5Io2qywAYgDT1r5wVBqAyqmQaYgCSkbJxIhiAxgiD7MCrASiK4kKl1PYgMyWoRgQwAI3wRXkwBiBK2VoJGgPQCtbeO7XWbi3Lcuo1u9IMgNb69yIy33tWBOCdAAbAO9LgO/zLv/xL+dGPfnRQnFu2bJH7778/+NgJ0C8BDIBfngH1VumpgFUNAOv/ASnrMxQMgE+acfQ1Pz8vCwsLBwU7Nzd3yP+LIxuibEIAA9CEXtjHVlkGqGIA5kczAGFnS3QzEcAAzIQt6oMmnfSphahlnSl4DMBM2KI4yBjzbhHZt1qwUw2A1voOEbkoiowJsjYBTvq1kUV9wM033yxXXHHF2Byuvvpqcf/45EMAA5C01tuNMVubGgCm/xOuEQxAwuKuSG1xcVHWrFn9RZ6uDReFfGoCrdPWetoyQJUZAAxAwjWCAUhY3BWpVT3ZUxPURD4E0s60qQFYP3oBUNqUMs6Ok30e4le9+C/RoC6oizwIpJ2lMca9GGj3pCxXnQHQWn93/8GfThtR3tlxok9bf6ev2+E/y4famIVaXMfUNYZxZUe0+9/e+z1jzGdmNQBM/ydeQ5zk0xW4ycWfmYB062J5ZhiA9HVebRlg2gwABiDx+sAApCmwj4s/JiDN2sAApK/r8gxnNQBHaK1/lxeq/LLFAKSnuc+LPyYgvfrAAKSt6crsjDHvEZHXx2U9cQagKIoblFJX5oUqv2wxAGlp3sbFHxOQVo1gANLVc1xm1toby7K8qpYB0Fr/QURWv2k4L45JZosBSEfWNi/+mIB06gQDkKaWq2S1aIw5vK4BYP0/gzrBAKQhcpWH/PjKlJrxRbL/ftgE2L8GXUQwaR/AxCUArTUGoAtleh6Dk3nPAngYvsuLPzMBHgQLqAsMQEBitBhKLQOgtT5LRB5qMR66DoQABiAQIWYMo4+LPyZgRrECPAwDEKAo7YS02RizY2XXY2cAtNaPisjp7cRBryERwACEpEa9WPq8+GMC6mkVamsMQKjKeI/rCWPMqVUNANP/3vmH2SEGIExdpkUVwsUfEzBNpfD/jgEIXyNfEY5bBpg0A4AB8EU98H4wAIELNCa8kC7+mID46md5xBiAuPWrEz0GoA6tTNpiAOISOsSLPyYgrhrCAMSrV5PIKxkArfU5IvLDJgNxbDwEMADxaBXyxR8TEE8dYQDi1MpD1J8wxjywvJ9DlgC01o+IyJkeBqOLCAhgACIQSURiuPhjAuKoJQxAfDp5iniHMWbzNAPA+r8n2jF0gwEIX6WYLv6YgPDrCQMQl0Y+o125DDBuBgAD4JN44H1hAMIWKMaLPyYg7JrCAMSjj+9IMQC+iUbeHwYgXAFjvvhjAsKtKwxAHNq0EeU0A7Bea/1qGwPTZ5gEMABh6rKwsCDz8/NhBlczKmqsJrAOm3MbYIewAxjKGPNeEdm1FMpBSwBFUfydUurGAOIkhI4IcHLuCHSNYVK6+DMTUEP4HppiAHqA3uOQ1tp/KMvynyYZgBeUUpt6jI+hOyaAAegY+JThUrz4YwLCqjGWAMLVo+3IrLUvlmV5zFgDwBsA28YfXv8YgHA0SfnijwkIp84wAGFq0VVUy/cBHLQEgAHoSoJwxsEAhKFFDhd/TEAYtYYBCE+HLiPCAHRJO/CxMAD9C5TTxR8T0H+9YQDC0qDraCYZgDVa6z90HQzj9UsAA9Av/xwv/piAfmsOAxAO/z4iMca8S0QW3NhvLwHwDoA+pOh/TAxAfxrs3btX1q1b118AAYxM/fUrAncB9Mu/p9HffifA2wagKIrvKKU+11NADNsTAU7A/YDn4v8Od2qwnxp0o2IA+mPf48h3G2M+f9AMQFEUryml8v450qMifQ3Nybd78lz8D2VOHXZfhxiAfpgHMOoeY8yRK5cAeAdAAMp0HQIn3m6Jc/GfzJta7LYWMQDd8w5lxKWNgMv3AGAAQlGnwzg46XYHm4v/dNbU43RGPluwBOCTZjx9YQDi0arVSDnhtor37c65+FfnTE1WZ9W0JQagKcE4j8cAxKmb96g52XpHekiHXPzrM6Yu6zOb5QgMwCzU4j9mpQHYoLV+Jf60yKAuAU60dYnVa79nzx5Zv359vYNofYAAtdl+IWAA2mcc4ghLbwU8sAdg/8X/kyLyryEGSkztEuAk2x7f3bt3y4YNG9obIIOeqc92RcYAtMs34N7/er8J+P4BA1AUxR1KqYsCDpbQWiLACbYdsFz8/XGlRv2xXNkTBqA9tiH3bK29rSzLLyzNADwjIseHHDCxtUOAk6t/rlz8/TOlTv0zdT1iANrhGkGvzxpjTlgyAG+6WoggaEL0TIATq1+gXPz98lzeG7Xqny0GwD/TSHocGmMOWzIAPAMgEtV8h8lJ1R9RLv7+WE7qiXr1yxgD4JdnTL25OwEwADEp1kKsnFD9QOXi74djlV6o2SqUqrXBAFTjlGIrDECKqtbMiZNpTWBjmnPxb86wbg/UbV1i49tjAPxwjLF0TTHUAAAgAElEQVQXDECMqnmOmRNpM6Bc/Jvxa3I0tduE3lvHYgCaM4y1BwxArMp5jJuT6OwwufjPzs7XkdRvM5IYgGb8Yj56yQDMaa3fiDkRYp+dACfQ2dhx8Z+NWxtHUcOzU8UAzM4u9iONMYe7TYDv11r/MvZkiH82Apw863Pj4l+fWdtHUMezEcYAzMYthaOMMccorfU5IvLDFBIih/oEOHHWY8bFvx6vLltTy/VpYwDqM0voiLNUURRfVkrdlFBSpFKDACfN6rC4+Fdn1VdL6rkeeQxAPV4ptbbWXu4MwLeUUpeklBi5VCfACbMaKy7+1TiF0Iqarq4CBqA6q9RaWmtvdUsAD4nIWaklRz7VCHCynM6Ji/90RqG1oK6rKYIBqMYp0VYPOwPwnyLywUQTJK0pBDhRrg6Ii3+8XyFqe7p2GIDpjFJtYa190S0BvKaUWpdqkuS1OgFOkpP5cPGP/9tDfa+uIQYg/hpvkMFeNwPAi4AaEIz9UE6Q4xXk4h97Zb8TPzU+WUsMQDp1PksmGIBZqCV0DCfHQ8Xk4p9QgY9Soc7Ha4oBSK/W62SEAahDK8G2nBgPFvU3v/mN/PEf/3GCSpMStX5oDWAA8v5eYADy1l84Kb5TAFz80/8yUO8Ha4wBSL/mV8sQA5C3/hiAkf5c/PP5ImAC3tEaA5BP3Y/LFAOQt/4YABHh4p/flwAT8JbmGID8an95xhiAvPXP3gBw8c/3C4AJwADkW/1vZY4ByLwCcj4JcvHPvPhFZGFhQebm5rIFwQxAttJjAPKW/q3sczUA3OpH9S8R2LZtm1x22WXZAVmzZo0sLi5mlzcJv0OAGYDMqyFXA8Avn8wLf0z67oL4Z3/2Z+L+m/Lnv/7rv+TJJ59MOUVyq0gAA1ARVKrNcjQA9957r5x//vmpSkpeEIAABCoRwABUwpRuoxwNwIknnijPPfdcuqKSGQQgAIEKBDAAFSCl3AQDkLK65AYBCEBgMgEMQObVkaMBYAkg86InfQhA4AABDEDmhZCjAXCSswkw88Ifk767HfDP//zPk98E+N///d/yk5/8hAKAAAYg9xrI1QDs2rVLNm7cmLv85C8iV1999YF/uX3m5+cPPAeBT74EmAHIV/sDmedqAFzuPAgo8+IXOXAffK6zQe67n/NDkKh+lgCyr4GcDQAmIO/yz732WQrLu/7ZA4D+Wc8ALMnPTEB+XwQu/m9pnuvsR34VPz5jlgAyrwROhG8VACYgny8CNf+O1hiAfOp+XKYYgLz1ZwZgmf6YgPS/DFz8D9YYA5B+za+WIQYgb/0xACv0xwSk+4Xg4n+othiAdOu9SmYYgCqUEm7DSfFQcXlTYHoFT52P1xQDkF6t18kIA1CHVoJtOTGOFxUTkE6xU+OTtcQApFPns2SiiqL4rVJq7SwHc0z8BDg5TtYQE0B9x09g9QwwAKkrPDk/a+0eZwBeUEptyhdD3pljAFbXHxMQ7/eD2p6uHQZgOqOEW/zCLQE8JCJnJZwkqa1CgJPk9PLABExnFFoL6rqaIhiAapwSbfWwmwG4RSl1aaIJktYUApwoq5UIJqAapxBaUdPVVcAAVGeVWktr7W3OAHxZKXVTasmRTzUCnCyrcXKtMAHVWfXVknquRx4DUI9XSq2ttZcrN/0/WgZIKTdyqUiAE2ZFUKNmmIB6vLpsTS3Xp40BqM8soSM+4QzAJq31CwklRSo1CHDSrAELE1AfVkdHUMezgcYAzMYthaOMMR9wBmBOa/1GCgmRQ30CnDjrM2M5YDZmbR1FDc9OFgMwO7vYjzTGHO4MgGitbezJEP9sBDh5zsYNEzA7N59HUr/NaGIAmvGL+WhjjMIAxKygh9g5gTaDyJ6AZvyaHE3tNqH31rEYgOYMY+0BAxCrch7j5iTaHCYmoDnDuj1Qt3WJjW+PAfDDMcZeMAAxquY5Zk6kfoBiAvxwrNILNVuFUrU2GIBqnFJshQFIUdWaOXEyrQlsleaYAH8sJ/VEvfpljAHwyzOm3pYbgDfdclBMwROrHwKcUP1wXOoFE+CX5/LeqFX/bDEA/plG0uPQGHPY0ibAZ0Tk+EgCJ0yPBDipeoQ56goT4J8pdeqfqesRA9AO1wh6fdYYc8IBA1AUxbeUUpdEEDQheibAidUzUEyAd6DUqHekb3eIAWiPbeA9bzfGbF2aAfhrEfmXwAMmvBYIcHJtASomwBtU6tMbyrEdYQDa5Rtw739jjPnXAwZARDZorV8JOFhCa4kAJ9iWwGICGoOlNhsjnNoBBmAqoiQbGGPeKyK7lgwATwNMUubpSXGSnc6oaYu9e/fKunXrmnaT1fHUZTdyYwC64RzaKO4OABcTBiA0ZTqOhxNtN8AxAdU5U5PVWTVtiQFoSjDO4zEAcermPWpOtt6RTuwQEzCdNfU4nZHPFhgAnzTj6QsDEI9WrUbKCbdVvId0jgmYzJta7LYW3WgYgO6ZhzDiIQagKIrXlFIsVIagTocxcNLtEPZoKEzAocypw+7rEAPQD/O+R7XW7inL8kgXx/I9AN8Rkc/1HRzjd0uAE2+3vJdGwwS8w50a7KcGMQD9ce955LuNMZ9faQDOEZEf9hwYw3dMgJNvx8CXDYcJEKH++qs/DEC/7Hsc/RPGmAcOMgAiskZr/Yceg2LoHghwAu4B+rIhFxYWZH5+vt8gehqd2usJ/LJh2QPQvwZdR2CMeZeILKw0ADwLoGslAhiPk3D/IuRoAqi7/uuOGYAwNOg6iqUNgBiArskHOB4n4jBEyckEUHNh1BwGIBwduowEA9Al7cDH4mQcjkA5mADqLZx6wwCEpUVX0Uw0AEVRvKCU2tRVIIzTPwFOyP1rsDyClE0AtRZWrWEAwtOj7YistS+WZXnM0jhv3wbo/kdRFH+vlPrHtoOg/3AIcFIOR4ulSFI0AdRZeHWGAQhTkzajstZeWZblP481ALwVsE30YfbNiTlMXVIyAdRYmDWGAQhXl7YiM8YcJSK7JxkA7gRoi3yg/XJyDlQYEVlcXJQ1a9aEG2CFyKivCpB6bMJtgD3C72Ho5ev/bviDlgDc/9Ba2x7iYsieCHCC7gl8xWFjNgHUVkWRe2yGAegRfg9DYwB6gB7ykJykQ1bnrdhiNAHUVfh1xRJAHBr5jLKKAXhERM70OSh9hUuAE3W42iyPLCYTQE3FUVMYgHh08hTpDmPM5uV9jVsC4J0AnmjH0A0n6xhUimcmgHqKp54wAHFp5SHat98BsNTXIQbA/YF9AB5QR9IFJ+xIhBqFGfJMALUUVy1hAOLTq0nEK6f/XV8YgCZEEziWk3Z8IoZoAqij+OoIAxCnZrNGjQGYlVzCx3HijlPckEwANRRnDWEA4tVtlsjrGIDHReSUWQbhmLgIcPKOS6/l0YZgAqifeOsHAxC3djWj32mMOWPlMZOWANxdAO5uAD6JE+AEHrfAfZoAaifu2sEAxK9fjQzONsY8XMkAuEZsBKyBNuKmnMQjFm8Ueh8mgLqJv24wAGloWCWLcdP/7rixMwAYgCpI02jDiTwNHbs0AdRMGjWDAUhHx2mZzGIA3hCRuWkd8/e4CXAyj1u/5dF3YQKol3TqBQOQlparZLNgjHnXuL9PnAEoiuIGpdSV2SDKNFFO6GkJ7/Scm2vHt1MradUKBiA9PcdlZK29sSzLq2oZABE5Qmv9uzwQ5ZslJ/X0tG/DBFAn6dUJBiBNTVdmZYx5j4i8XtcAsBEwg/rgxJ6myD5NADWSZo1gANLVdXlmk9b/XZuJSwDuj9wJkH6BcHJPV2MfJoD6SLc+MABpa7uUXRMD8F0R+XQemPLMkhN82ro3MQHURtq1gQFIX18R+Z4x5jOTMl11BmD/weu11q9mgSnTJDnJ5yH8YDColSh1UQtXtI3r1kW0iWYauDHmqP0/4nfPagBYBki8cDjRJy7wsvSqnuypCWoiHwJpZ7ra9P/UPQDsA0i7OFx2nOzT13gpwyrPCVhYWGjtNsJ8SMeTaVVTGE9GRLqcgA8DcIeIXATWNAlgANLUdVJW119/vbh/4z7btm2Tyy67LC8gmWeLAUi6ALYbY7auluG0PQDu2Hmt9e+TxpRxchiA/MSfdNKnFqiF/Aikm7Ex5t0isq+pAWAfQLo1whJAwtpOSm3NmjXilgOWf9z/27dv1XNFhqTST5kZgHQ1njb97zKvMgPgDICbAZhPF1W+mfGrLz/tP/7xj8uPf/zjgxLfsmWL3H///fnByDxjDECyBbBvNAOwaoKVDEBRFBcqpbYniyrjxDAA+Yn/2c9+Vu67776DEj/vvPPknnvuyQ9G5hljANIsAGvt1rIsp16zKxkAh4inAqZZKBiANHVdLSsMQH6aT8oYA5BmLVSZ/q+8BIABSLNIXFYYgHS1nZQZBiA/zTEAeWnehgF4SETOygtj+tliANLXeGWGGID8NMcAZKX5w8aYs6tkXHkJgNcDV8EZXxsMQHyaNY0YA9CUYDrHswSQjpZLmRhj/mj/s3v2VsmsjgFgH0AVopG1wQBEJpiHcDEAHiAm0gUGIBEhl6VRdfrfHVLLABRF8apSan16yPLNCAOQn/YYgPw0ZwkgD82ttbvLsnQvAKr0qWUAROQkrfVTlXqmURQEMABRyOQ1SAyAV5xRd8YMQNTyHRK8Uurk4XD4dNWs6hoAlgGqko2kHQYgEqE8hokB8Agz8q4wAJELuCL8OtP/tZcA3AE8FTCtgsEApKVnlWwwAFUo5dEGA5CUzgvGmHfVyWiWGQB3K6C7JZBPAgQwAAmIWDMFDEBNYAk3xwAkJe7ZxpiH62RU2wCMZgFsnUFoGy4BDEC42rQVGQagLbLx9YsBiE+zSRHXnf6faQlgZADeEJG5dNDlmwkGID/tMQD5aT4pYwxAMrWwaIw5vG42s84AsAxQl3Sg7Xft2iXr13NnZ6DytBIWBqAVrNF16r77GzdujC5uAh5L4BPGmAfqspnJALAMUBdzuO3vvfde+eQnPxlugETmnQAGwDvSKDv8/ve/L5/61KeijJ2gDyYwy/T/zEsAIwPwexGZR4i4CbiLvzMBfPIhgAHIR+vVMnWvgP7BD34AjPgJ7DPGvHuWNGaeARgMBidZa3ko0CzUAzpmbm5OFhYWAoqIUNomgAFom3Ac/c/Pz/Pdj0OqVaNUSv3pcDj82SypzGwARrMA3A0wC/XAjmEjYGCCtBwOBqBlwJF0zwbASISaEuas0/+NlgBGBuA/ReSDaWDMNwsMQF7aYwDy0ntSthiAJOrgF8aYP5k1k0YzACKyVmv921kH57gwCDz//PNy9NFHhxEMUbROAAPQOuLgB3jppZfk2GOPDT5OAlydQJ1X/47rqakB4N0ACVToli1b5P77708gE1KoQgADUIVS2m3OPfdcefDBB9NOMoPsmkz/N14CGC0D3CEiF2XAOukUWQZIWt6DksMA5KM10/9Ja32bMeYLTTJsPAMwMgFsBmyiQgDHYgACEKGjEDAAHYEOeBjW/wMWp2JoTX/9e5kBGBmAN0VkUDFumgVI4Pbbb5cLL7wwwMgIyTcBDIBvonH1d88998gFF1wQV9BEu5LATI/+XdmJlxmAwWBwirX2cTSKl4D7RbC4uBhvAkRemQAGoDKqJBu6Z38w4xe3tEqpM4bD4c6mWXgxACwDNJUhjOM5KYShQ9tRYADaJhx2/0z/h61Pleh8TP97WwIYGYBHROTMKsHTJkwCl112mWzbti3M4IjKGwEMgDeU0XX01a9+le94dKodEvDDxpizfaThbQbA7QHQWru9AHwiJsAsQMTiVQwdA1ARVILN+PUfv6i+fv17nQEYzQKwGTDy+vrpT38qJ554YuRZEP5qBDAAedaHe+DXcccdl2fy6WTtZfPfEg6fMwDCC4LirzI2A8av4bQMMADTCKX5dzb/xa9rURQnvPnmm8/6ysSrARjNAvBMAF/q9NTPyy+/LBs2bOhpdIZtmwAGoG3C4fW/d+9eWbduXXiBEVEtAj6n/70vAbgOi6K4QSl1Za2saBwUAWYBgpLDezAYAO9Ig+9wzZo13OYbvEqrB2itvaksy6/4TMP7DACzAD7l6a8vXhDUH/u2R8YAtE04rP558U9Yeswaje9f/63MAIwMAK8JnlXlgI7jjoCAxPAYCgbAI8wIumLnfwQiTQnRWvtiWZbH+M6klRkAbgn0LVM//bl7hq+//vp+BmfU1ghgAFpDG1zH7vvLdzg4WWoHZIw5XES8P6q1LQPgXhP8hojM1c6UA4IiwCxAUHJ4CQYD4AVjFJ3w6z8KmaYF6fXWv+WDtWYARGSD1vqVaZnx97AJuM1D+/btCztIoqtFAANQC1e0jdn4F610BwVujHmfiLzcRjZtGgA3C8AtgW2o1nGfX/va1+SKK67oeFSGa4sABqAtsuH0676zV199dTgBEcnMBNrY/LcUTNsGwL0bwL0jgE/kBF5//XWZn5+PPAvCdwQwAGnXgZuxO+KII9JOMp/szjbGPNxWuq0aABc0swBtSdd9v+wH6J55GyNiANqgGk6frPuHo0XTSNr89e9ia90AFEVxiVLqW01BcHz/BNgP0L8GPiLAAPigGGYfbpZuYWEhzOCIqhYBa+2lZVl+s9ZBNRu3bgCYBaipSODNP/zhD8uTTz4ZeJSEtxoBDECa9fGRj3xE3Mu8+KRBoO1f/53MALhBiqK4Vil1TRqykMUFF1wg3/72twERKQEMQKTCrRL2xRdfLHfddVd6iWWakbX2xrIsr2o7/U5mAJgFaFvG7vt3O4zZZdw9dx8jYgB8UAynDx72E44WviLp4td/ZzMAo1mAW5RSl/oCRD/9E8AE9K/BLBFgAGahFuYxXPzD1KVJVNbaW8uy/FKTPqoe29kMALMAVSWJqx3LAXHp5aLFAMSn2biImfZPQ8eVWXT167/TGQBmAdIsVpcVGwPj0hYDEJde46Jlw1/8Go7LoMtf/50bAGYB0ixalxW3CMajLQYgHq3GRcqtfnHrt1r0Xf7678UAcEdAusXrMuOJgeHriwEIX6NxEfKEvzh1qxq1tfa6siyvrdreR7tO9wAsBczTAX1IF24fbmOSe5UwnzAJYADC1GW1qLZt28Z3Kj7ZakXc9a//XmYARssAnxOR79SiQ+OoCMzNzfFEskAVwwAEKsyEsJjyj0uvWaLt4ql/4+LqZQZgZAJ4U+AslRLZMdwqGJ5gGIDwNBkXkfvuuLf68UmfQB+//nubAXADDwaDk6y1T6UvLRk6As8//7wcffTRwAiAAAYgABFWCeGll16SY489Nuwgic4ngc3GmB0+O6zaV28zAKNZgDedF6gaLO3iJuCWBXbt2iVr166NO5HIo8cAhCmg+268//3vF966GaY+bUXV16//XmcARjA3aK1faQss/YZJwL2u9Oc//zm/cnqSBwPQE/gJw7rvgruvnwt/WLp0EY0x5kgR2dPFWOPG6HUGYDQL8HsRme8LAOP2S+CKK65gnbNjCTAAHQOfMJyr/ZtvvjmMYIiiDwJ7jTF/1MfAS2P2bgDcEoDW2i0F8MmYgJsVuP322+X888/PmEI3qX/sYx+Tn/zkJwcN5n6BPvbYY90EkPEod955p3zxi1/k137GNbCUujHmcBFZ7BNFCAZAtNaPi8gpfYJg7LAIbNmy5cB9z2wc9K+LM1vjPkxB+2ftNvS5nfwPPvig/87pMWYCO4wxm/tOIAgD4CDwcKC+SyHs8d2jhv/iL/5CTjvtNDnuuONkw4YNYQccYHTuAn/EEUdMfD4Dz26YXbTdu3cf2NfiZlF+9KMfyeJirz/sZk+EIzsh0OfGv+UJBmMAiqK4SCl1Ryf0GQQCEIAABCDQAwFr7RfKsryth6EPGTIYA8AsQAjlQAwQgAAEINAmgVB+/bscgzIAIrJOa/1am/DpGwIQgAAEINAHgb5v+1uZc2gGQIqieEUpxQJvH9XJmBCAAAQg0AoBa+2usizf20rnM3YanAFgKWBGJTkMAhCAAASCJRDS1P8SpCANQFEUlyulvh6skgQGAQhAAAIQqEigr7f9TQsvSAPALMA02fg7BCAAAQjEQiDEX/+OXbAGwD0eWGvtHhPMBwIQgAAEIBAlAWPMe0Tk9RCDD9kAuIcDPSIiZ4YIjpggAAEIQAACUwg8YYw5NVRKQRsAlgJCLRviggAEIACBaQRCnfpfijt4AyAim7TWL0wDzd8hAAEIQAACoRAwxhwjIi+GEs+4OGIwAO7ZAL9WSm0MGSSxQQACEIAABBwBa+3LZVm+L3QaURgAlgJCLyPigwAEIACBJQKhT/3HtARwINbBYHCStfYpSgwCEIAABCAQKgFjzMki8nSo8S2PK5oZABd0URSvKqXWxwCWGCEAAQhAIDsCu40xR8WSdVQGgKWAWMqKOCEAAQjkRyCWqf/olgCWldLxWutn8istMoYABCAAgVAJGGNOEJFnQ41vXFzRzQCMZgGcATg+JtDECgEIQAACaRKw1r5YlqW77S+qT5QGgKWAqGqMYCEAAQgkTSC2qf+YlwCWYl+ntX4t6aoiOQhAAAIQCJqAMeZIEdkTdJATgot2BmA0C/Cd/eA/FyN4YoYABCAAgegJbDfGbI01i6gNwMgEvCEic7EKQNwQgAAEIBAlgUVjzOFRRj4KOnoDMDIBNmYRiB0CEIAABOIiEOu6/3LKSRiAwWBwurX20bjKh2ghAAEIQCBGAqNX/D4RY+zJGYDRLMB/iMiHYheE+CEAAQhAIGgCT48e9xt0kFWCS2IGYClRrTVLAVVUpw0EIAABCMxEIIWp/6XEkzIA7p1BWus3Z1KVgyAAAQhAAAKrEBht+ltMBVJqBsDpcorW+vFUBCIPCEAAAhDon0Aq6/7LSaZoAERr7TYEnt5/yRABBCAAAQgkQGCnMeaMBPI4KIUkDYDLUGvN8wFSq1bygQAEINA9gejv95+ELFkDMDIBbArs/svCiBCAAASSIZDSpr+VoiRtAESE9wUk8zUkEQhAAALdEoj5Of9VSKVuAKQoiouUUndUgUEbCEAAAhCAgCNgrd1aluX2lGkkbwBGSwFPichJKQtJbhCAAAQg4I3AE6Nd/946DLGjLAzAyASwKTDECiQmCEAAAmERSHbT30rM2RiAkQlgU2BYXzSigQAEIBAUgZQ3/WVtANxrg0e3BwZVcAQDAQhAAAL9E0jtSX/TiGY1AzCC8X6t9S+ngeHvEIAABCCQDwFjzDEi8mI+GYvkaADcnQGXKqVuyUlocoUABCAAgfEErLVfKcvyptz4ZGkAnMha60dE5MzcBCdfCEAAAhA4iECSj/mtonG2BmBkAl4VkfVVQNEGAhCAAASSI7DbGHNUcllVTChrAzAyAdweWLFYaAYBCEAgIQLZ3O43SbPsDcDIBHB7YELfalKBAAQgMI1ATrf7YQCmVIPWGhMw7RvD3yEAAQgkQICL/1siMgPwTjHzjIAEvtikAAEIQGA1Arnd678aCwzAwXTWa63dxkA+EIAABCCQGIHRhr/diaU1czoYgEPR8aCgmcuJAyEAAQiEScAY8wER+VWY0fUTFQZgPPfTtdaP9iMJo0IAAhCAgE8CxpgzRGSnzz5T6AsDMEFFrfU5IvLDFEQmBwhAAAK5EjDGfEJEHsg1/9XyxgCsQqcoiiuVUjdQOBCAAAQgEB8BpdT/GQ6H/xhf5N1EjAGYwnkwGFxrrb2mGzkYBQIQgAAEfBBQSl03HA6v9dFXqn1gACooiwmoAIkmEIAABAIhwMW/mhAYgGqcZDAY3Gqt/WLF5jSDAAQgAIEeCCilvjkcDi/tYejohsQA1JAME1ADFk0hAAEIdEyAi3894BiAerzcTAB7AmoyozkEIACBtgkw7V+fMAagPjNMwAzMOAQCEIBAWwS4+M9GFgMwGzcpiuIflFL/d8bDOQwCEIAABDwQUEpdNRwOb/TQVXZdYAAaSM7DghrA41AIQAACDQnwkJ9mADEAzfi5o3lscHOG9AABCECgFgEe71sL19jGGIDmDF0PvEDID0d6gQAEIDCVAC/2mYqoUgMMQCVMlRrxKuFKmGgEAQhAYHYCvNJ3dnYrj8QA+GPpeprTWr/ht0t6gwAEIAABR8AYc7iILELDDwEMgB+OB/WitbYtdEuXEIAABLIlYIzheuVZfYB6BrrU3WgmYK6l7ukWAhCAQC4EFke//HPJt7M8MQAtotZavyoi61scgq4hAAEIpExg92jNP+Uce8sNA9Ayeq31o+5WwZaHoXsIQAACqRHYYYzZnFpSIeWDAehAjaIoLldKfb2DoRgCAhCAQPQErLVfKsvy1ugTCTwBDEB3Am3SWr/Q3XCMBAEIQCA+Atzj351mGIDuWLuRuE2wW96MBgEIRESA2/y6FQsD0C3vA6Nxh0AP0BkSAhAImQA7/XtQBwPQA/SRCXhcRE7paXiGhQAEIBAKgaeNMSeHEkxOcWAAelS7KIqLlFJ39BgCQ0MAAhDojYC1dmtZltt7CyDzgTEA/RfAOq31a/2HQQQQgAAEuiNgjDlSRPZ0NyIjrSSAAQikJtgXEIgQhAEBCLRNgPX+tglX7B8DUBFUF814aFAXlBkDAhDokcBOY8wZPY7P0MsIYADCK4dTtNZugyAfCEAAAskQMMacKiJPJJNQAolgAMIUkecFhKkLUUEAAjMQMMYcJiLDGQ7lkBYJYABahNu0a631UyJyUtN+OB4CEIBATwR+Zoz5057GZtgpBDAA4ZcISwLha0SEEIDACgKjtf6dgAmXAAYgXG0Oioy7BCIRijAhAAF2+UdSAxiASIRyYWqt3UODLoooZEKFAATyInC3MebzeaUcb7YYgPi048FB8WlGxBBIngAP9olPYgxAfJodiLgoiheUUpsiDZ+wIQCBdAg8a4w5IZ108skEAxC31sdrrZ+JOwWihwAEYiUwuvA/G2v8uceNAUigArTWr4rI+gRSIQUIQCAOAruNMUfFESpRTiKAAUinNk4aPTcgnZ+elucAAAW6SURBVIzIBAIQCI7A6NW9TwcXGAHVJoABqI0s7AOKovi1Umpj2FESHQQgEBsBa+3LZVm+L7a4iXcyAQxAmtWxSWv9QpqpkRUEINA1AWPMMSLyYtfjMl67BDAA7fLttffRS4VO6TUIBocABGImsMMYsznmBIidGYCca+AIrfXvcgZA7hCAQH0Cxph3i8i++kdyRCwEmAGIRamGcRZFcalS6paG3XA4BCCQOAFr7VfKsrwp8TRJT0QwAJmVgdb6FRHZkFnapAsBCEwhYK3dVZblewGVDwEMQD5aL8+UxwnnqTtZQ2AsAR7jm2dhYADy1P1A1kVRXKKU+lbGCEgdAlkTsNZuLctye9YQMk4eA5Cx+Eupa60fEZEzQQEBCGRD4AljzKnZZEuiYwlgACiMJQJzo0cKrwUJBCCQLIF9xpj3iMgw2QxJrDIBDEBlVNk0ZH9ANlKTaEYEhsYY9xS/XRnlTKpTCGAAKJGxBLTWbknALQ3wgQAEIiaglDp5OBzy7P6INWwrdAxAW2QT6ZfnByQiJGnkSODzxpi7c0ycnKsRwABU45R9q6IorlVKXZM9CABAIHAC1trryrK8NvAwCS8AAhiAAESIKYSiKG5RSl0aU8zECoEcCFhrby3L8ks55EqOfghgAPxwzK4XjEB2kpNwoAS48AcqTARhYQAiECnkEIuiuEEpdWXIMRIbBFIkwFR/iqp2mxMGoFveyY7GZsFkpSWxwAhYay8py/L2wMIinAgJYAAiFC3kkLXWZ4nIQyHHSGwQiJTAZmPMjkhjJ+wACWAAAhQlkZA2aq1fEJG5RPIhDQj0QWDRGPMBHuDTB/r0x8QApK9x3xnOFUXxjFJqU9+BMD4EIiLwC2PMCTyyNyLFIgwVAxChaLGGXBTF15VSl8caP3FDoG0C1toby7K8qu1x6B8CjgAGgDronMBhhx12fFmWT7E80Dl6BgyTwFApdSqP6w1TnJSjwgCkrG4EuWmt3YZBt3GQDwRyI7DDGHM20/y5yR5OvhiAcLTIOpLBYHC6tdaZATYNZl0JySfvfu2fMRwOn0g+UxIMngAGIHiJ8guwKIpvKaUuyS9zMk6VgLV2e1mWW1PNj7ziJIABiFO3XKJeq7V2ryT+YC4Jk2dSBNxO/s0isjeprEgmGQIYgGSkTDuRwWDwIWutMwPzaWdKdpET2KeUOpsNfZGrmEn4GIBMhE4pTa31OSLyL+wXSEnVqHNZFJH/bYx5OOosCD47AhiA7CRPK+HRo4edGViTVmZkEziBfSLyGS76gatEeKsSwABQIMkQGAwGJ5Vl+V2l1PpkkiKRYAhYa3cXRfEZpveDkYRAGhLAADQEyOHBEnAbCO/gGQPB6hNLYA8bY9zufTbyxaIYcVYmgAGojIqGMRMoiuJCpdSNbCKMWcVOYt9nrb2yLMs7OxmNQSDQIwEMQI/wGbo3AvNa6xv2/6q7qLcIGDgkAtuNMe75+25dnw8EsiGAAchGahJdhcB6rfU1IvJpKGVB4HvGmOv26707i2xJEgITCGAAKA0IHErgiKIovjx6GiF3F8RdIYvW2lvLsvyGiLwedypEDwG/BDAAfnnSW6IERrcbXigipyeaYippuWfsf9MYsyOVhMgDAm0RwAC0RZZ+kycweiDRp0TkzOSTDTNBd5G/1xjzQJjhERUEwiaAAQhbH6KLj8D6oijOFZH/pZTaFF/44UVsrX1RRB4oy/J+EdkVXoREBIE4CWAA4tSNqOMjsEZr7ZYPTrPWnq6UWhdfCu1FbK3do5TaKSI7jTGPichCe6PRMwQg4AhgAKgDCIRBYIPW+jgR+ZCIuJkD9wbEQRihNY5iuH+Z5Bejf88aY57jl3xjpnQAgcYEMACNEdIBBDolMCciziz8T2vt+tFjj91swrr975xf2/bMgrXWPRFvr1Jqt/tXlqX75b7LGPP/9i97vCwi7sU4fCAAgQgI/H+qCsAgye3VhQAAAABJRU5ErkJggg==" + } + ], [ "icrc1:decimals", { @@ -270,13 +276,13 @@ [ "icrc1:name", { - "Text": "Modclub" + "Text": "DecideAI" } ], [ "icrc1:symbol", { - "Text": "MOD" + "Text": "DCD" } ], [ @@ -293,7 +299,7 @@ ] ], "icrc1_fee": [10000], - "icrc1_total_supply": 98074599901595070, + "icrc1_total_supply": 98074921642904690, "swap_params": { "params": { "min_participant_icp_e8s": 100000278, @@ -749,7 +755,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 100575407601909300, + "icrc1_total_supply": 100582137562060500, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -1139,20 +1145,14 @@ "index": "ux4b6-7qaaa-aaaaq-aaboa-cai", "governance": "umz53-fiaaa-aaaaq-aabmq-cai", "dapps": [ - "7xnbj-wqaaa-aaaap-aa4ea-cai", - "5escj-6iaaa-aaaap-aa4kq-cai", - "443xk-qiaaa-aaaap-aa4oq-cai", - "4sz2c-lyaaa-aaaap-aa4pq-cai", - "zjdgt-niaaa-aaaap-aa4qq-cai", - "zhbl3-wyaaa-aaaap-aa4rq-cai", - "yimtt-ziaaa-aaaap-ahe3q-cai", - "2jvhk-5aaaa-aaaap-ahewa-cai", - "4bli7-7iaaa-aaaap-ahd4a-cai", - "o7ouu-niaaa-aaaap-ahhdq-cai", - "zgfl7-pqaaa-aaaap-accpa-cai", - "inc34-eqaaa-aaaap-ahl2a-cai", "e3q3a-7yaaa-aaaap-ab3qq-cai", - "mtcuh-kiaaa-aaaap-ahasa-cai" + "mtcuh-kiaaa-aaaap-ahasa-cai", + "inc34-eqaaa-aaaap-ahl2a-cai", + "zgfl7-pqaaa-aaaap-accpa-cai", + "o7ouu-niaaa-aaaap-ahhdq-cai", + "4bli7-7iaaa-aaaap-ahd4a-cai", + "yimtt-ziaaa-aaaap-ahe3q-cai", + "2jvhk-5aaaa-aaaap-ahewa-cai" ], "archives": ["uz6mw-eaaaa-aaaaq-aabpa-cai"] }, @@ -1411,7 +1411,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 50198568174674376, + "icrc1_total_supply": 50203492593853900, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -2208,7 +2208,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 9957254379729974, + "icrc1_total_supply": 9957265470223630, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -3160,7 +3160,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 10029180495000078, + "icrc1_total_supply": 10029196177816938, "swap_params": { "params": { "min_participant_icp_e8s": 1000000000, @@ -4125,7 +4125,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 12591797645564276, + "icrc1_total_supply": 12596087081185460, "swap_params": { "params": { "min_participant_icp_e8s": 500000000, @@ -6031,7 +6031,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 99965872793128670, + "icrc1_total_supply": 99965959222447400, "swap_params": { "params": { "min_participant_icp_e8s": 800000000, diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json index fa9e7574167..ba429fae4d4 100644 --- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json +++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-2.json @@ -1475,7 +1475,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 100308522330214930, + "icrc1_total_supply": 100312053390123260, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -3352,7 +3352,7 @@ ] ], "icrc1_fee": [10000], - "icrc1_total_supply": 100319389738380, + "icrc1_total_supply": 100319926969670, "swap_params": { "params": { "min_participant_icp_e8s": 500000000, @@ -4083,7 +4083,7 @@ ] ], "icrc1_fee": [1000], - "icrc1_total_supply": 995766697000, + "icrc1_total_supply": 995765800000, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -5504,6 +5504,58 @@ "target_method_name": "sys_burn" } } + }, + { + "id": 1093, + "name": "DexAggr.verifyListingReferre", + "description": "Verify listing referrer", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "6zwik-gaaaa-aaaap-ab63q-cai", + "target_canister_id": "i2ied-uqaaa-aaaar-qaaza-cai", + "validator_method_name": "dexAggrVerifyListingReferrer", + "target_method_name": "verifyListingReferrer" + } + } + }, + { + "id": 1094, + "name": "DexAggr.dropListingReferrer", + "description": "Deleting a verified listing referrer", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "6zwik-gaaaa-aaaap-ab63q-cai", + "target_canister_id": "i2ied-uqaaa-aaaar-qaaza-cai", + "validator_method_name": "dexAggrDropListingReferrer", + "target_method_name": "dropListingReferrer" + } + } + }, + { + "id": 1095, + "name": "DexAggr.propose", + "description": "Listing Referrer creates a proposal to increase the trading pair score", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "6zwik-gaaaa-aaaap-ab63q-cai", + "target_canister_id": "i2ied-uqaaa-aaaar-qaaza-cai", + "validator_method_name": "dexAggrPropose", + "target_method_name": "propose" + } + } + }, + { + "id": 1096, + "name": "DexAggr.mngNFTUnStake", + "description": "Unlocking NFTs locked in DexAggregator by NFT holders", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "6zwik-gaaaa-aaaap-ab63q-cai", + "target_canister_id": "i2ied-uqaaa-aaaar-qaaza-cai", + "validator_method_name": "dexAggrMngNFTUnStake", + "target_method_name": "NFTUnStake" + } + } } ] }, @@ -6931,7 +6983,7 @@ ] ], "icrc1_fee": [1000000], - "icrc1_total_supply": 20023552077493028, + "icrc1_total_supply": 20024051237164500, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -8931,7 +8983,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 25091733263097804, + "icrc1_total_supply": 25096413085862880, "swap_params": { "params": { "min_participant_icp_e8s": 500000000, @@ -9776,6 +9828,32 @@ "target_method_name": "executeAddNewToken" } } + }, + { + "id": 22000, + "name": "Add CreatePlayer Callback Function", + "description": "Proposal to create the endpoint for adding the callback function to create a new player.", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "bboqb-jiaaa-aaaal-qb6ea-cai", + "target_canister_id": "bboqb-jiaaa-aaaal-qb6ea-cai", + "validator_method_name": "validateCreatePlayer", + "target_method_name": "executeCreatePlayer" + } + } + }, + { + "id": 23000, + "name": "Promote New Club", + "description": "Add Promoted Championship Team.", + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "bboqb-jiaaa-aaaal-qb6ea-cai", + "target_canister_id": "bboqb-jiaaa-aaaal-qb6ea-cai", + "validator_method_name": "validatePromoteNewClub", + "target_method_name": "executePromoteNewClub" + } + } } ] }, @@ -10359,7 +10437,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 10011813958302796, + "icrc1_total_supply": 10013494609757842, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -11397,7 +11475,7 @@ ] ], "icrc1_fee": [10000], - "icrc1_total_supply": 101057227531983300, + "icrc1_total_supply": 101071460163239330, "swap_params": { "params": { "min_participant_icp_e8s": 1000000000, @@ -11866,6 +11944,19 @@ "target_method_name": "removeFarmControllers" } } + }, + { + "id": 2101, + "name": "CreateFarm", + "description": null, + "function_type": { + "GenericNervousSystemFunction": { + "validator_canister_id": "arxpg-cyaaa-aaaag-qjzhq-cai", + "target_canister_id": "c5jrt-yaaaa-aaaag-qb5ra-cai", + "validator_method_name": "createValidate", + "target_method_name": "create" + } + } } ] }, @@ -12410,7 +12501,7 @@ ] ], "icrc1_fee": [1000000], - "icrc1_total_supply": 99270642249421360, + "icrc1_total_supply": 99276853751307380, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -13583,7 +13674,7 @@ ] ], "icrc1_fee": [1000000], - "icrc1_total_supply": 100001960765583300, + "icrc1_total_supply": 100002055316033820, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -14755,7 +14846,7 @@ ] ], "icrc1_fee": [100000], - "icrc1_total_supply": 100024384200525120, + "icrc1_total_supply": 100027613503665980, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, diff --git a/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json b/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json index 37d7663dc38..7cf43752384 100644 --- a/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json +++ b/frontend/src/tests/workflows/Launchpad/sns-agg-page-3.json @@ -1171,7 +1171,7 @@ { "id": 4, "name": "Add nervous system function", - "description": "Proposal to add a new, user-defined, nervous system function:a canister call which can then be executed by proposal.", + "description": "Proposal to add a new, user-defined, nervous system function: a canister call which can then be executed by proposal.", "function_type": { "NativeNervousSystemFunction": {} } @@ -1179,7 +1179,7 @@ { "id": 5, "name": "Remove nervous system function", - "description": "Proposal to remove a user-defined nervous system function,which will be no longer executable by proposal.", + "description": "Proposal to remove a user-defined nervous system function, which will be no longer executable by proposal.", "function_type": { "NativeNervousSystemFunction": {} } @@ -1187,7 +1187,7 @@ { "id": 6, "name": "Execute nervous system function", - "description": "Proposal to execute a user-defined nervous system function,previously added by an AddNervousSystemFunction proposal. A canister call will be made when executed.", + "description": "Proposal to execute a user-defined nervous system function, previously added by an AddNervousSystemFunction proposal. A canister call will be made when executed.", "function_type": { "NativeNervousSystemFunction": {} } @@ -1234,7 +1234,7 @@ }, { "id": 12, - "name": "Mint SNS Tokens", + "name": "Mint SNS tokens", "description": "Proposal to mint SNS tokens to a specified recipient.", "function_type": { "NativeNervousSystemFunction": {} @@ -1431,7 +1431,7 @@ ] ], "icrc1_fee": [10000], - "icrc1_total_supply": 999955730000, + "icrc1_total_supply": 999950010000, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -1812,7 +1812,7 @@ ] ], "icrc1_fee": [10000], - "icrc1_total_supply": 99999922260000, + "icrc1_total_supply": 99999918810000, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -2193,7 +2193,7 @@ ] ], "icrc1_fee": [200000], - "icrc1_total_supply": 1041796707162557600, + "icrc1_total_supply": 1041793657933846300, "swap_params": { "params": { "min_participant_icp_e8s": 100000000, @@ -2306,7 +2306,7 @@ "index": "iidmm-fiaaa-aaaaq-aadmq-cai", "governance": "jfnic-kaaaa-aaaaq-aadla-cai", "dapps": [], - "archives": [] + "archives": ["i2f3v-jyaaa-aaaaq-aadpq-cai"] }, "meta": { "url": "https://forum.dfinity.org/t/introducing-waterneuron/30897", @@ -2809,7 +2809,7 @@ ] ], "icrc1_fee": [1000000], - "icrc1_total_supply": 11595996506000000, + "icrc1_total_supply": 11598599548034456, "swap_params": { "params": { "min_participant_icp_e8s": 1000000000, @@ -3599,8 +3599,8 @@ "decentralization_sale_open_timestamp_seconds": 1719664200 }, "derived": { - "buyer_total_icp_e8s": 1426116042305, - "sns_tokens_per_icp": 420.7231 + "buyer_total_icp_e8s": 1661637432198, + "sns_tokens_per_icp": 361.0896 } }, "icrc1_metadata": [ @@ -3938,12 +3938,12 @@ } }, "derived_state": { - "sns_tokens_per_icp": 420.7231140136719, - "buyer_total_icp_e8s": 1426116042305, + "sns_tokens_per_icp": 361.089599609375, + "buyer_total_icp_e8s": 1661637432198, "cf_participant_count": 0, - "neurons_fund_participation_icp_e8s": 29552636323, - "direct_participation_icp_e8s": 1396563405982, - "direct_participant_count": 129, + "neurons_fund_participation_icp_e8s": 73526246493, + "direct_participation_icp_e8s": 1588111185705, + "direct_participant_count": 185, "cf_neuron_count": 0 }, "lifecycle": { diff --git a/frontend/src/tests/workflows/NnsProposalDetail.spec.ts b/frontend/src/tests/workflows/NnsProposalDetail.spec.ts index 6d59495076e..112ce5a3575 100644 --- a/frontend/src/tests/workflows/NnsProposalDetail.spec.ts +++ b/frontend/src/tests/workflows/NnsProposalDetail.spec.ts @@ -2,7 +2,6 @@ import { resetNeuronsApiService } from "$lib/api-services/governance.api-service import * as governanceApi from "$lib/api/governance.api"; import { queryProposal } from "$lib/api/proposals.api"; import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants"; -import { DEFAULT_PROPOSALS_FILTERS } from "$lib/constants/proposals.constants"; import NnsProposalDetail from "$lib/pages/NnsProposalDetail.svelte"; import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store"; import { authStore } from "$lib/stores/auth.store"; @@ -16,7 +15,12 @@ import { import { mockNeuron } from "$tests/mocks/neurons.mock"; import { mockProposalInfo } from "$tests/mocks/proposal.mock"; import { AnonymousIdentity } from "@dfinity/agent"; -import { ProposalRewardStatus, Vote } from "@dfinity/nns"; +import { + ProposalRewardStatus, + ProposalStatus, + Topic, + Vote, +} from "@dfinity/nns"; import { waitFor } from "@testing-library/dom"; import { render } from "@testing-library/svelte"; import { tick } from "svelte"; @@ -25,9 +29,9 @@ vi.mock("$lib/api/governance.api"); const proposal = { ...mockProposalInfo, - topic: DEFAULT_PROPOSALS_FILTERS.topics[0], + topic: Topic.NetworkEconomics, rewardStatus: ProposalRewardStatus.AcceptVotes, - status: DEFAULT_PROPOSALS_FILTERS.status[0], + status: ProposalStatus.Open, ballots: [ { neuronId: mockNeuron.neuronId, diff --git a/frontend/src/tests/workflows/NnsProposals.spec.ts b/frontend/src/tests/workflows/NnsProposals.spec.ts index 3752f9c30b9..8ed71f91584 100644 --- a/frontend/src/tests/workflows/NnsProposals.spec.ts +++ b/frontend/src/tests/workflows/NnsProposals.spec.ts @@ -14,16 +14,16 @@ import { } from "$tests/mocks/auth.store.mock"; import { mockProposalInfo } from "$tests/mocks/proposal.mock"; import { AnonymousIdentity } from "@dfinity/agent"; -import { ProposalRewardStatus } from "@dfinity/nns"; +import { ProposalRewardStatus, ProposalStatus, Topic } from "@dfinity/nns"; import { waitFor } from "@testing-library/dom"; import { render } from "@testing-library/svelte"; import type { Subscriber } from "svelte/store"; const proposal = { ...mockProposalInfo, - topic: DEFAULT_PROPOSALS_FILTERS.topics[0], + topic: Topic.NetworkEconomics, rewardStatus: ProposalRewardStatus.AcceptVotes, - status: DEFAULT_PROPOSALS_FILTERS.status[0], + status: ProposalStatus.Open, }; vi.mock("$lib/api/proposals.api", () => { diff --git a/rs/backend/Cargo.toml b/rs/backend/Cargo.toml index f14396f022c..d281674f9b7 100644 --- a/rs/backend/Cargo.toml +++ b/rs/backend/Cargo.toml @@ -14,7 +14,7 @@ itertools = "0.13.0" lazy_static = "1.5.0" lzma-rs = "0.3.0" regex = "1.10.5" -serde = "1.0.203" +serde = "1.0.204" serde_bytes = "0.11.15" serde_cbor = "0.11.2" sha2 = "0.10.8" diff --git a/rs/backend/src/accounts_store.rs b/rs/backend/src/accounts_store.rs index 424564b1862..5a2877948b3 100644 --- a/rs/backend/src/accounts_store.rs +++ b/rs/backend/src/accounts_store.rs @@ -12,7 +12,7 @@ use ic_nns_common::types::NeuronId; use ic_nns_constants::{CYCLES_MINTING_CANISTER_ID, GOVERNANCE_CANISTER_ID}; use ic_stable_structures::{storable::Bound, Storable}; use icp_ledger::Operation::{self, Approve, Burn, Mint, Transfer}; -use icp_ledger::{AccountIdentifier, BlockIndex, Memo, Subaccount, Tokens}; +use icp_ledger::{AccountIdentifier, BlockIndex, Memo, Subaccount}; use itertools::Itertools; use on_wire::{FromWire, IntoWire}; use serde::Deserialize; @@ -216,31 +216,6 @@ impl PartialOrd for NamedCanister { } } -#[derive(Copy, Clone, CandidType, Deserialize, Debug, Eq, PartialEq)] -pub struct CreateCanisterArgs { - pub controller: PrincipalId, - pub amount: Tokens, - pub refund_address: AccountIdentifier, -} - -#[derive(Copy, Clone, CandidType, Deserialize, Debug, Eq, PartialEq)] -pub struct TopUpCanisterArgs { - pub principal: PrincipalId, - pub canister_id: CanisterId, - pub amount: Tokens, - pub refund_address: AccountIdentifier, -} - -#[derive(Clone, CandidType, Deserialize, Debug, Eq, PartialEq)] -pub struct RefundTransactionArgs { - pub recipient_principal: PrincipalId, - pub from_sub_account: Subaccount, - pub amount: Tokens, - pub original_transaction_block_height: BlockIndex, - pub refund_address: AccountIdentifier, - pub error_message: String, -} - #[derive(Copy, Clone, CandidType, Deserialize, Debug, Eq, PartialEq)] pub enum TransactionType { Burn, @@ -579,7 +554,7 @@ impl AccountsStore { from, to, spender: _, - amount, + amount: _, fee: _, } => { let default_transaction_type = if matches!(transfer, Transfer { .. }) { @@ -601,15 +576,7 @@ impl AccountsStore { &canister_ids, default_transaction_type, ); - self.process_transaction_type( - transaction_type, - principal, - from, - to, - memo, - amount, - block_height, - ); + self.process_transaction_type(transaction_type, principal, from, to, memo, block_height); } } else if let Some(neuron_details) = self.neuron_accounts.get(&to) { // Handle the case where people top up their neuron from an external account @@ -779,13 +746,6 @@ impl AccountsStore { } } - pub fn enqueue_transaction_to_be_refunded(&mut self, args: RefundTransactionArgs) { - self.multi_part_transactions_processor.push( - args.original_transaction_block_height, - MultiPartTransactionToBeProcessed::RefundTransaction(args), - ); - } - #[must_use] pub fn get_block_height_synced_up_to(&self) -> Option { self.block_height_synced_up_to @@ -941,27 +901,14 @@ impl AccountsStore { } fn is_create_canister_transaction(memo: Memo, to: &AccountIdentifier, principal: &PrincipalId) -> bool { - // There are now 2 ways to create a canister. - // The new way involves sending ICP directly to an account controlled by the CMC, the NNS + // Creating a canister involves sending ICP directly to an account controlled by the CMC, the NNS // Dapp canister then notifies the CMC of the transfer. - // The old way involves sending ICP to an account controlled by the NNS Dapp, the NNS Dapp - // then forwards the ICP on to an account controlled by the CMC and calls notify on the - // ledger which in turns notifies the CMC. if memo == MEMO_CREATE_CANISTER { let subaccount = principal.into(); - { - // Check if sent to CMC account for this principal - let expected_to = AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)); - if *to == expected_to { - return true; - } - } - { - // Check if sent to NNS Dapp account for this principal - let expected_to = AccountIdentifier::new(dfn_core::api::id().get(), Some(subaccount)); - if *to == expected_to { - return true; - } + // Check if sent to CMC account for this principal + let expected_to = AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)); + if *to == expected_to { + return true; } } false @@ -972,28 +919,15 @@ impl AccountsStore { to: &AccountIdentifier, canister_ids: &[CanisterId], ) -> Option { - // There are now 2 ways to top up a canister. - // The new way involves sending ICP directly to an account controlled by the CMC, the NNS + // Topping up a canister involves sending ICP directly to an account controlled by the CMC, the NNS // Dapp canister then notifies the CMC of the transfer. - // The old way involves sending ICP to an account controlled by the NNS Dapp, the NNS Dapp - // then forwards the ICP on to an account controlled by the CMC and calls notify on the - // ledger which in turns notifies the CMC. if memo == MEMO_TOP_UP_CANISTER { for canister_id in canister_ids { let subaccount = (&canister_id.get()).into(); - { - // Check if sent to CMC account for this canister - let expected_to = AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)); - if *to == expected_to { - return Some(*canister_id); - } - } - { - // Check if sent to NNS Dapp account for this canister - let expected_to = AccountIdentifier::new(dfn_core::api::id().get(), Some(subaccount)); - if *to == expected_to { - return Some(*canister_id); - } + // Check if sent to CMC account for this canister + let expected_to = AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)); + if *to == expected_to { + return Some(*canister_id); } } } @@ -1033,7 +967,6 @@ impl AccountsStore { from: AccountIdentifier, to: AccountIdentifier, memo: Memo, - amount: Tokens, block_height: BlockIndex, ) { match transaction_type { @@ -1066,37 +999,16 @@ impl AccountsStore { } } TransactionType::CreateCanister => { - if to == AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some((&principal).into())) { - self.multi_part_transactions_processor.push( - block_height, - MultiPartTransactionToBeProcessed::CreateCanisterV2(principal), - ); - } else { - let args = CreateCanisterArgs { - controller: principal, - amount, - refund_address: from, - }; - self.multi_part_transactions_processor - .push(block_height, MultiPartTransactionToBeProcessed::CreateCanister(args)); - } + self.multi_part_transactions_processor.push( + block_height, + MultiPartTransactionToBeProcessed::CreateCanisterV2(principal), + ); } TransactionType::TopUpCanister(canister_id) => { - if to == AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some((&canister_id.get()).into())) { - self.multi_part_transactions_processor.push( - block_height, - MultiPartTransactionToBeProcessed::TopUpCanisterV2(principal, canister_id), - ); - } else { - let args = TopUpCanisterArgs { - principal, - canister_id, - amount, - refund_address: from, - }; - self.multi_part_transactions_processor - .push(block_height, MultiPartTransactionToBeProcessed::TopUpCanister(args)); - } + self.multi_part_transactions_processor.push( + block_height, + MultiPartTransactionToBeProcessed::TopUpCanisterV2(principal, canister_id), + ); } _ => {} }; diff --git a/rs/backend/src/canisters/ledger.rs b/rs/backend/src/canisters/ledger.rs index ec3f4848ccd..399431e3966 100644 --- a/rs/backend/src/canisters/ledger.rs +++ b/rs/backend/src/canisters/ledger.rs @@ -1,5 +1,5 @@ use dfn_core::CanisterId; -use dfn_protobuf::{protobuf, ToProto}; +use dfn_protobuf::protobuf; use ic_ledger_core::block::EncodedBlock; use ic_nns_constants::LEDGER_CANISTER_ID; use icp_ledger::protobuf::get_blocks_response::GetBlocksContent; @@ -7,22 +7,7 @@ use icp_ledger::protobuf::{ ArchiveIndexResponse as ArchiveIndexResponsePb, GetBlocksResponse as GetBlocksResponsePb, TipOfChainRequest as TipOfChainRequestPb, TipOfChainResponse as TipOfChainResponsePb, }; -use icp_ledger::{AccountBalanceArgs, BlockIndex, GetBlocksArgs, SendArgs, Tokens}; - -pub async fn send(request: SendArgs) -> Result { - dfn_core::call(LEDGER_CANISTER_ID, "send_pb", protobuf, request.into_proto()) - .await - .map_err(|e| e.1) -} - -pub async fn account_balance(request: AccountBalanceArgs) -> Result { - let tokens: icp_ledger::protobuf::Tokens = - dfn_core::call(LEDGER_CANISTER_ID, "account_balance_pb", protobuf, request.into_proto()) - .await - .map_err(|e| e.1)?; - - Ok(Tokens::from_e8s(tokens.e8s)) -} +use icp_ledger::{BlockIndex, GetBlocksArgs}; pub async fn tip_of_chain() -> Result { let response: TipOfChainResponsePb = diff --git a/rs/backend/src/multi_part_transactions_processor.rs b/rs/backend/src/multi_part_transactions_processor.rs index 92dbfa04399..b18158b46e5 100644 --- a/rs/backend/src/multi_part_transactions_processor.rs +++ b/rs/backend/src/multi_part_transactions_processor.rs @@ -1,4 +1,3 @@ -use crate::accounts_store::{CreateCanisterArgs, RefundTransactionArgs, TopUpCanisterArgs}; use candid::CandidType; use ic_base_types::{CanisterId, PrincipalId}; use icp_ledger::AccountIdentifier; @@ -15,9 +14,6 @@ pub struct MultiPartTransactionsProcessor { pub enum MultiPartTransactionToBeProcessed { StakeNeuron(PrincipalId, Memo), TopUpNeuron(PrincipalId, Memo), - CreateCanister(CreateCanisterArgs), - TopUpCanister(TopUpCanisterArgs), - RefundTransaction(RefundTransactionArgs), CreateCanisterV2(PrincipalId), TopUpCanisterV2(PrincipalId, CanisterId), // ParticipateSwap(buyer_id, from, to, swap_canister_id) diff --git a/rs/backend/src/periodic_tasks_runner.rs b/rs/backend/src/periodic_tasks_runner.rs index 419d9e18308..891db8576c3 100644 --- a/rs/backend/src/periodic_tasks_runner.rs +++ b/rs/backend/src/periodic_tasks_runner.rs @@ -1,18 +1,12 @@ -use crate::accounts_store::{CreateCanisterArgs, RefundTransactionArgs, TopUpCanisterArgs}; -use crate::canisters::ledger; use crate::canisters::{cmc, governance}; -use crate::constants::{MEMO_CREATE_CANISTER, MEMO_TOP_UP_CANISTER}; use crate::multi_part_transactions_processor::MultiPartTransactionToBeProcessed; use crate::state::STATE; use crate::{ledger_sync, Cycles}; use cycles_minting_canister::{NotifyCreateCanister, NotifyError, NotifyTopUp}; use dfn_core::api::{CanisterId, PrincipalId}; use ic_nns_common::types::NeuronId; -use ic_nns_constants::CYCLES_MINTING_CANISTER_ID; use ic_nns_governance::pb::v1::{claim_or_refresh_neuron_from_account_response, ClaimOrRefreshNeuronFromAccount}; -use icp_ledger::{ - AccountBalanceArgs, AccountIdentifier, BlockIndex, Memo, SendArgs, Subaccount, Tokens, DEFAULT_TRANSFER_FEE, -}; +use icp_ledger::{BlockIndex, Memo}; pub async fn run_periodic_tasks() { ledger_sync::sync_transactions().await; @@ -35,15 +29,6 @@ pub async fn run_periodic_tasks() { MultiPartTransactionToBeProcessed::TopUpNeuron(principal, memo) => { handle_top_up_neuron(principal, memo).await; } - MultiPartTransactionToBeProcessed::CreateCanister(args) => { - handle_create_canister(block_height, args).await; - } - MultiPartTransactionToBeProcessed::TopUpCanister(args) => { - handle_top_up_canister(block_height, args).await; - } - MultiPartTransactionToBeProcessed::RefundTransaction(args) => { - handle_refund(args).await; - } MultiPartTransactionToBeProcessed::CreateCanisterV2(controller) => { handle_create_canister_v2(block_height, controller).await; } @@ -92,41 +77,6 @@ async fn handle_create_canister_v2(block_height: BlockIndex, controller: Princip } } -async fn handle_create_canister(block_height: BlockIndex, args: CreateCanisterArgs) { - match create_canister(args.controller, args.amount).await { - Ok(Ok(canister_id)) => STATE.with(|s| { - s.accounts_store - .borrow_mut() - .attach_newly_created_canister(args.controller, canister_id); - }), - Ok(Err(error)) => { - let was_refunded = matches!(error, NotifyError::Refunded { .. }); - if was_refunded { - let subaccount = (&args.controller).into(); - enqueue_create_or_top_up_canister_refund( - args.controller, - subaccount, - block_height, - args.refund_address, - error.to_string(), - ) - .await; - } - } - Err(error) => { - let subaccount = (&args.controller).into(); - enqueue_create_or_top_up_canister_refund( - args.controller, - subaccount, - block_height, - args.refund_address, - error, - ) - .await; - } - } -} - async fn handle_top_up_canister_v2(block_height: BlockIndex, principal: PrincipalId, canister_id: CanisterId) { match top_up_canister_v2(block_height, canister_id).await { Ok(Ok(_)) => (), @@ -143,53 +93,6 @@ async fn handle_top_up_canister_v2(block_height: BlockIndex, principal: Principa } } -async fn handle_top_up_canister(block_height: BlockIndex, args: TopUpCanisterArgs) { - match top_up_canister(args.canister_id, args.amount).await { - Ok(Ok(_)) => (), - Ok(Err(error)) => { - let was_refunded = matches!(error, NotifyError::Refunded { .. }); - if was_refunded { - let subaccount = (&args.principal).into(); - enqueue_create_or_top_up_canister_refund( - args.principal, - subaccount, - block_height, - args.refund_address, - error.to_string(), - ) - .await; - } - } - Err(error) => { - let subaccount = (&args.principal).into(); - enqueue_create_or_top_up_canister_refund( - args.principal, - subaccount, - block_height, - args.refund_address, - error, - ) - .await; - } - } -} - -async fn handle_refund(args: RefundTransactionArgs) { - let send_request = SendArgs { - memo: Memo(0), - amount: args.amount, - fee: DEFAULT_TRANSFER_FEE, - from_subaccount: Some(args.from_sub_account), - to: args.refund_address, - created_at_time: None, - }; - - match ledger::send(send_request.clone()).await { - Ok(_block_height) => (), - Err(_error) => (), - } -} - async fn claim_or_refresh_neuron(principal: PrincipalId, memo: Memo) -> Result { let request = ClaimOrRefreshNeuronFromAccount { controller: Some(principal), @@ -222,34 +125,6 @@ async fn create_canister_v2( cmc::notify_create_canister(notify_request).await } -async fn create_canister(principal: PrincipalId, amount: Tokens) -> Result, String> { - // We need to hold back 1 transaction fee for the 'send' and also 1 for the 'notify' - let send_amount = Tokens::from_e8s(amount.get_e8s() - (2 * DEFAULT_TRANSFER_FEE.get_e8s())); - let subaccount: Subaccount = (&principal).into(); - - let send_request = SendArgs { - memo: MEMO_CREATE_CANISTER, - amount: send_amount, - fee: DEFAULT_TRANSFER_FEE, - from_subaccount: Some(subaccount), - to: AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)), - created_at_time: None, - }; - - let block_index = ledger::send(send_request.clone()).await?; - - #[allow(deprecated)] - let notify_request = NotifyCreateCanister { - block_index, - controller: principal, - subnet_type: None, - subnet_selection: None, - settings: None, - }; - - cmc::notify_create_canister(notify_request).await -} - async fn top_up_canister_v2( block_index: BlockIndex, canister_id: CanisterId, @@ -261,60 +136,3 @@ async fn top_up_canister_v2( cmc::notify_top_up_canister(notify_request).await } - -async fn top_up_canister(canister_id: CanisterId, amount: Tokens) -> Result, String> { - // We need to hold back 1 transaction fee for the 'send' and also 1 for the 'notify' - let send_amount = Tokens::from_e8s(amount.get_e8s() - (2 * DEFAULT_TRANSFER_FEE.get_e8s())); - let subaccount: Subaccount = (&canister_id).into(); - - let send_request = SendArgs { - memo: MEMO_TOP_UP_CANISTER, - amount: send_amount, - fee: DEFAULT_TRANSFER_FEE, - from_subaccount: Some(subaccount), - to: AccountIdentifier::new(CYCLES_MINTING_CANISTER_ID.into(), Some(subaccount)), - created_at_time: None, - }; - - let block_index = ledger::send(send_request.clone()).await?; - - let notify_request = NotifyTopUp { - block_index, - canister_id, - }; - - cmc::notify_top_up_canister(notify_request).await -} - -async fn enqueue_create_or_top_up_canister_refund( - principal: PrincipalId, - subaccount: Subaccount, - block_height: BlockIndex, - refund_address: AccountIdentifier, - error_message: String, -) { - let from_account = AccountIdentifier::new(dfn_core::api::id().get(), Some(subaccount)); - let balance_request = AccountBalanceArgs { account: from_account }; - - match ledger::account_balance(balance_request).await { - Ok(balance) => { - let refund_amount_e8s = balance.get_e8s().saturating_sub(DEFAULT_TRANSFER_FEE.get_e8s()); - if refund_amount_e8s > 0 { - let refund_args = RefundTransactionArgs { - recipient_principal: principal, - from_sub_account: subaccount, - amount: Tokens::from_e8s(refund_amount_e8s), - original_transaction_block_height: block_height, - refund_address, - error_message, - }; - STATE.with(|s| { - s.accounts_store - .borrow_mut() - .enqueue_transaction_to_be_refunded(refund_args); - }); - } - } - Err(_error) => (), - }; -} diff --git a/rs/backend/src/state/partitions.rs b/rs/backend/src/state/partitions.rs index 4fd4775ae39..186e129ad0d 100644 --- a/rs/backend/src/state/partitions.rs +++ b/rs/backend/src/state/partitions.rs @@ -197,7 +197,7 @@ impl Partitions { let min_pages: u64 = u64::try_from(bytes.len()) .unwrap_or_else(|err| unreachable!("Buffer for growing_write is longer than 2**64 bytes?? Err: {err}")) .saturating_add(offset) - .div_ceil(WASM_PAGE_SIZE_IN_BYTES as u64); + .div_ceil(WASM_PAGE_SIZE_IN_BYTES); let current_pages = memory.size(); if current_pages < min_pages { memory.grow(min_pages - current_pages); @@ -208,9 +208,7 @@ impl Partitions { /// Reads the exact number of bytes needed to fill `buffer`. pub fn read_exact(&self, memory_id: MemoryId, offset: u64, buffer: &mut [u8]) -> Result<(), String> { let memory = self.get(memory_id); - let bytes_in_memory = memory.size() - * u64::try_from(WASM_PAGE_SIZE_IN_BYTES) - .unwrap_or_else(|err| unreachable!("Wasm page size is fixed and well within the range of u64: {err}")); + let bytes_in_memory = memory.size() * WASM_PAGE_SIZE_IN_BYTES; if offset.saturating_add(u64::try_from(buffer.len()).unwrap_or_else(|err| { unreachable!("Buffer for read_exact is longer than 2**64. This seems extremely implausible. Err: {err}") })) > bytes_in_memory diff --git a/rs/backend/src/state/partitions/tests.rs b/rs/backend/src/state/partitions/tests.rs index 89bad1e9b2d..45f481fa698 100644 --- a/rs/backend/src/state/partitions/tests.rs +++ b/rs/backend/src/state/partitions/tests.rs @@ -87,7 +87,7 @@ fn partitions_should_get_correct_virtual_memory() { ); // Grow a partition in the memory manager. The partitions should grow with it. - let toy_metadata_fill = [9u8; WASM_PAGE_SIZE_IN_BYTES]; + let toy_metadata_fill = [9u8; WASM_PAGE_SIZE_IN_BYTES as usize]; memory_manager.get(PartitionType::Metadata.memory_id()).grow(1); let partitions = Partitions::try_from_memory(Rc::clone(&toy_memory)) .expect("Failed to get partitions when one partition has grown"); @@ -159,7 +159,7 @@ fn should_be_able_to_convert_memory_to_partitions_and_back() { /// Memory hasher, used to check that the memory is the same before and after. fn hash_memory(memory: &DefaultMemoryImpl) -> [u8; 32] { let mut hasher = Sha256::new(); - let mut buf = [0u8; WASM_PAGE_SIZE_IN_BYTES]; + let mut buf = [0u8; WASM_PAGE_SIZE_IN_BYTES as usize]; for page_num in 0..memory.size() { let byte_offset = page_num * u64::try_from(WASM_PAGE_SIZE_IN_BYTES).expect("Amazingly large pages"); memory.read(byte_offset, &mut buf); @@ -211,13 +211,13 @@ fn growing_write_test_vectors() -> Vec { }, GrowingWriteTestVector { initial_memory_pages: 0, - write_offset: WASM_PAGE_SIZE_IN_BYTES as u64, + write_offset: WASM_PAGE_SIZE_IN_BYTES, buffer: vec![], expected_final_memory_pages: 1, }, GrowingWriteTestVector { initial_memory_pages: 0, - write_offset: (WASM_PAGE_SIZE_IN_BYTES as u64) + 1, + write_offset: (WASM_PAGE_SIZE_IN_BYTES) + 1, buffer: vec![], expected_final_memory_pages: 2, }, @@ -235,19 +235,19 @@ fn growing_write_test_vectors() -> Vec { }, GrowingWriteTestVector { initial_memory_pages: 0, - write_offset: WASM_PAGE_SIZE_IN_BYTES as u64 - 4, + write_offset: WASM_PAGE_SIZE_IN_BYTES - 4, buffer: vec![1, 2, 3, 4], expected_final_memory_pages: 1, }, GrowingWriteTestVector { initial_memory_pages: 0, - write_offset: (WASM_PAGE_SIZE_IN_BYTES as u64) - 3, + write_offset: (WASM_PAGE_SIZE_IN_BYTES) - 3, buffer: vec![1, 2, 3, 4], expected_final_memory_pages: 2, }, GrowingWriteTestVector { initial_memory_pages: 0, - write_offset: (WASM_PAGE_SIZE_IN_BYTES as u64), + write_offset: (WASM_PAGE_SIZE_IN_BYTES), buffer: vec![1, 2, 3, 4], expected_final_memory_pages: 2, }, diff --git a/rs/backend/src/stats.rs b/rs/backend/src/stats.rs index 64d34a7c15a..767dbaa573c 100644 --- a/rs/backend/src/stats.rs +++ b/rs/backend/src/stats.rs @@ -9,7 +9,7 @@ mod tests; #[cfg(target_arch = "wasm32")] use core::arch::wasm32::memory_size as wasm_memory_size; #[cfg(target_arch = "wasm32")] -use ic_cdk::api::stable::stable64_size; +use ic_cdk::api::stable::stable_size; #[cfg(target_arch = "wasm32")] use ic_cdk::api::stable::WASM_PAGE_SIZE_IN_BYTES; const GIBIBYTE: u64 = 1 << 30; @@ -127,7 +127,7 @@ pub fn encode_metrics(w: &mut MetricsEncoder>) -> std::io::Result<()> { pub fn stable_memory_size_bytes() -> u64 { #[cfg(target_arch = "wasm32")] { - stable64_size() * (WASM_PAGE_SIZE_IN_BYTES as u64) + stable_size() * (WASM_PAGE_SIZE_IN_BYTES) } #[cfg(not(target_arch = "wasm32"))] { @@ -140,7 +140,7 @@ pub fn stable_memory_size_bytes() -> u64 { pub fn wasm_memory_size_bytes() -> u64 { #[cfg(target_arch = "wasm32")] { - (wasm_memory_size(0) as u64) * (WASM_PAGE_SIZE_IN_BYTES as u64) + (wasm_memory_size(0) as u64) * (WASM_PAGE_SIZE_IN_BYTES) } // This can happen only for test builds. When compiled for a canister, the target is // always wasm32. diff --git a/rs/proposals/Cargo.toml b/rs/proposals/Cargo.toml index 2f50e99e03d..67fad60d43a 100644 --- a/rs/proposals/Cargo.toml +++ b/rs/proposals/Cargo.toml @@ -9,9 +9,9 @@ candid = "0.10.9" candid_parser = "0.1.4" hex = "0.4.3" ic_principal = "0.1.1" -serde = "1.0.203" +serde = "1.0.204" serde_bytes = "0.11.15" -serde_json = "1.0.119" +serde_json = "1.0.120" cycles-minting-canister = { workspace = true } dfn_candid = { workspace = true } diff --git a/rs/proposals/src/canisters/nns_governance/api.rs b/rs/proposals/src/canisters/nns_governance/api.rs index 75f01e05ce3..80d7366f9d8 100644 --- a/rs/proposals/src/canisters/nns_governance/api.rs +++ b/rs/proposals/src/canisters/nns_governance/api.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister nns_governance --out api.rs --header did2rs.header --traits Serialize` -//! Candid for canister `nns_governance` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `nns_governance` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(missing_docs)] #![allow(clippy::missing_docs_in_private_items)] @@ -429,9 +429,32 @@ pub struct MakingSnsProposal { pub proposer_id: Option, } #[derive(Serialize, CandidType, Deserialize)] -pub struct MostRecentMonthlyNodeProviderRewards { +pub struct XdrConversionRate { + pub xdr_permyriad_per_icp: Option, + pub timestamp_seconds: Option, +} +#[derive(Serialize, CandidType, Deserialize)] +pub struct MonthlyNodeProviderRewards { + pub minimum_xdr_permyriad_per_icp: Option, + pub registry_version: Option, + pub node_providers: Vec, pub timestamp: u64, pub rewards: Vec, + pub xdr_conversion_rate: Option, + pub maximum_node_provider_rewards_e8s: Option, +} +#[derive(Serialize, CandidType, Deserialize)] +pub struct NeuronSubsetMetrics { + pub total_maturity_e8s_equivalent: Option, + pub maturity_e8s_equivalent_buckets: Vec<(u64, u64)>, + pub voting_power_buckets: Vec<(u64, u64)>, + pub total_staked_e8s: Option, + pub count: Option, + pub total_staked_maturity_e8s_equivalent: Option, + pub staked_maturity_e8s_equivalent_buckets: Vec<(u64, u64)>, + pub staked_e8s_buckets: Vec<(u64, u64)>, + pub total_voting_power: Option, + pub count_buckets: Vec<(u64, u64)>, } #[derive(Serialize, CandidType, Deserialize)] pub struct GovernanceCachedMetrics { @@ -465,6 +488,7 @@ pub struct GovernanceCachedMetrics { pub not_dissolving_neurons_staked_maturity_e8s_equivalent_buckets: Vec<(u64, f64)>, pub dissolving_neurons_count_buckets: Vec<(u64, u64)>, pub dissolving_neurons_e8s_buckets_ect: Vec<(u64, f64)>, + pub non_self_authenticating_controller_neuron_subset_metrics: Option, pub dissolving_neurons_count: u64, pub dissolving_neurons_e8s_buckets: Vec<(u64, f64)>, pub total_staked_maturity_e8s_equivalent_seed: u64, @@ -652,11 +676,6 @@ pub struct ProposalData { pub original_total_community_fund_maturity_e8s_equivalent: Option, } #[derive(Serialize, CandidType, Deserialize)] -pub struct XdrConversionRate { - pub xdr_permyriad_per_icp: Option, - pub timestamp_seconds: Option, -} -#[derive(Serialize, CandidType, Deserialize)] pub enum Command2 { Spawn(NeuronId), Split(Split), @@ -711,7 +730,7 @@ pub struct Neuron { pub struct Governance { pub default_followees: Vec<(i32, Followees)>, pub making_sns_proposal: Option, - pub most_recent_monthly_node_provider_rewards: Option, + pub most_recent_monthly_node_provider_rewards: Option, pub maturity_modulation_last_updated_at_timestamp_seconds: Option, pub wait_for_quiet_threshold_seconds: u64, pub metrics: Option, @@ -1003,7 +1022,7 @@ impl Service { } pub async fn get_most_recent_monthly_node_provider_rewards( &self, - ) -> CallResult<(Option,)> { + ) -> CallResult<(Option,)> { ic_cdk::call(self.0, "get_most_recent_monthly_node_provider_rewards", ()).await } pub async fn get_network_economics_parameters(&self) -> CallResult<(NetworkEconomics,)> { diff --git a/rs/proposals/src/canisters/nns_registry/api.rs b/rs/proposals/src/canisters/nns_registry/api.rs index 774aa4a2b56..51cc1be9f43 100644 --- a/rs/proposals/src/canisters/nns_registry/api.rs +++ b/rs/proposals/src/canisters/nns_registry/api.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister nns_registry --out api.rs --header did2rs.header --traits Serialize` -//! Candid for canister `nns_registry` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `nns_registry` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(missing_docs)] #![allow(clippy::missing_docs_in_private_items)] @@ -258,6 +258,7 @@ pub enum GetNodeOperatorsAndDcsOfNodeProviderResponse { } #[derive(Serialize, CandidType, Deserialize)] pub struct NodeProvidersMonthlyXdrRewards { + pub registry_version: Option, pub rewards: Vec<(String, u64)>, } #[derive(Serialize, CandidType, Deserialize)] diff --git a/rs/proposals/src/canisters/sns_wasm/api.rs b/rs/proposals/src/canisters/sns_wasm/api.rs index 612836670d0..f7b841912fc 100644 --- a/rs/proposals/src/canisters/sns_wasm/api.rs +++ b/rs/proposals/src/canisters/sns_wasm/api.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_wasm --out api.rs --header did2rs.header --traits Serialize` -//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(missing_docs)] #![allow(clippy::missing_docs_in_private_items)] diff --git a/rs/sns_aggregator/Cargo.toml b/rs/sns_aggregator/Cargo.toml index 1ec8b5d010d..19c40b7b224 100644 --- a/rs/sns_aggregator/Cargo.toml +++ b/rs/sns_aggregator/Cargo.toml @@ -15,19 +15,19 @@ dfn_candid = { workspace = true } dfn_core = { workspace = true } # This next candid is 0.9.0_beta code that fixes serde Nat but has other issues. Keep checking until the issues are fixed. #candid = { git = "https://github.com/dfinity/candid" , rev = "42ffed660ded37585c4b9f97e3ce90919e24c518" } -ic-cdk = { version = "0.14.0" } -ic-cdk-macros = { version = "0.14.0" } -ic-cdk-timers = "0.8.0" +ic-cdk = { version = "0.15.0" } +ic-cdk-macros = { version = "0.15.0" } +ic-cdk-timers = "0.9.0" ic-certified-map = { git = "https://github.com/dfinity/cdk-rs", rev = "58791941b72471e09e3d9e733f2a3d4d54e52b5a" } ic-management-canister-types = { workspace = true } ic-nervous-system-common = { workspace = true } lazy_static = "1.5.0" num-traits = "0.2.19" -serde = "1.0.203" +serde = "1.0.204" serde_bytes = "0.11.15" serde_cbor = "0.11.2" serde_derive = "1.0.126" -serde_json = "1.0.119" +serde_json = "1.0.120" sha2 = "0.10.8" [dev-dependencies] diff --git a/rs/sns_aggregator/src/types/ic_sns_governance.rs b/rs/sns_aggregator/src/types/ic_sns_governance.rs index 408e0d1cebd..3bd01bb0dd5 100644 --- a/rs/sns_aggregator/src/types/ic_sns_governance.rs +++ b/rs/sns_aggregator/src/types/ic_sns_governance.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_governance --out ic_sns_governance.rs --header did2rs.header --traits Serialize\,\ Clone\,\ Debug` -//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_governance` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(unused_imports)] #![allow(missing_docs)] diff --git a/rs/sns_aggregator/src/types/ic_sns_ledger.rs b/rs/sns_aggregator/src/types/ic_sns_ledger.rs index 352b614c3a5..39fa35ab37d 100644 --- a/rs/sns_aggregator/src/types/ic_sns_ledger.rs +++ b/rs/sns_aggregator/src/types/ic_sns_ledger.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_ledger --out ic_sns_ledger.rs --header did2rs.header --traits Serialize\,\ Clone\,\ Debug` -//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_ledger` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(unused_imports)] #![allow(missing_docs)] diff --git a/rs/sns_aggregator/src/types/ic_sns_root.rs b/rs/sns_aggregator/src/types/ic_sns_root.rs index f157375b251..67309185542 100644 --- a/rs/sns_aggregator/src/types/ic_sns_root.rs +++ b/rs/sns_aggregator/src/types/ic_sns_root.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_root --out ic_sns_root.rs --header did2rs.header --traits Serialize\,\ Clone\,\ Debug` -//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_root` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(unused_imports)] #![allow(missing_docs)] diff --git a/rs/sns_aggregator/src/types/ic_sns_swap.rs b/rs/sns_aggregator/src/types/ic_sns_swap.rs index 55b0cde5143..7cede08792a 100644 --- a/rs/sns_aggregator/src/types/ic_sns_swap.rs +++ b/rs/sns_aggregator/src/types/ic_sns_swap.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_swap --out ic_sns_swap.rs --header did2rs.header --traits Serialize\,\ Clone\,\ Debug` -//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_swap` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(unused_imports)] #![allow(missing_docs)] diff --git a/rs/sns_aggregator/src/types/ic_sns_wasm.rs b/rs/sns_aggregator/src/types/ic_sns_wasm.rs index 741426ee477..de7211ff7b9 100644 --- a/rs/sns_aggregator/src/types/ic_sns_wasm.rs +++ b/rs/sns_aggregator/src/types/ic_sns_wasm.rs @@ -1,5 +1,5 @@ //! Rust code created from candid by: `scripts/did2rs.sh --canister sns_wasm --out ic_sns_wasm.rs --header did2rs.header --traits Serialize\,\ Clone\,\ Debug` -//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: +//! Candid for canister `sns_wasm` obtained by `scripts/update_ic_commit` from: #![allow(clippy::all)] #![allow(unused_imports)] #![allow(missing_docs)] diff --git a/scripts/convert-id b/scripts/convert-id new file mode 100755 index 00000000000..ffcfb090215 --- /dev/null +++ b/scripts/convert-id @@ -0,0 +1,213 @@ +#!/usr/bin/env bash +set -euo pipefail +SOURCE_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" + +print_help() { + cat <<-EOF + + Convert IDs between different formats. + Formats are: text, hex, blob, and account_identifier. + + USAGE: + $(basename "$0") --input --output [--as_subaccount] [--subaccount_format ] [] + $(basename "$0") --input --output --as_neuron_subaccount] + + FORMAT EXAMPLES: + text: 3vbe6-ysauy-vhiiq-dkhzp-ndzfr-6knc4-45rwt-youy4-dwtze-oqoo3-nae + hex: 40A62A74220351F2F68F258F94D1739D8DA787531C1DA7923A0E76DA02 + blob: \\40\\A6\\2A\\74\\22\\03\\51\\F2\\F6\\8F\\25\\8F\\94\\D1\\73\\9D\\8D\\A7\\87\\53\\1C\\1D\\A7\\92\\3A\\0E\\76\\DA\\02 + account_identifier: e8865cf776c7cd1ffeb4491208f94d601570755bfe410e217497a0e626bef101 + + USAGE EXAMPLES: + $(basename "$0") --input text --output hex 3vbe6-ysauy-vhiiq-dkhzp-ndzfr-6knc4-45rwt-youy4-dwtze-oqoo3-nae + $(basename "$0") --input hex --output blob 40A62A74220351F2F68F258F94D1739D8DA787531C1DA7923A0E76DA02 + $(basename "$0") --input text --output account_identifier --subaccount_format index 3vbe6-ysauy-vhiiq-dkhzp-ndzfr-6knc4-45rwt-youy4-dwtze-oqoo3-nae 1 + EOF +} + +# Source the clap.bash file --------------------------------------------------- +source "$SOURCE_DIR/clap.bash" +# Define options +clap.define short=i long=input desc="The input format" variable=INPUT_FORMAT default="text" +clap.define short=o long=output desc="The output format" variable=OUTPUT_FORMAT default="hex" +clap.define long=as_subaccount desc="To be used as ICP subaccount" variable=AS_SUBACCOUNT nargs=0 +clap.define long=as_neuron_subaccount desc="To be used as neuron subaccount" variable=AS_NEURON_SUBACCOUNT nargs=0 +clap.define long=subaccount_format desc="Formet of the subaccount parameter for account_identifier" variable=SUBACCOUNT_FORMAT default="index" +# Source the output file ---------------------------------------------------------- +source "$(clap.build)" + +function check_format() { + case $1 in + text | hex | blob | account_identifier | icrc1) ;; + *) + echo "Invalid format: $1" >&2 + echo "Valid formats are: text, hex and blob" >&2 + exit 1 + ;; + esac +} + +# Copied from https://internetcomputer.org/docs/current/references/ic-interface-spec/#textual-representation-of-principals +function textual_encode() { + ( + echo "$1" | xxd -r -p | /usr/bin/crc32 /dev/stdin + echo -n "$1" + ) | + xxd -r -p | base32 | tr '[:upper:]' '[:lower:]' | + tr -d = | fold -w5 | paste -sd'-' - +} + +function textual_decode() { + echo -n "$1" | tr -d - | tr '[:lower:]' '[:upper:]' | + fold -w 8 | xargs -n1 printf '%-8s' | tr ' ' = | + base32 -d | xxd -p | tr -d '\n' | cut -b9- | tr '[:lower:]' '[:upper:]' +} + +function hex_to_hex() { + cat +} + +function hex_to_text() { + textual_encode "$(cat)" +} + +function text_to_hex() { + textual_decode "$(cat)" +} + +function hex_to_blob() { + sed -e 's@..@\\&@g' +} + +function blob_to_hex() { + sed -e 's@\\@@g' +} + +function get_subaccount_hex() { + if [[ "$INPUT_FORMAT" = "icrc1" ]]; then + if [[ -n "${SUBACCOUNT_ARG}" ]]; then + echo "The ICRC1 format includes the subaccount so you can't specify a separate subaccount as well." >&2 + exit 1 + fi + subaccount_part=$(echo -n "$ID_ARG" | sed 's@^[^.]*\.@@') + # Some version of bash produce leading spaces instead of zeros. + # tr ' ' '0' is used to replace them with zeros. + printf '%064s' "$subaccount_part" | tr ' ' '0' + return + fi + if [[ "$SUBACCOUNT_FORMAT" = "index" ]]; then + SUBACCOUNT_ARG="${SUBACCOUNT_ARG:-0}" + if ! [[ "$SUBACCOUNT_ARG" =~ ^[0-9]+$ ]]; then + echo "Subaccount index must be a decimal number." >&2 + exit 1 + fi + printf "%064x" "$SUBACCOUNT_ARG" + return + fi + + if ! [[ "$SUBACCOUNT_FORMAT" = "text" ]]; then + "$0" --input "$SUBACCOUNT_FORMAT" --output hex "$SUBACCOUNT_ARG" + return + fi + + "$0" --input "$SUBACCOUNT_FORMAT" --output hex --as_subaccount "$SUBACCOUNT_ARG" +} + +function hex_to_account_identifier() { + hex=$(cat) + subaccount_hex=$(get_subaccount_hex) + + # Logic translated from https://github.com/dfinity/ic/blob/6aceb6a35248ef2735ddd9ca99d8a1c6f4a13908/rs/rosetta-api/icp_ledger/src/account_identifier.rs#L58-L68 + hash_input="0a$(echo -n account-id | xxd -p)${hex}${subaccount_hex}" + hash_output="$(echo -n "$hash_input" | xxd -r -p | openssl dgst -sha224 | awk '{print $2}')" + checksum=$(echo -n "$hash_output" | xxd -r -p | /usr/bin/crc32 /dev/stdin) + echo "${checksum}${hash_output}" +} + +function account_identifier_to_hex() { + echo "Account identifiers can't be reversed." >&2 + exit 1 +} + +function icrc1_to_hex() { + sed 's@-[^-]*$@@' | text_to_hex +} + +# See https://internetcomputer.org/docs/current/references/icrc1-standard#textual-encoding-of-accounts +# The ICRC-1 format is: -. +# where is the lower-case hex representation the +# subaccount with leading zeros removed. +# If the subaccount is only zeros, the ICRC-1 format is just the principal. +function hex_to_icrc1() { + hex=$(cat) + + subaccount_hex="$(get_subaccount_hex)" + + # Remove leading zeros. + subaccount_hex="$(echo -n "$subaccount_hex" | sed -e 's@^0*@@')" + + if [[ -z "$subaccount_hex" ]]; then + echo -n "$hex" | hex_to_text + return + fi + + checksum="$(echo -n "${hex}${subaccount_hex}" | xxd -r -p | /usr/bin/crc32 /dev/stdin | xxd -p -r | base32 | tr -d = | tr '[:upper:]' '[:lower:]')" + + echo "$(echo "$hex" | hex_to_text)-${checksum}.${subaccount_hex}" +} + +function check_hex() { + if ! [[ $1 =~ ^([0-9a-fA-F]{2})+$ ]]; then + echo "Invalid hex: $1" >&2 + exit 1 + fi +} + +function check_blob() { + if ! [[ $1 =~ ^(\\[0-9a-fA-F]{2})+$ ]]; then + echo "Invalid blob: $1" >&2 + exit 1 + fi +} + +function check_text() { + if ! [[ $1 =~ ^[a-z0-9-]+$ ]]; then + echo "Invalid text: $1" >&2 + exit 1 + fi +} + +function check_icrc1() { + if ! [[ $1 =~ ^[a-z0-9-]+\.[0-9a-f]+$ ]]; then + echo "Invalid icrc1: $1" >&2 + exit 1 + fi +} + +function maybe_as_subaccount() { + if [ "${AS_NEURON_SUBACCOUNT:-}" = "true" ]; then + hex=$(cat) + # Logic translated from https://github.com/dfinity/ic/blob/1ec57bd3ac00a716eb6e58ba2f49cf4e9c3e6b03/rs/nervous_system/common/src/ledger.rs#L211-L223 + nonce=$(printf "%016x" "$NONCE_ARG") + hash_input="0c$(echo -n neuron-stake | xxd -p)${hex}${nonce}" + echo -n "$hash_input" | xxd -r -p | openssl dgst -sha256 | awk '{print $2}' + return + fi + if [ "${AS_SUBACCOUNT:-}" != "true" ]; then + cat + return + fi + hex=$(cat) + length=$(($(echo -n "$hex" | wc -c) / 2)) + printf '%02X%-062s' $length "$hex" | tr ' ' '0' +} + +check_format "$INPUT_FORMAT" +check_format "$OUTPUT_FORMAT" +check_"$INPUT_FORMAT" "$1" + +ID_ARG="$1" +SUBACCOUNT_ARG="${2:-}" +NONCE_ARG="${2:-}" + +echo -n "$1" | "${INPUT_FORMAT}_to_hex" | maybe_as_subaccount | "hex_to_${OUTPUT_FORMAT}" diff --git a/scripts/convert-id.test b/scripts/convert-id.test new file mode 100755 index 00000000000..b83cfea957d --- /dev/null +++ b/scripts/convert-id.test @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail +SOURCE_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" + +function expect_convert() { + expected_output="$1" + shift + SCRIPT_UNDER_TEST="$SOURCE_DIR/convert-id" + actual_output=$("$SCRIPT_UNDER_TEST" "$@") + if [ "$expected_output" != "$actual_output" ]; then + printf "Command: %s " "$(basename "$SCRIPT_UNDER_TEST")" + printf " %q" "$@" + echo + echo "Expected: $expected_output" + echo "Actual: $actual_output" + exit 1 + fi +} + +TEXT="w7xx4-wq2fm-6a" +HEX="1A2B3C" +BLOB="\\1A\\2B\\3C" +ACCOUNT_IDENTIFIER="66b308004b77291769646f70ebe4f392741ef6a44c5f7ab83cd2d42b20588d2b" + +expect_convert "$TEXT" --output text --input text "$TEXT" +expect_convert "$TEXT" --output text --input hex "$HEX" +expect_convert "$TEXT" --output text --input blob "$BLOB" +expect_convert "$HEX" --output hex --input text "$TEXT" +expect_convert "$HEX" --output hex --input hex "$HEX" +expect_convert "$HEX" --output hex --input blob "$BLOB" +expect_convert "$BLOB" --output blob --input text "$TEXT" +expect_convert "$BLOB" --output blob --input hex "$HEX" +expect_convert "$BLOB" --output blob --input blob "$BLOB" + +expect_convert "$ACCOUNT_IDENTIFIER" --output account_identifier --input text "$TEXT" + +TEXT2="3vbe6-ysauy-vhiiq-dkhzp-ndzfr-6knc4-45rwt-youy4-dwtze-oqoo3-nae" +HEX2="40A62A74220351F2F68F258F94D1739D8DA787531C1DA7923A0E76DA02" +BLOB2="\\40\\A6\\2A\\74\\22\\03\\51\\F2\\F6\\8F\\25\\8F\\94\\D1\\73\\9D\\8D\\A7\\87\\53\\1C\\1D\\A7\\92\\3A\\0E\\76\\DA\\02" +ACCOUNT_IDENTIFIER2="e8865cf776c7cd1ffeb4491208f94d601570755bfe410e217497a0e626bef101" +ACCOUNT_IDENTIFIER2_INDEX_1="9de8d877dd08d3a376d4dfd30ecc0624353e24b69479a62d79360de08e30a5e8" + +expect_convert "$TEXT2" --output text --input text "$TEXT2" +expect_convert "$TEXT2" --output text --input hex "$HEX2" +expect_convert "$TEXT2" --output text --input blob "$BLOB2" +expect_convert "$HEX2" --output hex --input text "$TEXT2" +expect_convert "$HEX2" --output hex --input hex "$HEX2" +expect_convert "$HEX2" --output hex --input blob "$BLOB2" +expect_convert "$BLOB2" --output blob --input text "$TEXT2" +expect_convert "$BLOB2" --output blob --input hex "$HEX2" +expect_convert "$BLOB2" --output blob --input blob "$BLOB2" + +expect_convert "$ACCOUNT_IDENTIFIER2" --output account_identifier --input text "$TEXT2" +expect_convert "$ACCOUNT_IDENTIFIER2_INDEX_1" --output account_identifier --input text "$TEXT2" 1 + +SUBACCOUNT_IDENTIFIER="0a0b22efafb70f6bc7b93951e7bf40feeb0d3a07b7b2e4a6ae37c4f17793360f" +HEX_AS_SUBACCOUNT="031A2B3C00000000000000000000000000000000000000000000000000000000" +expect_convert "$SUBACCOUNT_IDENTIFIER" --output account_identifier --input text --subaccount_format text "$TEXT2" "$TEXT" +expect_convert "$HEX_AS_SUBACCOUNT" --output hex --as_subaccount --input text "$TEXT" +expect_convert "$SUBACCOUNT_IDENTIFIER" --output account_identifier --input text --subaccount_format hex "$TEXT2" "$HEX_AS_SUBACCOUNT" + +ICRC1_CHECKSUM="3vbe6yq" +ICRC1="$TEXT2-$ICRC1_CHECKSUM.1" +expect_convert "$ICRC1" --output icrc1 --input icrc1 "$ICRC1" +expect_convert "$ICRC1" --output icrc1 --input text "$TEXT2" 1 +expect_convert "$ACCOUNT_IDENTIFIER2_INDEX_1" --output account_identifier --input icrc1 "$ICRC1" + +NONCE="1" +NEURON_SUBACCOUNT="13de0411947c35e473401a686c70366f0ee9407f6805352aa700392edaf17958" +expect_convert "$NEURON_SUBACCOUNT" --output hex --input text --as_neuron_subaccount "$TEXT2" "$NONCE" + +NONCE2="2651852402133984091" +NEURON_SUBACCOUNT2="edd9ba59cb70fe4cff968f282cfba36aef011003d7bdebaa49102e374641f72f" +NEURON_ACCOUNT2="240ddb2ab0fc6d74d9147839a974ca0a7d2408e122180411c204517b09c25010" +GOVERNANCE_CANISTER_ID="rrkah-fqaaa-aaaaa-aaaaq-cai" +expect_convert "$NEURON_SUBACCOUNT2" --output hex --input text --as_neuron_subaccount "$TEXT2" "$NONCE2" +expect_convert "$NEURON_ACCOUNT2" --output account_identifier --input text --subaccount_format hex "$GOVERNANCE_CANISTER_ID" "$NEURON_SUBACCOUNT2" + +echo PASS diff --git a/scripts/nns-dapp/test-cmc-notify b/scripts/nns-dapp/test-cmc-notify new file mode 100755 index 00000000000..1eb096c2afd --- /dev/null +++ b/scripts/nns-dapp/test-cmc-notify @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +set -euo pipefail +SOURCE_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")/.." + +print_help() { + cat <<-EOF + + Creating or topping up a canister is a 2-step process: + 1. Sending ICP to a subaccount of the CMC. + 2. Notifying the CMC about the transaction. + Both steps are done in the frontend, but if the process is interrupted and + the second step is not performed, the ICP could go missing. + So the nns-dapp canister monitors the ledger for such transactions and also + notifies the CMC about them. + + This script tests that fallback notification mechanism of the nns-dapp + canister. + EOF +} + +# Source the clap.bash file --------------------------------------------------- +source "$SOURCE_DIR/clap.bash" +# Define options +clap.define short=i long=identity desc="Identity to use to create proposals" variable=DFX_IDENTITY default="snsdemo8" +# Source the output file ---------------------------------------------------------- +source "$(clap.build)" + +export DFX_IDENTITY + +# Magic numbers defined at https://github.com/dfinity/ic/blob/be47f18b190689a055c7b198030a85e8cc816b65/rs/nns/cmc/src/lib.rs#L229-L230 +CREATE_CANISTER_MEMO="$((16#41455243))" # 1095062083 +TOP_UP_CANISTER_MEMO="$((16#50555054))" # 1347768404 + +ICP_E8S=11000000 + +function convert() { + "$SOURCE_DIR/convert-id" "$@" +} + +function hex_to_blob() { + convert --input hex --output blob "$1" +} + +function get_cmc_account_identifier() { + subaccount="$1" + convert --output account_identifier --subaccount_format text "$CMC_ID" "$subaccount" +} + +function get_user_canisters() { + dfx canister call nns-dapp get_canisters | idl2json | jq -r '.[] | .canister_id' +} + +function get_cycles_balance() { + canister_id="$1" + dfx canister status "$canister_id" | + grep "Balance:" | + sed -e 's@Balance: \(.*\) Cycles@\1@' | + sed -e 's@_@@g' +} + +# Make sure we have an account so nns-dapp tracks our transactions +dfx canister call nns-dapp add_account + +OLD_CANISTERS="$(get_user_canisters)" + +CMC_ID="$(dfx canister id nns-cycles-minting)" + +CYCLES_PER_ICP_E8="$( + dfx canister call nns-cycles-minting get_icp_xdr_conversion_rate | + idl2json | + jq -r '.data.xdr_permyriad_per_icp' +)" + +PRINCIPAL="$(dfx identity get-principal)" + +CREATE_CANISTER_ACCOUNT_IDENTIFIER="$(get_cmc_account_identifier "$PRINCIPAL")" +dfx canister call nns-ledger transfer "( + record { + to = blob \"$(hex_to_blob "$CREATE_CANISTER_ACCOUNT_IDENTIFIER")\"; + fee = record { e8s = 10_000 : nat64 }; + memo = $CREATE_CANISTER_MEMO : nat64; + amount = record { e8s = $ICP_E8S : nat64 }; + } +)" + +# Wait for the nns-dapp canister to see the transaction, to notify the CMC and +# to add the canister to the list of user canisters. +for ((try = 30; try > 0; try--)); do + ALL_CANISTERS="$(get_user_canisters)" + if [[ "$ALL_CANISTERS" != "$OLD_CANISTERS" ]]; then + break + fi + echo "Waiting for canister to be created..." + sleep 1 +done + +NEW_CANISTER="$( + echo "$ALL_CANISTERS $OLD_CANISTERS" | + sed -e 's@ @\n@g' | + sort | + uniq -u | + grep -v '^$' +)" + +if [[ -z "$NEW_CANISTER" ]]; then + echo "No new canister found" + exit 1 +fi + +echo "Created canister ID $NEW_CANISTER" + +EXPECTED_CYCLES="$((ICP_E8S * CYCLES_PER_ICP_E8))" + +ACTUAL_CYCLES="$(get_cycles_balance "$NEW_CANISTER")" + +echo "Cycles balance: $ACTUAL_CYCLES" + +if [[ "$ACTUAL_CYCLES" != "$EXPECTED_CYCLES" ]]; then + echo "Expected $EXPECTED_CYCLES cycles, got $ACTUAL_CYCLES" + exit 1 +fi + +TOP_UP_ACCOUNT_IDENTIFIER="$(get_cmc_account_identifier "$NEW_CANISTER")" +dfx canister call nns-ledger transfer "( + record { + to = blob \"$(hex_to_blob "$TOP_UP_ACCOUNT_IDENTIFIER")\"; + fee = record { e8s = 10_000 : nat64 }; + memo = $TOP_UP_CANISTER_MEMO : nat64; + amount = record { e8s = $ICP_E8S : nat64 }; + } +)" + +EXPECTED_CYCLES="$((2 * ICP_E8S * CYCLES_PER_ICP_E8))" +OLD_CYCLES="$ACTUAL_CYCLES" + +# Wait for the nns-dapp canister to see the transaction, for it to notify the +# CMC and for the CMC to update the canister's cycles balance. +for ((try = 30; try > 0; try--)); do + ACTUAL_CYCLES="$(get_cycles_balance "$NEW_CANISTER")" + if [[ "ACTUAL_CYCLES" != "$OLD_CYCLES" ]]; then + break + fi + echo "Waiting for canister to be topped up..." + sleep 1 +done + +echo "Cycles balance: $ACTUAL_CYCLES" + +if [[ "$ACTUAL_CYCLES" != "$EXPECTED_CYCLES" ]]; then + echo "Expected $EXPECTED_CYCLES cycles, got $ACTUAL_CYCLES" + exit 1 +fi + +echo "✅ PASS" diff --git a/scripts/nns-dapp/test-config-assets/app/arg.did b/scripts/nns-dapp/test-config-assets/app/arg.did index 204540764a8..697c8238ba2 100644 --- a/scripts/nns-dapp/test-config-assets/app/arg.did +++ b/scripts/nns-dapp/test-config-assets/app/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="app" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":false}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":false}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/app/env b/scripts/nns-dapp/test-config-assets/app/env index ce461cb0e6b..ec3195a66ec 100644 --- a/scripts/nns-dapp/test-config-assets/app/env +++ b/scripts/nns-dapp/test-config-assets/app/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=xnjld-hqaaa-aaaal-qb56q-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":false}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":false}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://otgyv-wyaaa-aaaak-qcgba-cai.icp0.io diff --git a/scripts/nns-dapp/test-config-assets/mainnet/arg.did b/scripts/nns-dapp/test-config-assets/mainnet/arg.did index 2f20e97f7da..17e090b48f9 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/arg.did +++ b/scripts/nns-dapp/test-config-assets/mainnet/arg.did @@ -10,7 +10,7 @@ record{ 0="CKUSDC_LEDGER_CANISTER_ID"; 1="xevnm-gaaaa-aaaar-qafnq-cai" }; record{ 0="CYCLES_MINTING_CANISTER_ID"; 1="rkp4c-7iaaa-aaaaa-aaaca-cai" }; record{ 0="DFX_NETWORK"; 1="mainnet" }; - record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":false}" }; + record{ 0="FEATURE_FLAGS"; 1="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":false}" }; record{ 0="FETCH_ROOT_KEY"; 1="false" }; record{ 0="GOVERNANCE_CANISTER_ID"; 1="rrkah-fqaaa-aaaaa-aaaaq-cai" }; record{ 0="HOST"; 1="https://icp-api.io" }; diff --git a/scripts/nns-dapp/test-config-assets/mainnet/env b/scripts/nns-dapp/test-config-assets/mainnet/env index 69c2b56a685..c675a83eedc 100644 --- a/scripts/nns-dapp/test-config-assets/mainnet/env +++ b/scripts/nns-dapp/test-config-assets/mainnet/env @@ -7,7 +7,7 @@ VITE_LEDGER_CANISTER_ID=ryjl3-tyaaa-aaaaa-aaaba-cai VITE_INDEX_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai VITE_OWN_CANISTER_ID=qoctq-giaaa-aaaaa-aaaea-cai VITE_FETCH_ROOT_KEY=false -VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":true,\"ENABLE_PROJECTS_TABLE\":false}" +VITE_FEATURE_FLAGS="{\"ENABLE_CKBTC\":true,\"ENABLE_CKTESTBTC\":false,\"ENABLE_IMPORT_TOKEN\":false,\"ENABLE_PROJECTS_TABLE\":false}" VITE_HOST=https://icp-api.io VITE_IDENTITY_SERVICE_URL=https://identity.internetcomputer.org/ VITE_AGGREGATOR_CANISTER_URL=https://3r4gx-wqaaa-aaaaq-aaaia-cai.icp0.io