From bd43426177cdaf4fc149767184f04f47e61409ef Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 16 Jun 2026 19:49:59 +0200 Subject: [PATCH 01/14] chore: migrate rust/guards to icp-cli Replace dfx.json with icp.yaml using @dfinity/rust@v3.3.0, restructure source into backend/ subdirectory, update management canister usage to ic-cdk-management-canister, add Makefile with guard behavior tests, and add CI workflow. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/guards.yml | 28 + rust/guards/.devcontainer/devcontainer.json | 20 - rust/guards/BUILD.md | 113 -- rust/guards/Cargo.lock | 1965 +++---------------- rust/guards/Cargo.toml | 20 +- rust/guards/Makefile | 44 + rust/guards/README.md | 71 +- rust/guards/backend/Cargo.toml | 15 + rust/guards/{src => backend}/lib.rs | 2 +- rust/guards/dfx.json | 17 - rust/guards/guards.did | 14 - rust/guards/icp.yaml | 7 + rust/guards/tests/tests.rs | 189 -- 13 files changed, 451 insertions(+), 2054 deletions(-) create mode 100644 .github/workflows/guards.yml delete mode 100644 rust/guards/.devcontainer/devcontainer.json delete mode 100644 rust/guards/BUILD.md create mode 100644 rust/guards/Makefile create mode 100644 rust/guards/backend/Cargo.toml rename rust/guards/{src => backend}/lib.rs (98%) delete mode 100644 rust/guards/dfx.json delete mode 100644 rust/guards/guards.did create mode 100644 rust/guards/icp.yaml delete mode 100644 rust/guards/tests/tests.rs diff --git a/.github/workflows/guards.yml b/.github/workflows/guards.yml new file mode 100644 index 0000000000..1a378a10b8 --- /dev/null +++ b/.github/workflows/guards.yml @@ -0,0 +1,28 @@ +name: guards + +on: + push: + branches: [master] + pull_request: + paths: + - rust/guards/** + - .github/workflows/guards.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-guards: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy and test + working-directory: rust/guards + run: | + icp network start -d + icp deploy + make test diff --git a/rust/guards/.devcontainer/devcontainer.json b/rust/guards/.devcontainer/devcontainer.json deleted file mode 100644 index ebb0b8bcc6..0000000000 --- a/rust/guards/.devcontainer/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ICP Dev Environment", - "image": "ghcr.io/dfinity/icp-dev-env-slim:22", - "forwardPorts": [4943, 5173], - "portsAttributes": { - "4943": { - "label": "dfx", - "onAutoForward": "ignore" - }, - "5173": { - "label": "vite", - "onAutoForward": "openBrowser" - } - }, - "customizations": { - "vscode": { - "extensions": ["dfinity-foundation.vscode-motoko"] - } - } -} diff --git a/rust/guards/BUILD.md b/rust/guards/BUILD.md deleted file mode 100644 index 24cfcb7547..0000000000 --- a/rust/guards/BUILD.md +++ /dev/null @@ -1,113 +0,0 @@ -# Continue building locally - -Projects deployed through ICP Ninja are temporary; they will only be live for 20 minutes before they are removed. The command-line tool `dfx` can be used to continue building your ICP Ninja project locally and deploy it to the mainnet. - -To migrate your ICP Ninja project off of the web browser and develop it locally, follow these steps. - -### 1. Install developer tools. - -You can install the developer tools natively or use Dev Containers. - -#### Option 1: Natively install developer tools - -> Installing `dfx` natively is currently only supported on macOS and Linux systems. On Windows, it is recommended to use the Dev Containers option. - -1. Install `dfx` with the following command: - -``` - -sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" - -``` - -> On Apple Silicon (e.g., Apple M1 chip), make sure you have Rosetta installed (`softwareupdate --install-rosetta`). - -2. [Install NodeJS](https://nodejs.org/en/download/package-manager). - -3. For Rust projects, you will also need to: - -- Install [Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo): `curl https://sh.rustup.rs -sSf | sh` - -- Install [candid-extractor](https://crates.io/crates/candid-extractor): `cargo install candid-extractor` - -4. For Motoko projects, you will also need to: - -- Install the Motoko package manager [Mops](https://docs.mops.one/quick-start#2-install-mops-cli): `npm i -g ic-mops` - -Lastly, navigate into your project's directory that you downloaded from ICP Ninja. - -#### Option 2: Dev Containers - -Continue building your projects locally by installing the [Dev Container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code and [Docker](https://docs.docker.com/engine/install/). - -Make sure Docker is running, then navigate into your project's directory that you downloaded from ICP Ninja and start the Dev Container by selecting `Dev-Containers: Reopen in Container` in VS Code's command palette (F1 or Ctrl/Cmd+Shift+P). - -> Note that local development ports (e.g. the ports used by `dfx` or `vite`) are forwarded from the Dev Container to your local machine. In the VS code terminal, use Ctrl/Cmd+Click on the displayed local URLs to open them in your browser. To view the current port mappings, click the "Ports" tab in the VS Code terminal window. - -### 2. Start the local development environment. - -``` -dfx start --background -``` - -### 3. Create a local developer identity. - -To manage your project's canisters, it is recommended that you create a local [developer identity](https://internetcomputer.org/docs/building-apps/getting-started/identities) rather than use the `dfx` default identity that is not stored securely. - -To create a new identity, run the commands: - -``` - -dfx identity new IDENTITY_NAME - -dfx identity use IDENTITY_NAME - -``` - -Replace `IDENTITY_NAME` with your preferred identity name. The first command `dfx start --background` starts the local `dfx` processes, then `dfx identity new` will create a new identity and return your identity's seed phase. Be sure to save this in a safe, secure location. - -The third command `dfx identity use` will tell `dfx` to use your new identity as the active identity. Any canister smart contracts created after running `dfx identity use` will be owned and controlled by the active identity. - -Your identity will have a principal ID associated with it. Principal IDs are used to identify different entities on ICP, such as users and canisters. - -[Learn more about ICP developer identities](https://internetcomputer.org/docs/building-apps/getting-started/identities). - -### 4. Deploy the project locally. - -Deploy your project to your local developer environment with: - -``` -npm install -dfx deploy - -``` - -Your project will be hosted on your local machine. The local canister URLs for your project will be shown in the terminal window as output of the `dfx deploy` command. You can open these URLs in your web browser to view the local instance of your project. - -### 5. Obtain cycles. - -To deploy your project to the mainnet for long-term public accessibility, first you will need [cycles](https://internetcomputer.org/docs/building-apps/getting-started/tokens-and-cycles). Cycles are used to pay for the resources your project uses on the mainnet, such as storage and compute. - -> This cost model is known as ICP's [reverse gas model](https://internetcomputer.org/docs/building-apps/essentials/gas-cost), where developers pay for their project's gas fees rather than users pay for their own gas fees. This model provides an enhanced end user experience since they do not need to hold tokens or sign transactions when using a dapp deployed on ICP. - -> Learn how much a project may cost by using the [pricing calculator](https://internetcomputer.org/docs/building-apps/essentials/cost-estimations-and-examples). - -Cycles can be obtained through [converting ICP tokens into cycles using `dfx`](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/dfx-cycles#dfx-cycles-convert). - -### 6. Deploy to the mainnet. - -Once you have cycles, run the command: - -``` - -dfx deploy --network ic - -``` - -After your project has been deployed to the mainnet, it will continuously require cycles to pay for the resources it uses. You will need to [top up](https://internetcomputer.org/docs/building-apps/canister-management/topping-up) your project's canisters or set up automatic cycles management through a service such as [CycleOps](https://cycleops.dev/). - -> If your project's canisters run out of cycles, they will be removed from the network. - -## Additional examples - -Additional code examples and sample applications can be found in the [DFINITY examples repo](https://github.com/dfinity/examples). diff --git a/rust/guards/Cargo.lock b/rust/guards/Cargo.lock index 75c570f501..0c08189b6c 100644 --- a/rust/guards/Cargo.lock +++ b/rust/guards/Cargo.lock @@ -3,86 +3,43 @@ version = 3 [[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" +name = "anyhow" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "ar_archive_writer" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" dependencies = [ - "memchr", + "object", ] -[[package]] -name = "anyhow" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" - [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" -version = "1.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +name = "backend" +version = "0.1.0" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", + "candid", + "ic-cdk", + "ic-cdk-management-canister", + "scopeguard", + "serde", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "binread" version = "2.2.0" @@ -106,12 +63,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - [[package]] name = "block-buffer" version = "0.10.4" @@ -121,29 +72,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" - [[package]] name = "candid" -version = "0.10.7" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818394610ed32d9e4c81025f97c8580698b69542527efde18514cf9ad1f8f5f0" +checksum = "f8f781afa4a1303e3eab4ada0720a874942bcfa936ce01b816ac6378945c43a9" dependencies = [ "anyhow", "binread", @@ -159,106 +98,104 @@ dependencies = [ "serde", "serde_bytes", "stacker", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" +checksum = "ad6ae8e7944dd0035651bc0e7b3a3e4cb16f5fc43f8ae4fd76b36ff2cd52759f" dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.118", ] [[package]] name = "cc" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "core-foundation" -version = "0.9.4" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ - "core-foundation-sys", - "libc", + "find-msvc-tools", + "shlex", ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] -name = "crossbeam-channel" -version = "0.5.12" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "crossbeam-utils", + "generic-array", + "typenum", ] [[package]] -name = "crossbeam-utils" -version = "0.8.19" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "generic-array", - "typenum", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", ] [[package]] -name = "data-encoding" -version = "2.5.0" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] [[package]] -name = "deranged" -version = "0.3.11" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "digest" @@ -271,1392 +208,440 @@ dependencies = [ ] [[package]] -name = "dyn-clone" -version = "1.0.17" +name = "either" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] -name = "either" -version = "1.11.0" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "equivalent" -version = "1.0.1" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] -name = "fnv" -version = "1.0.7" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "form_urlencoded" -version = "1.2.1" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "futures-channel" -version = "0.3.30" +name = "ic-cdk" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "6a7971f4983db147afbbc4e7f87f60b09fcd60ac707af37ff3d2468dcddbf551" dependencies = [ - "futures-core", - "futures-sink", + "candid", + "ic-cdk-executor", + "ic-cdk-macros", + "ic-error-types", + "ic0", + "pin-project-lite", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" +name = "ic-cdk-executor" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "33716b730ded33690b8a704bff3533fda87d229e58046823647d28816e9bcee7" +dependencies = [ + "ic0", + "slotmap", + "smallvec", +] [[package]] -name = "futures-macro" -version = "0.3.30" +name = "ic-cdk-macros" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "a7c20c002200c720958f321bb78b4d4987fc2c380bf9ef69ed4404730810f45f" dependencies = [ + "candid", + "darling", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.118", ] [[package]] -name = "futures-sink" -version = "0.3.30" +name = "ic-cdk-management-canister" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "92c1319a274caebf0ab70ab826b8905c29e8563498289356b9a59464f2a85c56" +dependencies = [ + "candid", + "ic-cdk", + "ic-management-canister-types", + "serde", + "serde_bytes", + "thiserror 2.0.18", +] [[package]] -name = "futures-task" -version = "0.3.30" +name = "ic-error-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "bbeeb3d91aa179d6496d7293becdacedfc413c825cac79fd54ea1906f003ee55" +dependencies = [ + "serde", + "strum", + "strum_macros", +] [[package]] -name = "futures-util" -version = "0.3.30" +name = "ic-management-canister-types" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "51705516ed4d23f24e8d714a70fe9d7ec17106cfd830d5434a1b29f583ef70ee" dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", + "candid", + "serde", + "serde_bytes", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "ic0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] +checksum = "c77c8932bff1f09502d0d8c079d5a206a06afe523e35e816162cf4d30b5bf80d" [[package]] -name = "getrandom" -version = "0.2.14" +name = "ic_principal" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "1aea751965eaf92990be8c79c64b4f3174ff22dd3604e6696a06a494afbaba2a" dependencies = [ - "cfg-if", - "libc", - "wasi", + "crc32fast", + "data-encoding", + "serde", + "sha2", + "thiserror 1.0.69", ] [[package]] -name = "gimli" -version = "0.28.1" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "h2" -version = "0.4.5" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "hashbrown" -version = "0.14.3" +name = "leb128" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "libc" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "hex" -version = "0.4.3" +name = "memchr" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] -name = "http" -version = "1.1.0" +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "bytes", - "fnv", - "itoa", + "num-integer", + "num-traits", + "serde", ] [[package]] -name = "http-body" -version = "1.0.0" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "bytes", - "http", + "num-traits", ] [[package]] -name = "http-body-util" -version = "0.1.1" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "autocfg", ] [[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "hyper" -version = "1.3.1" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", + "memchr", ] [[package]] -name = "hyper-rustls" -version = "0.26.0" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "hyper-util" -version = "0.1.5" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower", - "tower-service", - "tracing", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "ic-cdk" -version = "0.13.2" +name = "pretty" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8859bc2b863a77750acf199e1fb7e3fc403e1b475855ba13f59cb4e4036d238" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" dependencies = [ - "candid", - "ic-cdk-macros 0.13.2", - "ic0 0.21.1", - "serde", - "serde_bytes", + "arrayvec", + "typed-arena", + "unicode-width", ] [[package]] -name = "ic-cdk" -version = "0.15.1" +name = "proc-macro2" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038ff230bf0fc8630943e3c52e989d248a7c89834ccb65da408fabc5817a475b" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "candid", - "ic-cdk-macros 0.15.0", - "ic0 0.23.0", - "serde", - "serde_bytes", + "unicode-ident", ] [[package]] -name = "ic-cdk-macros" -version = "0.13.2" +name = "psm" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45800053d80a6df839a71aaea5797e723188c0b992618208ca3b941350c7355" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" dependencies = [ - "candid", - "proc-macro2", - "quote", - "serde", - "serde_tokenstream 0.1.7", - "syn 1.0.109", + "ar_archive_writer", + "cc", ] [[package]] -name = "ic-cdk-macros" -version = "0.15.0" +name = "quote" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af44fb4ec3a4b18831c9d3303ca8fa2ace846c4022d50cb8df4122635d3782e" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ - "candid", "proc-macro2", - "quote", - "serde", - "serde_tokenstream 0.2.2", - "syn 2.0.60", ] [[package]] -name = "ic-fun-with-guards" -version = "0.1.0" -dependencies = [ - "assert_matches", - "candid", - "ic-cdk 0.15.1", - "pocket-ic", - "scopeguard", - "serde", -] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "ic0" -version = "0.21.1" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a54b5297861c651551676e8c43df805dad175cc33bc97dbd992edbbb85dcbcdf" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "ic0" -version = "0.23.0" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] [[package]] -name = "ic_principal" -version = "0.1.1" +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ - "crc32fast", - "data-encoding", "serde", - "sha2", - "thiserror", + "serde_core", ] [[package]] -name = "idna" -version = "0.5.0" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "serde_derive", ] [[package]] -name = "indexmap" -version = "2.2.6" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "equivalent", - "hashbrown", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "ipnet" -version = "2.9.0" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] -name = "itoa" -version = "1.0.11" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "js-sys" -version = "0.3.69" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ - "wasm-bindgen", + "version_check", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "smallvec" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] -name = "leb128" -version = "0.2.5" +name = "stacker" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys", +] [[package]] -name = "libc" -version = "0.2.153" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "lock_api" -version = "0.4.12" +name = "strum" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi", - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.5", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pocket-ic" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629f46b7ab8a8d2fee02220ef8e99ae552c7e220117efa1ce0882ff09c8fb038" -dependencies = [ - "base64 0.13.1", - "candid", - "hex", - "ic-cdk 0.13.2", - "reqwest", - "schemars", - "serde", - "serde_bytes", - "serde_json", - "sha2", - "tokio", - "tracing", - "tracing-appender", - "tracing-subscriber", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "pretty" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" -dependencies = [ - "arrayvec", - "typed-arena", - "unicode-width", -] - -[[package]] -name = "proc-macro2" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.3", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "reqwest" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-native-certs", - "rustls-pemfile", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" - -[[package]] -name = "rustls-webpki" -version = "0.102.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "schemars" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.209" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.209" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_json" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_tokenstream" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9" -dependencies = [ - "proc-macro2", - "serde", - "syn 1.0.109", -] - -[[package]] -name = "serde_tokenstream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.60", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stacker" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "winapi", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "thiserror" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", + "strum_macros", ] [[package]] -name = "tokio-macros" -version = "2.4.0" +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 2.0.60", -] - -[[package]] -name = "tokio-rustls" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" -dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", + "rustversion", + "syn 2.0.118", ] [[package]] -name = "tracing-appender" -version = "0.2.3" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "tracing-attributes" -version = "0.1.27" +name = "syn" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "unicode-ident", ] [[package]] -name = "tracing-core" -version = "0.1.32" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "once_cell", - "valuable", + "thiserror-impl 1.0.69", ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "log", - "once_cell", - "tracing-core", + "thiserror-impl 2.0.18", ] [[package]] -name = "tracing-serde" -version = "0.1.3" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "serde", - "tracing-core", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] -name = "tracing-subscriber" -version = "0.3.18" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", + "proc-macro2", + "quote", + "syn 2.0.118", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typed-arena" version = "2.0.2" @@ -1665,361 +650,39 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicase" -version = "2.7.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "valuable" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.60", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "wasm-streams" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.52.5", + "windows-link", ] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/rust/guards/Cargo.toml b/rust/guards/Cargo.toml index 1091895c0d..d1e49e317a 100644 --- a/rust/guards/Cargo.toml +++ b/rust/guards/Cargo.toml @@ -1,17 +1,3 @@ -[package] -name = "ic-fun-with-guards" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -candid = "0.10" -ic-cdk = "0.15" -scopeguard = "1.2.0" -serde = "1.0" - -[dev-dependencies] -assert_matches = "1.5.0" -pocket-ic = "5.0.0" +[workspace] +members = ["backend"] +resolver = "2" diff --git a/rust/guards/Makefile b/rust/guards/Makefile new file mode 100644 index 0000000000..f05e12b3b8 --- /dev/null +++ b/rust/guards/Makefile @@ -0,0 +1,44 @@ +.PHONY: test + +test: + @echo "=== Test 1: set items and verify they are not yet processed ===" + @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + result=$$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 2: guard marks item as processed despite panicking callback (TrueAsyncCall) ===" + @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ + result=$$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'true' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 3: guard fails to mark item as processed when no true async call (FalseAsyncCall) ===" + @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null; \ + result=$$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 4: guard marks first item as processed when processing multiple items (TrueAsyncCall) ===" + @icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' && \ + icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null; \ + result1=$$(icp canister call --query backend is_item_processed '("mint1")') && \ + result2=$$(icp canister call --query backend is_item_processed '("mint2")') && \ + echo "mint1: $$result1, mint2: $$result2" && \ + echo "$$result1" | grep -q 'true' && \ + echo "$$result2" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 5: set_non_processed_items resets state ===" + @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ + icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + result=$$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$$result" && \ + echo "$$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/guards/README.md b/rust/guards/README.md index a38f7087b6..82464a152c 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -4,7 +4,7 @@ This example canister shows some advanced behavior between guards and asynchrono canister developers that are already familiar with [asynchronous code](https://internetcomputer.org/docs/references/async-code) and the security best-practices related -to [inter-canister calls and rollbacks](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). +to [inter-canister calls and rollbacks](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls). ## Guard to maintain invariants @@ -14,75 +14,82 @@ item involves some asynchronous code. A concrete example of such a setting would requests by contacting a ledger canister, where crucially double minting should be avoided. One tricky part in this scenario is that an item can therefore only be marked as processed after the asynchronous code -has completed, meaning in the callback. As mentioned in -the [security best-practices](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#securely-handle-traps-in-callbacks), +has completed, meaning in the callback. As mentioned in the +[security best-practices](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls), it's not always feasible to guarantee that the callback will not trap, which in that case would break the invariant due to the state being rolled back. The standard solution to maintain such an invariant, despite a potential panic in the callback is to use a guard. However, this example canister shows that it's also crucially important that the declaration of the guard happens in another message than the callback, which is the case for true asynchronous code (e.g. inter-canister calls, `raw_rand`, -etc.). It's in particular not enough to `await` a function that's declared to be `async`, since if the future can polled -until completion directly, everything will be executed in a single message. +etc.). It's in particular not enough to `await` a function that's declared to be `async`, since if the future can be +polled until completion directly, everything will be executed in a single message. -## Deploying from ICP Ninja +## Build and deploy from the command line -[![](https://icp.ninja/assets/open.svg)](https://icp.ninja/editor?g=https://github.com/dfinity/examples/tree/master/rust/guards) +### Prerequisites +- Node.js +- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` -## Build and deploy from the command-line +### Install -### 1. [Download and install the IC SDK.](https://internetcomputer.org/docs/building-apps/getting-started/install) - -### 2. Download your project from ICP Ninja using the 'Download files' button on the upper left corner, or [clone the GitHub examples repository.](https://github.com/dfinity/examples/) - -### 3. Navigate into the project's directory. - -### 4. Deploy the project to your local environment: - -``` -dfx start --background --clean && dfx deploy +```bash +git clone https://github.com/dfinity/examples +cd examples/rust/guards ``` -## Automated integration tests - -To run the integration tests under `tests/` install [PocketIC server](https://github.com/dfinity/pocketic) and then run: +### Deploy and test -```shell -cargo build --target wasm32-unknown-unknown --release && cargo test +```bash +icp network start -d +icp deploy +make test +icp network stop ``` -### Test +### Manual test walkthrough -Below tests the behavior in `should_process_single_item_and_mark_it_as_processed` manually. +Below manually tests the behavior demonstrated by the `TrueAsyncCall` variant. Set the item `"mint"` to be processed: ```shell -dfx canister call guards set_non_processed_items 'vec { "mint" }' +icp canister call backend set_non_processed_items '(vec { "mint" })' ``` As a sanity check, ensure that the item is not yet processed: ```shell -dfx canister call guards is_item_processed 'mint' +icp canister call --query backend is_item_processed '("mint")' ``` This should return `(opt false)`. -Process the item by calling the *panicking* callback: +Process the item by calling the *panicking* callback with a true async call (the guard fires in `call_on_cleanup`): ```shell -dfx canister call guards process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' +icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' ``` -Ensure that the guard was executed to ensure that the item is marked as processed despite the previous panic: +Ensure that the guard was executed and the item is marked as processed despite the previous panic: ```shell -dfx canister call guards is_item_processed 'mint' +icp canister call --query backend is_item_processed '("mint")' ``` This should return `(opt true)`. +Now repeat the test with `FalseAsyncCall` — here the future is polled to completion in a single message, so the +panic reverts all state changes and the guard has no effect: + +```shell +icp canister call backend set_non_processed_items '(vec { "mint" })' +icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' +icp canister call --query backend is_item_processed '("mint")' +``` + +This should return `(opt false)` — the item was **not** marked as processed. + ## Security considerations and best practices -If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/building-apps/security/overview) for developing on ICP. This example may not implement all the best practices. +If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on ICP. This example may not implement all the best practices. diff --git a/rust/guards/backend/Cargo.toml b/rust/guards/backend/Cargo.toml new file mode 100644 index 0000000000..e526d4f1a1 --- /dev/null +++ b/rust/guards/backend/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] +path = "lib.rs" + +[dependencies] +candid = "0.10" +ic-cdk = "0.20" +ic-cdk-management-canister = "0.1.1" +scopeguard = "1.2.0" +serde = "1.0" diff --git a/rust/guards/src/lib.rs b/rust/guards/backend/lib.rs similarity index 98% rename from rust/guards/src/lib.rs rename to rust/guards/backend/lib.rs index 5223f15b44..1128d36711 100644 --- a/rust/guards/src/lib.rs +++ b/rust/guards/backend/lib.rs @@ -89,7 +89,7 @@ impl FutureType { pub async fn call(&self) { match self { FutureType::TrueAsyncCall => { - let _ = ic_cdk::api::management_canister::main::raw_rand().await; + let _ = ic_cdk_management_canister::raw_rand().await; } FutureType::FalseAsyncCall => { //NOP diff --git a/rust/guards/dfx.json b/rust/guards/dfx.json deleted file mode 100644 index 17c18a6440..0000000000 --- a/rust/guards/dfx.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "canisters": { - "guards": { - "candid": "guards.did", - "package": "ic-fun-with-guards", - "type": "custom", - "build": ["cargo build --target wasm32-unknown-unknown --release"], - "wasm": "target/wasm32-unknown-unknown/release/ic_fun_with_guards.wasm", - "metadata": [ - { - "name": "candid:service" - } - ] - } - }, - "version": 1 -} diff --git a/rust/guards/guards.did b/rust/guards/guards.did deleted file mode 100644 index e7c7ff62c0..0000000000 --- a/rust/guards/guards.did +++ /dev/null @@ -1,14 +0,0 @@ -type FutureType = variant { - TrueAsyncCall; - FalseAsyncCall; -}; - -service : () -> { - is_item_processed : (text) -> (opt bool) query; - - set_non_processed_items: (vec text) -> (); - - process_single_item_with_panicking_callback: (text, FutureType) -> (); - - process_all_items_with_panicking_callback: (text, FutureType) -> (); -} diff --git a/rust/guards/icp.yaml b/rust/guards/icp.yaml new file mode 100644 index 0000000000..ea796e0460 --- /dev/null +++ b/rust/guards/icp.yaml @@ -0,0 +1,7 @@ +networks: + - name: local + mode: managed +canisters: + - name: backend + recipe: + type: "@dfinity/rust@v3.3.0" diff --git a/rust/guards/tests/tests.rs b/rust/guards/tests/tests.rs deleted file mode 100644 index 7813318448..0000000000 --- a/rust/guards/tests/tests.rs +++ /dev/null @@ -1,189 +0,0 @@ -use assert_matches::assert_matches; -use candid::{CandidType, Decode, Encode, Principal}; -use ic_cdk::api::management_canister::main::CanisterId; -use pocket_ic::common::rest::RawMessageId; -use pocket_ic::{ErrorCode, PocketIc, UserError, WasmResult}; - -pub const CANISTER_WASM: &[u8] = - include_bytes!("../target/wasm32-unknown-unknown/release/ic_fun_with_guards.wasm"); - -#[test] -fn should_process_single_item_and_mark_it_as_processed() { - let canister = CanisterSetup::default(); - canister.set_non_processed_items(&["mint"]); - assert_eq!(canister.is_item_processed("mint"), Some(false)); - - canister.process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall); - - assert_eq!(canister.is_item_processed("mint"), Some(true)); -} - -#[test] -fn should_process_single_item_but_fail_to_mark_it_as_processed() { - let canister = CanisterSetup::default(); - canister.set_non_processed_items(&["mint"]); - assert_eq!(canister.is_item_processed("mint"), Some(false)); - - canister.process_single_item_with_panicking_callback("mint", &FutureType::FalseAsyncCall); - - assert_eq!(canister.is_item_processed("mint"), Some(false)); -} - -#[test] -fn should_process_all_items_and_mark_the_first_one_as_processed() { - let canister = CanisterSetup::default(); - canister.set_non_processed_items(&["mint1", "mint2", "mint3"]); - assert_eq!(canister.is_item_processed("mint1"), Some(false)); - assert_eq!(canister.is_item_processed("mint2"), Some(false)); - assert_eq!(canister.is_item_processed("mint3"), Some(false)); - - canister.process_all_items_with_panicking_callback("mint2", &FutureType::TrueAsyncCall); - - assert_eq!(canister.is_item_processed("mint1"), Some(true)); - assert_eq!(canister.is_item_processed("mint2"), Some(false)); - assert_eq!(canister.is_item_processed("mint3"), Some(false)); -} - -#[test] -fn should_process_all_items_but_fail_to_mark_the_first_one_as_processed() { - let canister = CanisterSetup::default(); - canister.set_non_processed_items(&["mint1", "mint2", "mint3"]); - assert_eq!(canister.is_item_processed("mint1"), Some(false)); - assert_eq!(canister.is_item_processed("mint2"), Some(false)); - assert_eq!(canister.is_item_processed("mint3"), Some(false)); - - canister.process_all_items_with_panicking_callback("mint2", &FutureType::FalseAsyncCall); - - assert_eq!(canister.is_item_processed("mint1"), Some(false)); - assert_eq!(canister.is_item_processed("mint2"), Some(false)); - assert_eq!(canister.is_item_processed("mint3"), Some(false)); -} - -#[test] -fn should_prevent_parallel_processing() { - let canister = CanisterSetup::default(); - canister.set_non_processed_items(&["mint"]); - - let process_item_1 = canister - .submit_process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall) - .unwrap(); - - let process_item_2 = canister - .submit_process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall) - .unwrap(); - - let result_1 = canister.env.await_call(process_item_1); - let result_2 = canister.env.await_call(process_item_2); - - assert_matches!((result_1, result_2), - (Err(UserError { code, description }), _) | (_, Err(UserError { code, description })) - if code == ErrorCode::CanisterCalledTrap && description.contains("ERROR: Item already in processing!")); -} - -pub struct CanisterSetup { - env: PocketIc, - canister_id: CanisterId, -} - -impl CanisterSetup { - pub fn new() -> Self { - let env = PocketIc::new(); - let canister_id = env.create_canister(); - env.add_cycles(canister_id, u128::MAX); - env.install_canister(canister_id, CANISTER_WASM.to_vec(), vec![], None); - Self { env, canister_id } - } - - pub fn is_item_processed(&self, item: &str) -> Option { - match self - .env - .query_call( - self.canister_id, - Principal::anonymous(), - "is_item_processed", - Encode!(&item).unwrap(), - ) - .expect("failed to get value") - { - WasmResult::Reply(bytes) => Decode!(&bytes, Option).unwrap(), - WasmResult::Reject(e) => { - panic!("Failed to get value: {:?}", e); - } - } - } - - pub fn set_non_processed_items(&self, values: &[&str]) { - let values = values.iter().map(|s| s.to_string()).collect::>(); - let result = self - .env - .update_call( - self.canister_id, - Principal::anonymous(), - "set_non_processed_items", - Encode!(&values).unwrap(), - ) - .expect("failed to set non-processed items"); - assert_matches!(result, WasmResult::Reply(_)); - } - - pub fn process_single_item_with_panicking_callback( - &self, - item: &str, - future_type: &FutureType, - ) { - let res = self - .env - .update_call( - self.canister_id, - Principal::anonymous(), - "process_single_item_with_panicking_callback", - Encode!(&item, &future_type).unwrap(), - ) - .expect_err("process_single_item_with_panicking_callback should panic"); - assert_eq!(res.code, ErrorCode::CanisterCalledTrap); - assert!(res.description.contains("panicking callback!")); - } - - pub fn submit_process_single_item_with_panicking_callback( - &self, - item: &str, - future_type: &FutureType, - ) -> Result { - self.env.submit_call( - self.canister_id, - Principal::anonymous(), - "process_single_item_with_panicking_callback", - Encode!(&item, &future_type).unwrap(), - ) - } - - pub fn process_all_items_with_panicking_callback( - &self, - panicking_item: &str, - future_type: &FutureType, - ) { - let res = self - .env - .update_call( - self.canister_id, - Principal::anonymous(), - "process_all_items_with_panicking_callback", - Encode!(&panicking_item, &future_type).unwrap(), - ) - .expect_err("process_all_items_with_panicking_callback should panic"); - assert_eq!(res.code, ErrorCode::CanisterCalledTrap); - assert!(res.description.contains("panicking callback!")); - } -} - -impl Default for CanisterSetup { - fn default() -> Self { - Self::new() - } -} - -#[derive(CandidType, Debug, PartialEq, Eq)] -pub enum FutureType { - TrueAsyncCall, - FalseAsyncCall, -} From e04382558c417444001e36fbf4c78324dba31c9f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 17 Jun 2026 19:00:05 +0200 Subject: [PATCH 02/14] chore(rust/guards): test.sh over Makefile, bump CI image to 1.0.1 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/guards.yml | 4 ++-- rust/guards/Makefile | 44 ------------------------------------ rust/guards/README.md | 2 +- rust/guards/test.sh | 44 ++++++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 47 deletions(-) delete mode 100644 rust/guards/Makefile create mode 100755 rust/guards/test.sh diff --git a/.github/workflows/guards.yml b/.github/workflows/guards.yml index 1a378a10b8..4868813620 100644 --- a/.github/workflows/guards.yml +++ b/.github/workflows/guards.yml @@ -15,7 +15,7 @@ concurrency: jobs: rust-guards: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -25,4 +25,4 @@ jobs: run: | icp network start -d icp deploy - make test + bash test.sh diff --git a/rust/guards/Makefile b/rust/guards/Makefile deleted file mode 100644 index f05e12b3b8..0000000000 --- a/rust/guards/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -.PHONY: test - -test: - @echo "=== Test 1: set items and verify they are not yet processed ===" - @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - result=$$(icp canister call --query backend is_item_processed '("mint")') && \ - echo "$$result" && \ - echo "$$result" | grep -q 'false' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 2: guard marks item as processed despite panicking callback (TrueAsyncCall) ===" - @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ - result=$$(icp canister call --query backend is_item_processed '("mint")') && \ - echo "$$result" && \ - echo "$$result" | grep -q 'true' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 3: guard fails to mark item as processed when no true async call (FalseAsyncCall) ===" - @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null; \ - result=$$(icp canister call --query backend is_item_processed '("mint")') && \ - echo "$$result" && \ - echo "$$result" | grep -q 'false' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 4: guard marks first item as processed when processing multiple items (TrueAsyncCall) ===" - @icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' && \ - icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null; \ - result1=$$(icp canister call --query backend is_item_processed '("mint1")') && \ - result2=$$(icp canister call --query backend is_item_processed '("mint2")') && \ - echo "mint1: $$result1, mint2: $$result2" && \ - echo "$$result1" | grep -q 'true' && \ - echo "$$result2" | grep -q 'false' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 5: set_non_processed_items resets state ===" - @icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ - icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - result=$$(icp canister call --query backend is_item_processed '("mint")') && \ - echo "$$result" && \ - echo "$$result" | grep -q 'false' && \ - echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/guards/README.md b/rust/guards/README.md index 82464a152c..22a3645fa5 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -43,7 +43,7 @@ cd examples/rust/guards ```bash icp network start -d icp deploy -make test +bash test.sh icp network stop ``` diff --git a/rust/guards/test.sh b/rust/guards/test.sh new file mode 100755 index 0000000000..93f0473928 --- /dev/null +++ b/rust/guards/test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -e + +echo "=== Test 1: set items and verify they are not yet processed ===" +icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + result=$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$result" && \ + echo "$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 2: guard marks item as processed despite panicking callback (TrueAsyncCall) ===" +icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ + result=$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$result" && \ + echo "$result" | grep -q 'true' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 3: guard fails to mark item as processed when no true async call (FalseAsyncCall) ===" +icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null; \ + result=$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$result" && \ + echo "$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 4: guard marks first item as processed when processing multiple items (TrueAsyncCall) ===" +icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' && \ + icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null; \ + result1=$(icp canister call --query backend is_item_processed '("mint1")') && \ + result2=$(icp canister call --query backend is_item_processed '("mint2")') && \ + echo "mint1: $result1, mint2: $result2" && \ + echo "$result1" | grep -q 'true' && \ + echo "$result2" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 5: set_non_processed_items resets state ===" +icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ + icp canister call backend set_non_processed_items '(vec { "mint" })' && \ + result=$(icp canister call --query backend is_item_processed '("mint")') && \ + echo "$result" && \ + echo "$result" | grep -q 'false' && \ + echo "PASS" || (echo "FAIL" && exit 1) From d2a60a515b980b533551aebb619bb82031b76a7b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:26:29 +0200 Subject: [PATCH 03/14] fix(rust/guards): drop pinned channel 1.78 from rust-toolchain.toml Transitive dependencies pulled in by ic-cdk 0.20 require edition2024 support (stabilized in Rust 1.85). Removing the 1.78 pin lets rustup use the current stable toolchain. Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/rust-toolchain.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/guards/rust-toolchain.toml b/rust/guards/rust-toolchain.toml index 3736fc7612..990104f055 100644 --- a/rust/guards/rust-toolchain.toml +++ b/rust/guards/rust-toolchain.toml @@ -1,4 +1,2 @@ [toolchain] -channel = "1.78" targets = ["wasm32-unknown-unknown"] -components = ["rustfmt", "clippy"] From 5b15db124f103a71b6c4d412964baafcdcb92b06 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:34:42 +0200 Subject: [PATCH 04/14] fix(rust/guards): add ic_cdk::export_candid!() for candid-extractor Required for the icp-cli recipe to extract the Candid interface from the compiled WASM via get_candid_pointer. Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/backend/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/guards/backend/lib.rs b/rust/guards/backend/lib.rs index 1128d36711..1c686426b5 100644 --- a/rust/guards/backend/lib.rs +++ b/rust/guards/backend/lib.rs @@ -162,3 +162,5 @@ mod parallel_guard { } } } + +ic_cdk::export_candid!(); From c135b9a37d3190677e29b9a054cdbf87d9a5ea5b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:45:27 +0200 Subject: [PATCH 05/14] fix(rust/guards): fix set -e interaction with expected-panicking canister calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The panicking calls return a non-zero exit code (canister rejection). In the old script, 'A && B; C' meant if B failed, set -e exited before C ran — so the assertion after the panic was never checked. Fix: split setup and panicking call onto separate lines, add '|| true' to the panicking call to explicitly allow failure. The setup call still uses '&&' so it fails loudly if the state reset itself fails. Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/test.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/rust/guards/test.sh b/rust/guards/test.sh index 93f0473928..b8b67f071c 100755 --- a/rust/guards/test.sh +++ b/rust/guards/test.sh @@ -9,25 +9,29 @@ icp canister call backend set_non_processed_items '(vec { "mint" })' && \ echo "PASS" || (echo "FAIL" && exit 1) echo "=== Test 2: guard marks item as processed despite panicking callback (TrueAsyncCall) ===" -icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ - result=$(icp canister call --query backend is_item_processed '("mint")') && \ +# The panicking call is expected to return a rejection — ic-cdk uses call_on_cleanup to +# drop locals after the panic, committing the guard's state change before the rollback. +icp canister call backend set_non_processed_items '(vec { "mint" })' +icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null || true +result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ echo "$result" | grep -q 'true' && \ echo "PASS" || (echo "FAIL" && exit 1) echo "=== Test 3: guard fails to mark item as processed when no true async call (FalseAsyncCall) ===" -icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null; \ - result=$(icp canister call --query backend is_item_processed '("mint")') && \ +# Without a true async await boundary, the entire function runs in one message. +# The panic rolls back all state changes including the guard's drop. +icp canister call backend set_non_processed_items '(vec { "mint" })' +icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null || true +result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ echo "$result" | grep -q 'false' && \ echo "PASS" || (echo "FAIL" && exit 1) echo "=== Test 4: guard marks first item as processed when processing multiple items (TrueAsyncCall) ===" -icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' && \ - icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null; \ - result1=$(icp canister call --query backend is_item_processed '("mint1")') && \ +icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' +icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null || true +result1=$(icp canister call --query backend is_item_processed '("mint1")') && \ result2=$(icp canister call --query backend is_item_processed '("mint2")') && \ echo "mint1: $result1, mint2: $result2" && \ echo "$result1" | grep -q 'true' && \ @@ -35,9 +39,9 @@ icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "min echo "PASS" || (echo "FAIL" && exit 1) echo "=== Test 5: set_non_processed_items resets state ===" +icp canister call backend set_non_processed_items '(vec { "mint" })' +icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null || true icp canister call backend set_non_processed_items '(vec { "mint" })' && \ - icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null; \ - icp canister call backend set_non_processed_items '(vec { "mint" })' && \ result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ echo "$result" | grep -q 'false' && \ From b317665d51a236481a0e2bb38682ea7c10c982f4 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:58:49 +0200 Subject: [PATCH 06/14] fix(rust/guards): restore doc anchor links, drop redundant networks block - Restore #inter-canister-calls-and-rollbacks and #securely-handle-traps-in-callbacks fragment identifiers that were dropped during migration - Remove redundant networks block from icp.yaml (managed mode is default) Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/README.md | 4 ++-- rust/guards/icp.yaml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/rust/guards/README.md b/rust/guards/README.md index 22a3645fa5..36a472eb45 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -4,7 +4,7 @@ This example canister shows some advanced behavior between guards and asynchrono canister developers that are already familiar with [asynchronous code](https://internetcomputer.org/docs/references/async-code) and the security best-practices related -to [inter-canister calls and rollbacks](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls). +to [inter-canister calls and rollbacks](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). ## Guard to maintain invariants @@ -15,7 +15,7 @@ requests by contacting a ledger canister, where crucially double minting should One tricky part in this scenario is that an item can therefore only be marked as processed after the asynchronous code has completed, meaning in the callback. As mentioned in the -[security best-practices](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls), +[security best-practices](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#securely-handle-traps-in-callbacks), it's not always feasible to guarantee that the callback will not trap, which in that case would break the invariant due to the state being rolled back. diff --git a/rust/guards/icp.yaml b/rust/guards/icp.yaml index ea796e0460..b7c87727a0 100644 --- a/rust/guards/icp.yaml +++ b/rust/guards/icp.yaml @@ -1,6 +1,3 @@ -networks: - - name: local - mode: managed canisters: - name: backend recipe: From 2eedadd1df530298cc482b7d22ac9293bf86072e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:59:44 +0200 Subject: [PATCH 07/14] fix(rust/guards): use docs.internetcomputer.org link format consistently Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/guards/README.md b/rust/guards/README.md index 36a472eb45..f208a98bdd 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -2,9 +2,9 @@ This example canister shows some advanced behavior between guards and asynchronous code. This example is meant for Rust canister developers that are already familiar -with [asynchronous code](https://internetcomputer.org/docs/references/async-code) +with [asynchronous code](https://docs.internetcomputer.org/references/async-code) and the security best-practices related -to [inter-canister calls and rollbacks](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). +to [inter-canister calls and rollbacks](https://docs.internetcomputer.org/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). ## Guard to maintain invariants @@ -15,7 +15,7 @@ requests by contacting a ledger canister, where crucially double minting should One tricky part in this scenario is that an item can therefore only be marked as processed after the asynchronous code has completed, meaning in the callback. As mentioned in the -[security best-practices](https://internetcomputer.org/docs/building-apps/security/inter-canister-calls#securely-handle-traps-in-callbacks), +[security best-practices](https://docs.internetcomputer.org/building-apps/security/inter-canister-calls#securely-handle-traps-in-callbacks), it's not always feasible to guarantee that the callback will not trap, which in that case would break the invariant due to the state being rolled back. From 5a76db2a13e7c92a23458b613fecfd983763417d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 08:25:36 +0200 Subject: [PATCH 08/14] fix(rust/guards): update async-code link to inter-canister-calls The old URL now redirects to the inter-canister calls page. Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/guards/README.md b/rust/guards/README.md index f208a98bdd..5b3cf46ba6 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -2,7 +2,7 @@ This example canister shows some advanced behavior between guards and asynchronous code. This example is meant for Rust canister developers that are already familiar -with [asynchronous code](https://docs.internetcomputer.org/references/async-code) +with [inter-canister calls](https://docs.internetcomputer.org/guides/canister-calls/inter-canister-calls) and the security best-practices related to [inter-canister calls and rollbacks](https://docs.internetcomputer.org/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). From 41cb6ceca7934abf781d968515f605c226b147ed Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 08:27:14 +0200 Subject: [PATCH 09/14] fix(rust/guards): shorten intro sentence and consolidate security link Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/guards/README.md b/rust/guards/README.md index 5b3cf46ba6..46340b3618 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -3,8 +3,7 @@ This example canister shows some advanced behavior between guards and asynchronous code. This example is meant for Rust canister developers that are already familiar with [inter-canister calls](https://docs.internetcomputer.org/guides/canister-calls/inter-canister-calls) -and the security best-practices related -to [inter-canister calls and rollbacks](https://docs.internetcomputer.org/building-apps/security/inter-canister-calls#inter-canister-calls-and-rollbacks). +and the [security best practices](https://docs.internetcomputer.org/guides/security/inter-canister-calls/#inter-canister-calls-and-rollbacks) related to it. ## Guard to maintain invariants From 0edceeb5447a0c08182279434ce739fe226e543f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 09:10:54 +0200 Subject: [PATCH 10/14] feat(rust/guards): restore PocketIC integration tests, fix docs links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore backend/tests/tests.rs with pocket-ic 14.0.0 API (updated from original v5 — WasmResult/UserError replaced by direct Vec/RejectResponse) - Covers all original scenarios including parallel processing prevention (submit_call/await_call for concurrent calls — not expressible in bash) - Fix remaining building-apps/ doc links to guides/security/inter-canister-calls - README explains both test approaches and how to run PocketIC tests locally - CI downloads pocket-ic server via gh release download (authenticated, no rate limit risk) and runs cargo test as a separate step Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/guards.yml | 17 ++- rust/guards/README.md | 29 ++++- rust/guards/backend/Cargo.toml | 3 + rust/guards/backend/tests/tests.rs | 200 +++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 rust/guards/backend/tests/tests.rs diff --git a/.github/workflows/guards.yml b/.github/workflows/guards.yml index 4868813620..9b6943be6f 100644 --- a/.github/workflows/guards.yml +++ b/.github/workflows/guards.yml @@ -20,9 +20,24 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test + - name: Deploy and test (icp-cli) working-directory: rust/guards run: | icp network start -d icp deploy bash test.sh + - name: Download PocketIC server + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download 14.0.0 \ + --repo dfinity/pocketic \ + --pattern 'pocket-ic-x86_64-linux.gz' \ + --output - | gunzip > /usr/local/bin/pocket-ic-server + chmod +x /usr/local/bin/pocket-ic-server + echo "POCKET_IC_BIN=/usr/local/bin/pocket-ic-server" >> $GITHUB_ENV + - name: Build WASM and run PocketIC tests + working-directory: rust/guards + run: | + cargo build --package backend --target wasm32-unknown-unknown --release + cargo test --package backend diff --git a/rust/guards/README.md b/rust/guards/README.md index 46340b3618..0251763229 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -14,7 +14,7 @@ requests by contacting a ledger canister, where crucially double minting should One tricky part in this scenario is that an item can therefore only be marked as processed after the asynchronous code has completed, meaning in the callback. As mentioned in the -[security best-practices](https://docs.internetcomputer.org/building-apps/security/inter-canister-calls#securely-handle-traps-in-callbacks), +[security best practices](https://docs.internetcomputer.org/guides/security/inter-canister-calls/#securely-handle-traps-in-callbacks), it's not always feasible to guarantee that the callback will not trap, which in that case would break the invariant due to the state being rolled back. @@ -29,6 +29,7 @@ polled until completion directly, everything will be executed in a single messag ### Prerequisites - Node.js - icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Rust toolchain with `wasm32-unknown-unknown` target (only for PocketIC tests): [rustup.rs](https://rustup.rs) ### Install @@ -37,7 +38,7 @@ git clone https://github.com/dfinity/examples cd examples/rust/guards ``` -### Deploy and test +### Deploy and test with icp-cli ```bash icp network start -d @@ -46,6 +47,30 @@ bash test.sh icp network stop ``` +`bash test.sh` exercises the guard behavior via `icp canister call` — verifying that `TrueAsyncCall` marks items as processed and `FalseAsyncCall` does not. + +### Run PocketIC integration tests + +The `backend/tests/` directory contains Rust integration tests using [PocketIC](https://docs.internetcomputer.org/guides/testing/pocket-ic). These cover the same guard/async scenarios as `test.sh` and additionally test **parallel processing prevention** — submitting two concurrent calls for the same item and asserting that one is rejected with "Item already in processing!". This concurrent scenario cannot be reliably expressed in a bash script. + +First, install the [PocketIC server](https://github.com/dfinity/pocketic/releases): + +```bash +# macOS +curl -sL https://github.com/dfinity/pocketic/releases/download/14.0.0/pocket-ic-x86_64-darwin.gz | gunzip > pocket-ic-server +# Linux +curl -sL https://github.com/dfinity/pocketic/releases/download/14.0.0/pocket-ic-x86_64-linux.gz | gunzip > pocket-ic-server +chmod +x pocket-ic-server +export POCKET_IC_BIN=$(pwd)/pocket-ic-server +``` + +Then build the canister WASM and run the tests: + +```bash +cargo build --package backend --target wasm32-unknown-unknown --release +cargo test --package backend +``` + ### Manual test walkthrough Below manually tests the behavior demonstrated by the `TrueAsyncCall` variant. diff --git a/rust/guards/backend/Cargo.toml b/rust/guards/backend/Cargo.toml index e526d4f1a1..928c12b399 100644 --- a/rust/guards/backend/Cargo.toml +++ b/rust/guards/backend/Cargo.toml @@ -13,3 +13,6 @@ ic-cdk = "0.20" ic-cdk-management-canister = "0.1.1" scopeguard = "1.2.0" serde = "1.0" + +[dev-dependencies] +pocket-ic = "14.0.0" diff --git a/rust/guards/backend/tests/tests.rs b/rust/guards/backend/tests/tests.rs new file mode 100644 index 0000000000..23a9ade021 --- /dev/null +++ b/rust/guards/backend/tests/tests.rs @@ -0,0 +1,200 @@ +use candid::{CandidType, Decode, Encode, Principal}; +use pocket_ic::common::rest::RawMessageId; +use pocket_ic::{PocketIc, RejectResponse}; + +/// Path to the compiled canister WASM (relative to this file: backend/tests/ -> workspace root -> target/). +pub const CANISTER_WASM: &[u8] = + include_bytes!("../../target/wasm32-unknown-unknown/release/backend.wasm"); + +#[derive(CandidType, Debug, PartialEq, Eq)] +pub enum FutureType { + TrueAsyncCall, + FalseAsyncCall, +} + +pub struct CanisterSetup { + pub env: PocketIc, + canister_id: Principal, +} + +impl Default for CanisterSetup { + fn default() -> Self { + Self::new() + } +} + +impl CanisterSetup { + pub fn new() -> Self { + let env = PocketIc::new(); + let canister_id = env.create_canister(); + env.add_cycles(canister_id, u128::MAX); + env.install_canister(canister_id, CANISTER_WASM.to_vec(), vec![], None); + Self { env, canister_id } + } + + pub fn is_item_processed(&self, item: &str) -> Option { + let bytes = self + .env + .query_call( + self.canister_id, + Principal::anonymous(), + "is_item_processed", + Encode!(&item).unwrap(), + ) + .expect("failed to query is_item_processed"); + Decode!(&bytes, Option).unwrap() + } + + pub fn set_non_processed_items(&self, values: &[&str]) { + let values: Vec = values.iter().map(|s| s.to_string()).collect(); + self.env + .update_call( + self.canister_id, + Principal::anonymous(), + "set_non_processed_items", + Encode!(&values).unwrap(), + ) + .expect("failed to set non-processed items"); + } + + pub fn process_single_item_with_panicking_callback( + &self, + item: &str, + future_type: &FutureType, + ) { + let err = self + .env + .update_call( + self.canister_id, + Principal::anonymous(), + "process_single_item_with_panicking_callback", + Encode!(&item, future_type).unwrap(), + ) + .expect_err("expected canister to trap with 'panicking callback!'"); + assert!( + err.reject_message.contains("panicking callback!"), + "unexpected trap message: {}", + err.reject_message + ); + } + + pub fn submit_process_single_item_with_panicking_callback( + &self, + item: &str, + future_type: &FutureType, + ) -> Result { + self.env.submit_call( + self.canister_id, + Principal::anonymous(), + "process_single_item_with_panicking_callback", + Encode!(&item, future_type).unwrap(), + ) + } + + pub fn process_all_items_with_panicking_callback( + &self, + panicking_item: &str, + future_type: &FutureType, + ) { + let err = self + .env + .update_call( + self.canister_id, + Principal::anonymous(), + "process_all_items_with_panicking_callback", + Encode!(&panicking_item, future_type).unwrap(), + ) + .expect_err("expected canister to trap with 'panicking callback!'"); + assert!( + err.reject_message.contains("panicking callback!"), + "unexpected trap message: {}", + err.reject_message + ); + } +} + +/// The guard fires via `call_on_cleanup` after a true async call crosses a message boundary: +/// the item is marked as processed even though the callback panicked. +#[test] +fn should_process_single_item_and_mark_it_as_processed() { + let canister = CanisterSetup::default(); + canister.set_non_processed_items(&["mint"]); + assert_eq!(canister.is_item_processed("mint"), Some(false)); + + canister.process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall); + + assert_eq!(canister.is_item_processed("mint"), Some(true)); +} + +/// Without a true async boundary the entire function runs in one message, so a panic +/// rolls back all state changes — the guard has no effect. +#[test] +fn should_process_single_item_but_fail_to_mark_it_as_processed() { + let canister = CanisterSetup::default(); + canister.set_non_processed_items(&["mint"]); + assert_eq!(canister.is_item_processed("mint"), Some(false)); + + canister.process_single_item_with_panicking_callback("mint", &FutureType::FalseAsyncCall); + + assert_eq!(canister.is_item_processed("mint"), Some(false)); +} + +/// When processing multiple items, the first item's guard fires correctly; the panicking +/// item itself is not marked as processed. +#[test] +fn should_process_all_items_and_mark_the_first_one_as_processed() { + let canister = CanisterSetup::default(); + canister.set_non_processed_items(&["mint1", "mint2", "mint3"]); + + canister.process_all_items_with_panicking_callback("mint2", &FutureType::TrueAsyncCall); + + assert_eq!(canister.is_item_processed("mint1"), Some(true)); + assert_eq!(canister.is_item_processed("mint2"), Some(false)); + assert_eq!(canister.is_item_processed("mint3"), Some(false)); +} + +/// Without a true async boundary, no item is marked as processed even across multiple items. +#[test] +fn should_process_all_items_but_fail_to_mark_the_first_one_as_processed() { + let canister = CanisterSetup::default(); + canister.set_non_processed_items(&["mint1", "mint2", "mint3"]); + + canister.process_all_items_with_panicking_callback("mint2", &FutureType::FalseAsyncCall); + + assert_eq!(canister.is_item_processed("mint1"), Some(false)); + assert_eq!(canister.is_item_processed("mint2"), Some(false)); + assert_eq!(canister.is_item_processed("mint3"), Some(false)); +} + +/// Submitting two calls for the same item concurrently: one must be rejected with +/// "Item already in processing!" because the in-processing guard prevents it. +/// This test can only be expressed using PocketIC's `submit_call`/`await_call` API, +/// which allows sending calls without waiting for prior ones to complete. +#[test] +fn should_prevent_parallel_processing() { + let canister = CanisterSetup::default(); + canister.set_non_processed_items(&["mint"]); + + let msg_1 = canister + .submit_process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall) + .expect("failed to submit first call"); + + let msg_2 = canister + .submit_process_single_item_with_panicking_callback("mint", &FutureType::TrueAsyncCall) + .expect("failed to submit second call"); + + let result_1 = canister.env.await_call(msg_1); + let result_2 = canister.env.await_call(msg_2); + + // Exactly one call must fail with "Item already in processing!"; + // the other fails with "panicking callback!" after the guard fires. + let has_parallel_rejection = [&result_1, &result_2].iter().any(|r| { + matches!(r, Err(e) if e.reject_message.contains("ERROR: Item already in processing!")) + }); + assert!( + has_parallel_rejection, + "expected one call to be rejected with 'Item already in processing!', got: {:?} / {:?}", + result_1, + result_2 + ); +} From 2f7f9e4ec410704f128a115c4ca7ad14fe7abf6f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 09:13:02 +0200 Subject: [PATCH 11/14] improve(rust/guards): assert exact trap message in test.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture stderr from expected-panicking calls and verify it contains 'panicking callback!' — same check the PocketIC tests perform. Extracted into assert_traps() helper to avoid repetition. Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/test.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rust/guards/test.sh b/rust/guards/test.sh index b8b67f071c..2dce581a6e 100755 --- a/rust/guards/test.sh +++ b/rust/guards/test.sh @@ -1,6 +1,15 @@ #!/usr/bin/env bash set -e +# Runs a canister call that is expected to trap and verifies the trap message. +# Usage: assert_traps +assert_traps() { + local expected="$1"; shift + trap_output=$(icp canister call "$@" 2>&1 >/dev/null || true) + echo "$trap_output" | grep -q "$expected" && echo "(trap: $expected) PASS" || \ + (echo "FAIL: expected trap message '$expected', got: $trap_output" && exit 1) +} + echo "=== Test 1: set items and verify they are not yet processed ===" icp canister call backend set_non_processed_items '(vec { "mint" })' && \ result=$(icp canister call --query backend is_item_processed '("mint")') && \ @@ -12,7 +21,7 @@ echo "=== Test 2: guard marks item as processed despite panicking callback (True # The panicking call is expected to return a rejection — ic-cdk uses call_on_cleanup to # drop locals after the panic, committing the guard's state change before the rollback. icp canister call backend set_non_processed_items '(vec { "mint" })' -icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null || true +assert_traps "panicking callback!" backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ echo "$result" | grep -q 'true' && \ @@ -22,7 +31,7 @@ echo "=== Test 3: guard fails to mark item as processed when no true async call # Without a true async await boundary, the entire function runs in one message. # The panic rolls back all state changes including the guard's drop. icp canister call backend set_non_processed_items '(vec { "mint" })' -icp canister call backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' 2>/dev/null || true +assert_traps "panicking callback!" backend process_single_item_with_panicking_callback '("mint", variant { FalseAsyncCall })' result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ echo "$result" | grep -q 'false' && \ @@ -30,7 +39,7 @@ result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "=== Test 4: guard marks first item as processed when processing multiple items (TrueAsyncCall) ===" icp canister call backend set_non_processed_items '(vec { "mint1"; "mint2"; "mint3" })' -icp canister call backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' 2>/dev/null || true +assert_traps "panicking callback!" backend process_all_items_with_panicking_callback '("mint2", variant { TrueAsyncCall })' result1=$(icp canister call --query backend is_item_processed '("mint1")') && \ result2=$(icp canister call --query backend is_item_processed '("mint2")') && \ echo "mint1: $result1, mint2: $result2" && \ @@ -40,7 +49,7 @@ result1=$(icp canister call --query backend is_item_processed '("mint1")') && \ echo "=== Test 5: set_non_processed_items resets state ===" icp canister call backend set_non_processed_items '(vec { "mint" })' -icp canister call backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' 2>/dev/null || true +assert_traps "panicking callback!" backend process_single_item_with_panicking_callback '("mint", variant { TrueAsyncCall })' icp canister call backend set_non_processed_items '(vec { "mint" })' && \ result=$(icp canister call --query backend is_item_processed '("mint")') && \ echo "$result" && \ From da8f523fee6b10ed04175a6256a062452d341846 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 09:13:43 +0200 Subject: [PATCH 12/14] fix(rust/guards): use curl instead of gh for PocketIC server download gh CLI is not available in the icp-dev-env-rust container. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/guards.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/guards.yml b/.github/workflows/guards.yml index 9b6943be6f..495b598624 100644 --- a/.github/workflows/guards.yml +++ b/.github/workflows/guards.yml @@ -27,13 +27,11 @@ jobs: icp deploy bash test.sh - name: Download PocketIC server - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release download 14.0.0 \ - --repo dfinity/pocketic \ - --pattern 'pocket-ic-x86_64-linux.gz' \ - --output - | gunzip > /usr/local/bin/pocket-ic-server + curl -sL \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + https://github.com/dfinity/pocketic/releases/download/14.0.0/pocket-ic-x86_64-linux.gz \ + | gunzip > /usr/local/bin/pocket-ic-server chmod +x /usr/local/bin/pocket-ic-server echo "POCKET_IC_BIN=/usr/local/bin/pocket-ic-server" >> $GITHUB_ENV - name: Build WASM and run PocketIC tests From 3b9f2db01e6a8b3a7d4a2add551c2880547aa5d2 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 09:17:40 +0200 Subject: [PATCH 13/14] docs(rust/guards): mention trap message assertion in test.sh description Co-Authored-By: Claude Sonnet 4.6 --- rust/guards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/guards/README.md b/rust/guards/README.md index 0251763229..41ab9694b4 100644 --- a/rust/guards/README.md +++ b/rust/guards/README.md @@ -47,7 +47,7 @@ bash test.sh icp network stop ``` -`bash test.sh` exercises the guard behavior via `icp canister call` — verifying that `TrueAsyncCall` marks items as processed and `FalseAsyncCall` does not. +`bash test.sh` exercises the guard behavior via `icp canister call` — verifying that `TrueAsyncCall` marks items as processed and `FalseAsyncCall` does not, and asserting the exact trap message in each case. ### Run PocketIC integration tests From 76cfe64203e9ade6534c171cd90c76ad54de8741 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 10:32:50 +0200 Subject: [PATCH 14/14] fix(rust/guards): use dfinity/pocketic action to install PocketIC server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces manual curl download with the official dfinity/pocketic composite action — handles OS/arch detection, rate-limit-safe, sets POCKET_IC_BIN automatically. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/guards.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/guards.yml b/.github/workflows/guards.yml index 495b598624..2de521c4c8 100644 --- a/.github/workflows/guards.yml +++ b/.github/workflows/guards.yml @@ -26,14 +26,10 @@ jobs: icp network start -d icp deploy bash test.sh - - name: Download PocketIC server - run: | - curl -sL \ - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - https://github.com/dfinity/pocketic/releases/download/14.0.0/pocket-ic-x86_64-linux.gz \ - | gunzip > /usr/local/bin/pocket-ic-server - chmod +x /usr/local/bin/pocket-ic-server - echo "POCKET_IC_BIN=/usr/local/bin/pocket-ic-server" >> $GITHUB_ENV + - name: Install PocketIC server + uses: dfinity/pocketic@07bfae058ce2aa56994759b563531a7e8d98ba96 # main + with: + pocket-ic-server-version: "14.0.0" - name: Build WASM and run PocketIC tests working-directory: rust/guards run: |