├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── conventional-commits.yml ├── .gitignore ├── .gitmodules ├── .mergify.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── TROUBLESHOOTING.md ├── clippy.toml ├── e2e-tests ├── Cargo.toml ├── build.rs ├── src │ └── bin │ │ ├── api.rs │ │ ├── async.rs │ │ ├── bindgen.rs │ │ ├── bindgen_callee │ │ ├── callee.did │ │ ├── main.rs │ │ └── management.toml │ │ ├── bitcoin_canister.rs │ │ ├── call.rs │ │ ├── canister_info.rs │ │ ├── chunk.rs │ │ ├── http_request.rs │ │ ├── macros │ │ ├── canister.proto │ │ └── main.rs │ │ ├── management_canister.rs │ │ ├── simple_kv_store.rs │ │ └── timers.rs └── tests │ ├── api.rs │ ├── async.rs │ ├── bindgen.rs │ ├── bitcoin_canister.rs │ ├── call.rs │ ├── canister_info.rs │ ├── chunk.rs │ ├── http_request.rs │ ├── macros.rs │ ├── management_canister.rs │ ├── simple_kv_store.rs │ ├── test_utilities.rs │ └── timers.rs ├── ic-cdk-bindgen ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── lib.rs │ └── templates │ ├── dynamic_callee.hbs │ ├── static_callee.hbs │ └── types_only.hbs ├── ic-cdk-executor ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── lib.rs │ └── machinery.rs ├── ic-cdk-macros ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── export.rs │ └── lib.rs ├── ic-cdk-timers ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src │ └── lib.rs ├── ic-cdk ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── V18_GUIDE.md ├── src │ ├── api.rs │ ├── api │ │ ├── call.rs │ │ ├── management_canister │ │ │ ├── bitcoin │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── ecdsa │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── http_request │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── main │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── mod.rs │ │ │ ├── provisional.rs │ │ │ └── schnorr │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ └── stable │ │ │ ├── canister.rs │ │ │ └── mod.rs │ ├── bitcoin_canister.rs │ ├── call.rs │ ├── futures.rs │ ├── futures │ │ └── internals.rs │ ├── lib.rs │ ├── macros.rs │ ├── management_canister.rs │ ├── stable.rs │ └── storage.rs └── tests │ ├── bitcoin.did │ ├── bitcoin_candid_equality.rs │ ├── compile_fail │ ├── lifecycle_functions_should_have_no_return.rs │ ├── lifecycle_functions_should_have_no_return.stderr │ ├── no_generic.rs │ ├── no_generic.stderr │ ├── no_guard_function_for_lifecycle.rs │ ├── no_guard_function_for_lifecycle.stderr │ ├── no_self.rs │ ├── no_self.stderr │ ├── only_function.rs │ └── only_function.stderr │ ├── compile_test.rs │ └── pass │ ├── blank_methods.rs │ ├── guard.rs │ └── method_arg_same_name.rs ├── ic-management-canister-types ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src │ └── lib.rs └── tests │ ├── candid_equality.rs │ └── ic.did ├── ic0 ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── ic0.txt ├── manual_safety_comments.txt ├── src │ ├── lib.rs │ └── sys.rs └── util │ ├── ic0build.rs │ └── work.rs ├── legacy ├── ic-cdk-optimizer │ ├── .gitignore │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── main.rs │ │ ├── passes.rs │ │ └── passes │ │ └── binaryen.rs ├── ic-certified-assets │ └── README.md └── ic-response-codes │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ └── lib.rs ├── library ├── ic-certified-map │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── hashtree.rs │ │ ├── hashtree │ │ └── test.rs │ │ ├── lib.rs │ │ ├── rbtree.rs │ │ └── rbtree │ │ └── test.rs └── ic-ledger-types │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ └── lib.rs ├── rust-toolchain.toml └── scripts └── download_pocket_ic_server.sh /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dfinity/sdk 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 4 | 5 | Fixes # (issue) 6 | 7 | # How Has This Been Tested? 8 | 9 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. 10 | 11 | # Checklist: 12 | 13 | - [ ] The title of this PR complies with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 14 | - [ ] I have edited the CHANGELOG accordingly. 15 | - [ ] I have made corresponding changes to the documentation. 16 | -------------------------------------------------------------------------------- /.github/workflows/conventional-commits.yml: -------------------------------------------------------------------------------- 1 | name: Check PR title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - reopened 8 | - edited 9 | - synchronize 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | check: 17 | name: conventional-pr-title:required 18 | runs-on: ubuntu-24.04 19 | steps: 20 | # Conventional commit patterns: 21 | # verb: description 22 | # verb!: description of breaking change 23 | # verb(scope): Description of change to $scope 24 | # verb(scope)!: Description of breaking change to $scope 25 | # verb: feat, fix, ... 26 | # scope: refers to the part of code being changed. E.g. " (accounts)" or " (accounts,canisters)" 27 | # !: Indicates that the PR contains a breaking change. 28 | - env: 29 | TITLE: ${{ github.event.pull_request.title }} 30 | run: | 31 | echo "PR title: $TITLE" 32 | if [[ "$TITLE" =~ ^(feat|fix|chore|build|ci|docs|style|refactor|perf|test)(\([-a-zA-Z0-9,]+\))?\!?\: ]]; then 33 | echo pass 34 | else 35 | echo "PR title does not match conventions" 36 | exit 1 37 | fi 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .idea/ 3 | .vscode/ 4 | 5 | # Build Output 6 | target/ 7 | 8 | # DFX 9 | .dfx/ 10 | 11 | # The root Cargo.lock is ignored because the workspace only has library crates. 12 | # The candid-extractor has its own Cargo.lock file which is tracked to build the binary. 13 | Cargo.lock 14 | 15 | # Generated bindings 16 | **/declarations/ 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfinity/cdk-rs/cd36aaf80f5734297d4fe19edf677075a894b148/.gitmodules -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatic merge 3 | conditions: 4 | - "#approved-reviews-by>=1" 5 | - "#changes-requested-reviews-by=0" 6 | - status-success=conventional-pr-title 7 | - label=automerge-squash 8 | actions: 9 | merge: 10 | method: squash 11 | strict: smart 12 | commit_message: title+body 13 | delete_head_branch: {} 14 | 15 | - name: Clean up automerge tags 16 | conditions: 17 | - closed 18 | actions: 19 | label: 20 | remove: 21 | - automerge-squash 22 | 23 | - name: Auto-approve auto-PRs 24 | conditions: 25 | - author=dfinity-bot 26 | actions: 27 | review: 28 | type: APPROVE 29 | message: This bot trusts that bot 30 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ic-cdk", 4 | "ic-cdk-bindgen", 5 | "ic-cdk-executor", 6 | "ic-cdk-macros", 7 | "ic-cdk-timers", 8 | "ic-management-canister-types", 9 | "ic0", 10 | "library/*", 11 | "e2e-tests", 12 | ] 13 | resolver = "3" 14 | exclude = ["candid-extractor"] 15 | 16 | [workspace.package] 17 | authors = ["DFINITY Stiftung "] 18 | edition = "2024" 19 | repository = "https://github.com/dfinity/cdk-rs" 20 | # MSRV 21 | # Avoid updating this field unless we use new Rust features. 22 | # Sync with the `toolchain` field when setting up Rust toolchain in ci.yml msrv job. 23 | rust-version = "1.85.0" 24 | license = "Apache-2.0" 25 | 26 | [profile.canister-release] 27 | inherits = "release" 28 | debug = false 29 | panic = "abort" 30 | lto = true 31 | opt-level = 'z' 32 | 33 | [workspace.dependencies] 34 | ic0 = { path = "ic0", version = "1.0.1" } 35 | ic-cdk = { path = "ic-cdk", version = "0.19.0-beta.3" } 36 | ic-cdk-bindgen = { path = "ic-cdk-bindgen", version = "0.2.0-alpha.2" } 37 | ic-cdk-timers = { path = "ic-cdk-timers", version = "1.0.0-beta.1" } 38 | ic-cdk-executor = { path = "ic-cdk-executor", version = "2.0.0-beta.1" } 39 | ic-management-canister-types = { path = "ic-management-canister-types", version = "0.4.1" } 40 | 41 | candid = "0.10.18" # sync with the doc comment in ic-cdk/README.md 42 | candid_parser = "0.2.1" 43 | futures = "0.3" 44 | hex = "0.4" 45 | ic_principal = "0.1" 46 | quote = "1" 47 | serde = "1" 48 | serde_bytes = "0.11" 49 | sha2 = "0.10" 50 | slotmap = "1" 51 | syn = "2" 52 | thiserror = "2.0" 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Canister Development Kit 2 | 3 | [![Documentation](https://docs.rs/ic-cdk/badge.svg)](https://docs.rs/ic-cdk/) 4 | [![Crates.io](https://img.shields.io/crates/v/ic-cdk.svg)](https://crates.io/crates/ic-cdk) 5 | [![License](https://img.shields.io/crates/l/ic-cdk.svg)](https://github.com/dfinity/cdk-rs/blob/main/LICENSE) 6 | [![Downloads](https://img.shields.io/crates/d/ic-cdk.svg)](https://crates.io/crates/ic-cdk) 7 | [![CI](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml) 8 | 9 | **Rust CDK provides tools for building Canisters on Internet Computer (IC).** 10 | 11 | You may be looking for: 12 | 13 | - [Documentation Site of the Internet Computer](https://internetcomputer.org/docs) 14 | - [Tutorials of Rust CDK](https://internetcomputer.org/docs/current/developer-docs/backend/rust/) 15 | - [`dfx` for managing IC projects](https://github.com/dfinity/sdk) 16 | 17 | If you are looking for a crate to communicate with existing canisters on IC, 18 | you may want to check [agent-rs](https://github.com/dfinity/agent-rs). 19 | 20 | # Introduction 21 | 22 | A `canister` is a WebAssembly (wasm) module that can run on the Internet Computer. 23 | 24 | To be a `canister`, a wasm module should communicate with the execution environment using [Canister interfaces (System API)](https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api). 25 | 26 | This repo provides libraries and tools to facilitate developing canisters in Rust. 27 | 28 | - [`ic0`](ic0): 29 | Internet Computer System API binding. 30 | - [`ic-cdk`](ic-cdk): 31 | Internet Computer Canister Development Kit. 32 | - [`ic-cdk-bindgen`](ic-cdk-bindgen): 33 | Generate Rust bindings from Candid to make inter-canister calls. 34 | - [`ic-cdk-macros`](ic-cdk-macros): 35 | Annotate functions with attribute macros to make them exposed public interfaces. 36 | - [`ic-cdk-timers`](ic-cdk-timers): 37 | The library implements multiple and periodic timers. 38 | - [`ic-management-canister-types`](ic-management-canister-types): Types for calling the IC management canister. 39 | - [`ic-certified-map`](library/ic-certified-map): 40 | An implementation of map which support *certified queries*. 41 | - [`ic-ledger-types`](library/ic-ledger-types): 42 | Type definitions to communicate with the ICP ledger canister. 43 | 44 | ## Rust CDK in Action 45 | 46 | In Cargo.toml: 47 | 48 | ```toml 49 | [lib] 50 | crate-type = ["cdylib"] 51 | 52 | [dependencies] 53 | ic-cdk = "0.18" 54 | candid = "0.10" # required if you want to define Candid data types 55 | ``` 56 | 57 | Then in Rust source code: 58 | 59 | ```rust 60 | #[ic_cdk::query] 61 | fn hello() -> String{ 62 | "world".to_string() 63 | } 64 | ``` 65 | 66 | Check [Rust quickstart](https://internetcomputer.org/docs/current/developer-docs/backend/rust/quickstart) for a detailed guidance. 67 | -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## I was linked here by a Cargo error! 4 | 5 | If your Cargo command produces the following error: 6 | 7 | ``` 8 | error: failed to select a version for `ic-cdk-executor`. 9 | ... required by package `yourcrate v0.1.0 (/Users/you/yourcrate)` 10 | versions that meet the requirements `0.1.0` are: 0.1.0 11 | 12 | the package `ic-cdk-executor` links to the native library `ic-cdk async executor`, but it conflicts with a previous package which links to `ic-cdk async executor` as well: 13 | package `ic-cdk-executor v1.0.0` 14 | ... which satisfies dependency `ic-cdk-executor = "^1.0.0` of package `someothercrate v0.1.0` 15 | Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = "ic-cdk async executor"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. 16 | 17 | failed to select a version for `ic-cdk-executor` which could resolve this conflict 18 | ``` 19 | 20 | You have two incompatible versions of `ic-cdk` in your dependency tree. There are two common reasons for this. 21 | 22 | First, a dependency may be using an older (or newer) version of the CDK. Many versions are compatible with each other, but versions 0.17 and earlier are incompatible with version 0.18, and 0.18 is incompatible with 0.19 or later. You will have to wait for those dependencies to update, or patch them yourself. 23 | 24 | Second, a dependency may be using a nominally compatible version of the CDK, but you are using a GitHub prerelease of the CDK with `ic-cdk = { git =`. Git dependencies are automatically incompatible with everything, even if nothing changed. You will need to create a [patch table](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html) that replaces `ic-cdk` with a Git dependency at the same commit (you may also need to replace `ic-cdk-executor`). 25 | 26 | You can find the dependencies responsible with the command `cargo tree -i ic-cdk`. 27 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["VetKD", "PocketIC", "TxID", "UTXO", "McJones", ".."] 2 | -------------------------------------------------------------------------------- /e2e-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-e2e-tests" 3 | version = "0.1.0" 4 | authors = ["DFINITY Stiftung "] 5 | edition = "2021" 6 | description = "End-to-end tests for the Rust Canister Development Kit" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/dfinity/cdk-rs" 9 | publish = false 10 | 11 | [dependencies] 12 | async-channel = "2.3.1" 13 | candid.workspace = true 14 | env_logger = "0.11.5" 15 | escargot = { version = "0.5.7" } 16 | futures.workspace = true 17 | ic-cdk = { workspace = true, features = ["transform-closure"] } 18 | ic-cdk-timers.workspace = true 19 | lazy_static = "1.4.0" 20 | serde.workspace = true 21 | serde_bytes.workspace = true 22 | sha2.workspace = true 23 | prost = "0.14" 24 | 25 | [dev-dependencies] 26 | candid_parser.workspace = true 27 | cargo_metadata = "0.21" 28 | futures = "0.3" 29 | hex.workspace = true 30 | ic-vetkd-utils = { git = "https://github.com/dfinity/ic", rev = "95231520" } 31 | pocket-ic = { git = "https://github.com/dfinity/ic", tag = "release-2025-10-17_03-17-base" } 32 | reqwest = "0.12" 33 | 34 | [build-dependencies] 35 | prost-build = "0.14" 36 | ic-cdk-bindgen.workspace = true 37 | candid.workspace = true 38 | -------------------------------------------------------------------------------- /e2e-tests/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // For the `macros` test 3 | prost_build::compile_protos(&["src/bin/macros/canister.proto"], &["src/"]) 4 | .expect("Failed to compile protos"); 5 | 6 | // For the `bindgen` test 7 | // Static Callee mode bindgen for the management_canister 8 | ic_cdk_bindgen::Config::new( 9 | "management_canister", 10 | "../ic-management-canister-types/tests/ic.did", 11 | ) 12 | .set_type_selector_config("src/bin/bindgen_callee/management.toml") 13 | .static_callee(candid::Principal::management_canister()) 14 | .generate(); 15 | // Dynamic Callee mode bindgen for the bindgen_callee 16 | ic_cdk_bindgen::Config::new("bindgen_callee", "src/bin/bindgen_callee/callee.did") 17 | .dynamic_callee("ICP_CANISTER_ID:bindgen_callee") 18 | .generate(); 19 | } 20 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/bindgen.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::update; 2 | 3 | // It's a common practice to suppress the warnings from generated code. 4 | // These attributes are scoped to the module to avoid affecting other code. 5 | #[allow(dead_code, unused_imports)] 6 | mod management_canister { 7 | include!(concat!(env!("OUT_DIR"), "/management_canister.rs")); 8 | } 9 | 10 | #[update] 11 | async fn call_management_canister() { 12 | // In modern IDE/editors like VSCode, you can often use "Go to Definition" or similar features 13 | // to quickly navigate to the generated bindings. 14 | let _rand = management_canister::raw_rand().await.unwrap(); 15 | } 16 | 17 | #[allow(dead_code, unused_imports)] 18 | mod bindgen_callee { 19 | include!(concat!(env!("OUT_DIR"), "/bindgen_callee.rs")); 20 | } 21 | 22 | #[update] 23 | async fn call_bindgen_callee() { 24 | assert_eq!(bindgen_callee::add(&1, &2).await.unwrap(), 3); 25 | } 26 | 27 | fn main() {} 28 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/bindgen_callee/callee.did: -------------------------------------------------------------------------------- 1 | service : { 2 | add : (int32, int32) -> (int32); 3 | echo : (text) -> (text); 4 | } 5 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/bindgen_callee/main.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{export_candid, update}; 2 | 3 | #[update] 4 | async fn add(a: i32, b: i32) -> i32 { 5 | a + b 6 | } 7 | 8 | #[update] 9 | async fn echo(s: String) -> String { 10 | s 11 | } 12 | 13 | export_candid! {} 14 | 15 | fn main() {} 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use candid_parser::utils::{service_equal, CandidSource}; 20 | 21 | #[test] 22 | fn candid_equality_test() { 23 | let expected = include_str!("callee.did"); 24 | let expected_candid = CandidSource::Text(expected); 25 | 26 | let actual = super::__export_service(); 27 | let actual_candid = CandidSource::Text(&actual); 28 | 29 | let result = service_equal(expected_candid, actual_candid); 30 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/bindgen_callee/management.toml: -------------------------------------------------------------------------------- 1 | [rust] 2 | visibility = "pub(crate)" 3 | attributes = "#[derive(CandidType, Deserialize, Clone, Debug)]" 4 | blob.use_type = "Vec" 5 | change_details.variant.creation.name = "CreationRecord" 6 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/bitcoin_canister.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::bitcoin_canister::*; 2 | use ic_cdk::call::Error; 3 | use ic_cdk::update; 4 | 5 | /// A random Bitcoin address for testing. 6 | const BTC_ADDRESS: &str = "bcrt1qu58aj62urda83c00eylc6w34yl2s6e5rkzqet7"; 7 | 8 | #[update] 9 | async fn execute_non_query_methods(network: Network) { 10 | let arg = GetUtxosRequest { 11 | address: BTC_ADDRESS.to_string(), 12 | network, 13 | filter: Some(UtxosFilter::MinConfirmations(1)), 14 | }; 15 | let _response = bitcoin_get_utxos(&arg).await.unwrap(); 16 | 17 | let arg = GetBalanceRequest { 18 | network, 19 | address: BTC_ADDRESS.to_string(), 20 | min_confirmations: Some(1), 21 | }; 22 | let _balance = bitcoin_get_balance(&arg).await.unwrap(); 23 | 24 | let arg = GetCurrentFeePercentilesRequest { network }; 25 | let _percentiles = bitcoin_get_current_fee_percentiles(&arg).await.unwrap(); 26 | 27 | let arg = GetBlockHeadersRequest { 28 | network, 29 | start_height: 0, 30 | end_height: None, 31 | }; 32 | let _response = bitcoin_get_block_headers(&arg).await.unwrap(); 33 | 34 | let arg = SendTransactionRequest { 35 | transaction: vec![], 36 | network, 37 | }; 38 | let err = bitcoin_send_transaction(&arg).await.unwrap_err(); 39 | assert!(matches!(err, Error::CallRejected { .. })); 40 | } 41 | 42 | fn main() {} 43 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/canister_info.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use ic_cdk::management_canister::{ 3 | canister_info, create_canister_with_extra_cycles, install_code, uninstall_code, 4 | update_settings, CanisterInfoArgs, CanisterInfoResult, 5 | CanisterInstallMode::{Install, Reinstall, Upgrade}, 6 | CanisterSettings, CreateCanisterArgs, InstallCodeArgs, UninstallCodeArgs, UpdateSettingsArgs, 7 | }; 8 | 9 | #[ic_cdk::update] 10 | async fn info(canister_id: Principal) -> CanisterInfoResult { 11 | let request = CanisterInfoArgs { 12 | canister_id, 13 | num_requested_changes: Some(20), 14 | }; 15 | canister_info(&request).await.unwrap() 16 | } 17 | 18 | #[ic_cdk::update] 19 | async fn canister_lifecycle() -> Principal { 20 | let canister_id = 21 | create_canister_with_extra_cycles(&CreateCanisterArgs::default(), 1_000_000_000_000) 22 | .await 23 | .unwrap() 24 | .canister_id; 25 | install_code(&InstallCodeArgs { 26 | mode: Install, 27 | arg: vec![], 28 | wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], 29 | canister_id, 30 | }) 31 | .await 32 | .unwrap(); 33 | uninstall_code(&UninstallCodeArgs { canister_id }) 34 | .await 35 | .unwrap(); 36 | install_code(&InstallCodeArgs { 37 | mode: Install, 38 | arg: vec![], 39 | wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], 40 | canister_id, 41 | }) 42 | .await 43 | .unwrap(); 44 | install_code(&InstallCodeArgs { 45 | mode: Reinstall, 46 | arg: vec![], 47 | wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], 48 | canister_id, 49 | }) 50 | .await 51 | .unwrap(); 52 | install_code(&InstallCodeArgs { 53 | mode: Upgrade(None), 54 | arg: vec![], 55 | wasm_module: vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00], 56 | canister_id, 57 | }) 58 | .await 59 | .unwrap(); 60 | update_settings(&UpdateSettingsArgs { 61 | settings: CanisterSettings { 62 | controllers: Some(vec![ 63 | ic_cdk::api::canister_self(), 64 | canister_id, 65 | Principal::anonymous(), 66 | ]), 67 | compute_allocation: None, 68 | memory_allocation: None, 69 | freezing_threshold: None, 70 | reserved_cycles_limit: None, 71 | log_visibility: None, 72 | wasm_memory_limit: None, 73 | wasm_memory_threshold: None, 74 | environment_variables: None, 75 | }, 76 | canister_id, 77 | }) 78 | .await 79 | .unwrap(); 80 | canister_id 81 | } 82 | 83 | fn main() {} 84 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/chunk.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use ic_cdk::management_canister::{ 3 | clear_chunk_store, create_canister_with_extra_cycles, install_chunked_code, stored_chunks, 4 | upload_chunk, CanisterInstallMode, ChunkHash, ClearChunkStoreArgs, CreateCanisterArgs, 5 | InstallChunkedCodeArgs, StoredChunksArgs, UploadChunkArgs, 6 | }; 7 | use ic_cdk::update; 8 | 9 | #[update] 10 | async fn call_create_canister() -> Principal { 11 | create_canister_with_extra_cycles(&CreateCanisterArgs::default(), 1_000_000_000_000u128) 12 | .await 13 | .unwrap() 14 | .canister_id 15 | } 16 | 17 | #[update] 18 | async fn call_upload_chunk(canister_id: Principal, chunk: Vec) -> Vec { 19 | let arg = UploadChunkArgs { 20 | canister_id, 21 | chunk: chunk.clone(), 22 | }; 23 | upload_chunk(&arg).await.unwrap().hash 24 | } 25 | 26 | #[update] 27 | async fn call_stored_chunks(canister_id: Principal) -> Vec> { 28 | let arg = StoredChunksArgs { canister_id }; 29 | let hashes = stored_chunks(&arg).await.unwrap(); 30 | hashes.into_iter().map(|v| v.hash).collect() 31 | } 32 | 33 | #[update] 34 | async fn call_clear_chunk_store(canister_id: Principal) { 35 | let arg = ClearChunkStoreArgs { canister_id }; 36 | clear_chunk_store(&arg).await.unwrap(); 37 | } 38 | 39 | #[update] 40 | async fn call_install_chunked_code( 41 | canister_id: Principal, 42 | chunk_hashes_list: Vec>, 43 | wasm_module_hash: Vec, 44 | ) { 45 | let chunk_hashes_list = chunk_hashes_list 46 | .iter() 47 | .map(|v| ChunkHash { hash: v.clone() }) 48 | .collect(); 49 | let arg = InstallChunkedCodeArgs { 50 | mode: CanisterInstallMode::Install, 51 | target_canister: canister_id, 52 | store_canister: None, 53 | chunk_hashes_list, 54 | wasm_module_hash, 55 | arg: vec![], 56 | }; 57 | install_chunked_code(&arg).await.unwrap(); 58 | } 59 | 60 | fn main() {} 61 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/http_request.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::management_canister::{ 2 | http_request, http_request_with_closure, transform_context_from_query, HttpHeader, HttpMethod, 3 | HttpRequestArgs, HttpRequestResult, TransformArgs, 4 | }; 5 | use ic_cdk::{query, update}; 6 | 7 | /// All fields are Some except transform. 8 | #[update] 9 | async fn get_without_transform() { 10 | let args = HttpRequestArgs { 11 | url: "https://example.com".to_string(), 12 | method: HttpMethod::GET, 13 | headers: vec![HttpHeader { 14 | name: "request_header_name".to_string(), 15 | value: "request_header_value".to_string(), 16 | }], 17 | body: Some(vec![1]), 18 | max_response_bytes: Some(100_000), 19 | transform: None, 20 | is_replicated: Some(true), 21 | }; 22 | 23 | let res = http_request(&args).await.unwrap(); 24 | assert_eq!(res.status, 200u32); 25 | assert_eq!( 26 | res.headers, 27 | vec![HttpHeader { 28 | name: "response_header_name".to_string(), 29 | value: "response_header_value".to_string(), 30 | }] 31 | ); 32 | assert_eq!(res.body, vec![42]); 33 | } 34 | 35 | /// Method is POST. 36 | #[update] 37 | async fn post() { 38 | let args = HttpRequestArgs { 39 | url: "https://example.com".to_string(), 40 | method: HttpMethod::POST, 41 | ..Default::default() 42 | }; 43 | 44 | http_request(&args).await.unwrap(); 45 | } 46 | 47 | /// Method is HEAD. 48 | #[update] 49 | async fn head() { 50 | let args = HttpRequestArgs { 51 | url: "https://example.com".to_string(), 52 | method: HttpMethod::HEAD, 53 | ..Default::default() 54 | }; 55 | 56 | http_request(&args).await.unwrap(); 57 | } 58 | 59 | /// The standard way to define a transform function. 60 | /// 61 | /// It is a query method that takes a `TransformArgs` and returns an `HttpRequestResult`. 62 | #[query] 63 | fn transform(args: TransformArgs) -> HttpRequestResult { 64 | let mut body = args.response.body; 65 | body.push(args.context[0]); 66 | HttpRequestResult { 67 | status: args.response.status, 68 | headers: args.response.headers, 69 | body, 70 | } 71 | } 72 | 73 | /// Set the transform field with the name of the transform query method. 74 | #[update] 75 | async fn get_with_transform() { 76 | let args = HttpRequestArgs { 77 | url: "https://example.com".to_string(), 78 | method: HttpMethod::GET, 79 | transform: Some(transform_context_from_query( 80 | "transform".to_string(), 81 | vec![42], 82 | )), 83 | ..Default::default() 84 | }; 85 | 86 | let res = http_request(&args).await.unwrap(); 87 | assert_eq!(res.status, 200u32); 88 | assert_eq!( 89 | res.headers, 90 | vec![HttpHeader { 91 | name: "response_header_name".to_string(), 92 | value: "response_header_value".to_string(), 93 | }] 94 | ); 95 | // The first 42 is from the response body, the second 42 is from the transform context. 96 | assert_eq!(res.body, vec![42, 42]); 97 | } 98 | 99 | /// Set the transform field with a closure. 100 | #[update] 101 | async fn get_with_transform_closure() { 102 | let transform = |args: HttpRequestResult| { 103 | let mut body = args.body; 104 | body.push(42); 105 | HttpRequestResult { 106 | status: args.status, 107 | headers: args.headers, 108 | body, 109 | } 110 | }; 111 | let args = HttpRequestArgs { 112 | url: "https://example.com".to_string(), 113 | method: HttpMethod::GET, 114 | transform: None, 115 | ..Default::default() 116 | }; 117 | let res = http_request_with_closure(&args, transform).await.unwrap(); 118 | assert_eq!(res.status, 200u32); 119 | assert_eq!( 120 | res.headers, 121 | vec![HttpHeader { 122 | name: "response_header_name".to_string(), 123 | value: "response_header_value".to_string(), 124 | }] 125 | ); 126 | // The first 42 is from the response body, the second 42 is from the transform closure. 127 | assert_eq!(res.body, vec![42, 42]); 128 | } 129 | 130 | /// Non replicated HTTP request. 131 | #[update] 132 | async fn non_replicated() { 133 | let args = HttpRequestArgs { 134 | url: "https://example.com".to_string(), 135 | method: HttpMethod::GET, 136 | is_replicated: Some(false), 137 | ..Default::default() 138 | }; 139 | 140 | http_request(&args).await.unwrap(); 141 | } 142 | 143 | fn main() {} 144 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/macros/canister.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package canister; 4 | 5 | service ExampleService { 6 | rpc MethodOne (MethodOneRequest) returns (MethodOneResponse); 7 | rpc MethodTwo (MethodTwoRequest) returns (MethodTwoResponse); 8 | } 9 | 10 | message MethodOneRequest { 11 | string input = 1; 12 | } 13 | 14 | message MethodOneResponse { 15 | int32 result = 1; 16 | } 17 | 18 | message MethodTwoRequest { 19 | repeated float values = 1; 20 | } 21 | 22 | message MethodTwoResponse { 23 | bool success = 1; 24 | string message = 2; 25 | } 26 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/macros/main.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{export_candid, update}; 2 | use prost::Message; 3 | use std::marker::PhantomData; 4 | 5 | #[update(decode_with = "decode_arg0")] 6 | fn arg0() {} 7 | fn decode_arg0(_arg_bytes: Vec) {} 8 | 9 | #[update(decode_with = "decode_arg1")] 10 | fn arg1(a: u32) { 11 | assert_eq!(a, 1); 12 | } 13 | fn decode_arg1(arg_bytes: Vec) -> u32 { 14 | candid::utils::decode_one(&arg_bytes).unwrap() 15 | } 16 | 17 | #[update(decode_with = "decode_arg2")] 18 | fn arg2(a: u32, b: u32) { 19 | assert_eq!(a, 1); 20 | assert_eq!(b, 2); 21 | } 22 | fn decode_arg2(arg_bytes: Vec) -> (u32, u32) { 23 | candid::utils::decode_args(&arg_bytes).unwrap() 24 | } 25 | 26 | #[update(encode_with = "encode_ret0")] 27 | fn ret0() {} 28 | fn encode_ret0() -> Vec { 29 | vec![0] 30 | } 31 | 32 | #[update(encode_with = "encode_ret1")] 33 | fn ret1() -> u32 { 34 | 42 35 | } 36 | fn encode_ret1(v1: u32) -> Vec { 37 | vec![v1 as u8] 38 | } 39 | 40 | #[update(encode_with = "encode_ret2")] 41 | fn ret2() -> (u32, u32) { 42 | (1, 2) 43 | } 44 | fn encode_ret2(ret: (u32, u32)) -> Vec { 45 | vec![ret.0 as u8, ret.1 as u8] 46 | } 47 | 48 | // The type binding is generated by the build.rs script in the `e2e-tests` directory. 49 | mod canister { 50 | include!(concat!(env!("OUT_DIR"), "/canister.rs")); 51 | } 52 | use canister::*; 53 | 54 | /// The following two methods demonstrate how to use generic decode/encode functions. 55 | /// They take different types of arguments and return values. 56 | /// While the decode/encode functions are generic and can be used for both entry points. 57 | #[update(decode_with = "from_proto_bytes", encode_with = "to_proto_bytes")] 58 | fn method_one(arg: MethodOneRequest) -> MethodOneResponse { 59 | MethodOneResponse { 60 | result: arg.input.len() as i32, 61 | } 62 | } 63 | #[update(decode_with = "from_proto_bytes", encode_with = "to_proto_bytes")] 64 | fn method_two(arg: MethodTwoRequest) -> MethodTwoResponse { 65 | MethodTwoResponse { 66 | success: arg.values.iter().sum::() > 0.0, 67 | message: "Hello world!".to_string(), 68 | } 69 | } 70 | fn to_proto_bytes(msg: T) -> Vec { 71 | msg.encode_to_vec() 72 | } 73 | fn from_proto_bytes(msg: Vec) -> T { 74 | Message::decode(&msg[..]).unwrap() 75 | } 76 | 77 | /// The following method demonstrates how to specify `guard`/`decode_with`/`encode_with` attributes with generic parameters. 78 | #[update( 79 | guard = "generic_guard::<0>", // N = 0, any input length is accepted 80 | decode_with = "custom_candid_decode::<10000,_>", 81 | encode_with = "custom_candid_encode::<100,_>" 82 | )] 83 | fn generic(a: u32) -> u32 { 84 | a + 1 85 | } 86 | 87 | // A guard to verify the length of the input data is at least N bytes (N is specified as a const generic parameter). 88 | fn generic_guard() -> Result<(), String> { 89 | let input = ic_cdk::api::msg_arg_data(); 90 | if input.len() < N { 91 | Err("generic_guard failed".to_string()) 92 | } else { 93 | Ok(()) 94 | } 95 | } 96 | 97 | // A Candid decode function that uses a custom decoding quota N which is specified as a const generic parameter. 98 | fn custom_candid_decode candid::Deserialize<'a> + candid::CandidType>( 99 | bytes: Vec, 100 | ) -> T { 101 | let mut config = candid::de::DecoderConfig::new(); 102 | config.set_decoding_quota(N); 103 | candid::utils::decode_one_with_config(&bytes[..], &config).unwrap() 104 | } 105 | 106 | // A Candid encode function that checks the length of the encoded bytes is less than N which is specified as a const generic parameter. 107 | fn custom_candid_encode(v: T) -> Vec { 108 | let bytes = candid::utils::encode_one(v).unwrap(); 109 | assert!(bytes.len() < N); 110 | bytes 111 | } 112 | 113 | #[update(manual_reply = true)] 114 | fn manual_reply() -> PhantomData { 115 | let v: u32 = 1; 116 | let reply_bytes = candid::encode_one(v).unwrap(); 117 | ic_cdk::api::msg_reply(reply_bytes); 118 | PhantomData 119 | } 120 | 121 | #[update(guard = "guard1", guard = "guard2")] 122 | fn with_guards() {} 123 | 124 | fn guard1() -> Result<(), String> { 125 | let input = ic_cdk::api::msg_arg_data(); 126 | if !input[0].is_multiple_of(3) { 127 | Err("guard1 failed".to_string()) 128 | } else { 129 | Ok(()) 130 | } 131 | } 132 | 133 | fn guard2() -> Result<(), String> { 134 | let input = ic_cdk::api::msg_arg_data(); 135 | if !input[0].is_multiple_of(5) { 136 | Err("guard2 failed".to_string()) 137 | } else { 138 | Ok(()) 139 | } 140 | } 141 | 142 | // This method will be called with "malicious" payloads. 143 | // We expected that a default skipping_quota (10_000) is set in the update/query macros. 144 | // This will trigger a decoding error when the payload exceeds the quota. 145 | #[update] 146 | fn default_skipping_quota(_arg: Option) {} 147 | 148 | export_candid! {} 149 | 150 | fn main() { 151 | println!("{}", __export_service()); 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use candid_parser::utils::{service_equal, CandidSource}; 157 | 158 | #[test] 159 | fn candid_equality_test() { 160 | // If `decode_with` is specified, the argument type would be `blob` in Candid. 161 | // If `encode_with` is specified, the return type would be `blob` in Candid. 162 | let expected = "service : { 163 | arg0 : (blob) -> (); 164 | arg1 : (blob) -> (); 165 | arg2 : (blob) -> (); 166 | ret0 : () -> (blob); 167 | ret1 : () -> (blob); 168 | ret2 : () -> (blob); 169 | method_one : (blob) -> (blob); 170 | method_two : (blob) -> (blob); 171 | generic : (blob) -> (blob); 172 | manual_reply : () -> (nat32); 173 | with_guards : () -> (); 174 | default_skipping_quota : (opt nat32) -> (); 175 | }"; 176 | let expected_candid = CandidSource::Text(expected); 177 | 178 | let actual = super::__export_service(); 179 | let actual_candid = CandidSource::Text(&actual); 180 | 181 | let result = service_equal(expected_candid, actual_candid); 182 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/simple_kv_store.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{post_upgrade, pre_upgrade, query, update}; 2 | use serde_bytes::ByteBuf; 3 | use std::cell::RefCell; 4 | use std::collections::BTreeMap; 5 | 6 | type Store = BTreeMap; 7 | 8 | thread_local! { 9 | static STORE: RefCell = RefCell::default(); 10 | } 11 | 12 | #[update] 13 | fn insert(key: String, value: ByteBuf) { 14 | STORE.with(|store| store.borrow_mut().insert(key, value)); 15 | } 16 | 17 | #[query] 18 | fn lookup(key: String) -> Option { 19 | STORE.with(|store| store.borrow().get(&key).cloned()) 20 | } 21 | 22 | #[pre_upgrade] 23 | fn pre_upgrade() { 24 | STORE.with(|store| ic_cdk::storage::stable_save((store,)).unwrap()); 25 | } 26 | 27 | #[post_upgrade] 28 | fn post_upgrade() { 29 | let (persisted_store,): (Store,) = ic_cdk::storage::stable_restore().unwrap(); 30 | STORE.with(|store| *store.borrow_mut() = persisted_store); 31 | } 32 | 33 | fn main() {} 34 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/timers.rs: -------------------------------------------------------------------------------- 1 | use futures::{stream::FuturesUnordered, StreamExt}; 2 | use ic_cdk::{api::canister_self, call::Call, futures::spawn, query, update}; 3 | use ic_cdk_timers::{clear_timer, set_timer, set_timer_interval, TimerId}; 4 | use std::{ 5 | cell::{Cell, RefCell}, 6 | sync::atomic::{AtomicU32, Ordering}, 7 | time::Duration, 8 | }; 9 | 10 | thread_local! { 11 | static EVENTS: RefCell> = RefCell::default(); 12 | static LONG: Cell = Cell::default(); 13 | static REPEATING: Cell = Cell::default(); 14 | } 15 | 16 | static EXECUTED_TIMERS: AtomicU32 = AtomicU32::new(0); 17 | 18 | #[query] 19 | fn get_events() -> Vec { 20 | EVENTS.with(|events| events.borrow().clone()) 21 | } 22 | 23 | #[update] 24 | fn clear_events() { 25 | EVENTS.with(|events| events.borrow_mut().clear()); 26 | } 27 | 28 | #[update] 29 | fn schedule() { 30 | set_timer(Duration::from_secs(2), async { 31 | add_event("2"); 32 | }); 33 | set_timer(Duration::from_secs(1), async { 34 | add_event("1"); 35 | set_timer(Duration::from_secs(2), async { add_event("3") }); 36 | }); 37 | set_timer(Duration::from_secs(4), async { 38 | add_event("4"); 39 | }); 40 | } 41 | 42 | #[update] 43 | fn schedule_n_timers(n: u32) { 44 | for i in 0..n { 45 | ic_cdk_timers::set_timer(Duration::from_nanos(i.into()), async move { 46 | EXECUTED_TIMERS.fetch_add(1, Ordering::Relaxed); 47 | }); 48 | } 49 | } 50 | 51 | #[query] 52 | fn executed_timers() -> u32 { 53 | EXECUTED_TIMERS.load(Ordering::Relaxed) 54 | } 55 | 56 | #[update] 57 | fn schedule_long() { 58 | let id = set_timer(Duration::from_secs(9), async { add_event("long") }); 59 | LONG.with(|long| long.set(id)); 60 | } 61 | 62 | #[update] 63 | fn set_self_cancelling_timer() { 64 | let id = set_timer(Duration::from_secs(0), async { 65 | cancel_long(); 66 | add_event("timer cancelled self"); 67 | }); 68 | LONG.with(|long| long.set(id)); 69 | } 70 | 71 | #[update] 72 | fn cancel_long() { 73 | LONG.with(|long| clear_timer(long.get())); 74 | } 75 | 76 | #[update] 77 | fn start_repeating() { 78 | let id = set_timer_interval(Duration::from_secs(1), async || { 79 | add_event("repeat"); 80 | }); 81 | REPEATING.with(|repeating| repeating.set(id)); 82 | } 83 | 84 | #[update] 85 | fn set_self_cancelling_periodic_timer() { 86 | let id = set_timer_interval(Duration::from_secs(0), async || { 87 | stop_repeating(); 88 | add_event("periodic timer cancelled self"); 89 | }); 90 | REPEATING.with(|repeating| repeating.set(id)); 91 | } 92 | 93 | #[update] 94 | fn stop_repeating() { 95 | REPEATING.with(|repeating| clear_timer(repeating.get())); 96 | } 97 | 98 | fn add_event(event: &str) { 99 | EVENTS.with(|events| events.borrow_mut().push(event.to_string())); 100 | } 101 | 102 | #[update] 103 | fn global_timer_set(timestamp: u64) -> u64 { 104 | ic_cdk::api::global_timer_set(timestamp) 105 | } 106 | 107 | #[update] 108 | fn add_event_method(name: &str) { 109 | add_event(&format!("method {name}")); 110 | } 111 | 112 | #[update] 113 | fn async_await() { 114 | set_timer(Duration::from_secs(1), async { 115 | add_event("1"); 116 | Call::bounded_wait(canister_self(), "add_event_method") 117 | .with_arg("outer") 118 | .await 119 | .unwrap(); 120 | add_event("2"); 121 | spawn(async { 122 | Call::bounded_wait(canister_self(), "add_event_method") 123 | .with_arg("spawned") 124 | .await 125 | .unwrap(); 126 | }); 127 | let futs = FuturesUnordered::new(); 128 | for _ in 0..3 { 129 | futs.push(async move { 130 | Call::bounded_wait(canister_self(), "add_event_method") 131 | .with_arg("concurrent") 132 | .await 133 | .unwrap(); 134 | }); 135 | } 136 | futs.collect::<()>().await; 137 | }); 138 | add_event("0") 139 | } 140 | 141 | fn main() {} 142 | -------------------------------------------------------------------------------- /e2e-tests/tests/bindgen.rs: -------------------------------------------------------------------------------- 1 | mod test_utilities; 2 | use candid::Principal; 3 | use ic_cdk::management_canister::{CanisterSettings, EnvironmentVariable, UpdateSettingsArgs}; 4 | use test_utilities::{cargo_build_canister, pic_base, update}; 5 | 6 | #[test] 7 | fn bindgen() { 8 | let wasm = cargo_build_canister("bindgen"); 9 | let callee_wasm = cargo_build_canister("bindgen_callee"); 10 | 11 | let pic = pic_base().build(); 12 | 13 | let callee_canister_id = pic.create_canister(); 14 | pic.add_cycles(callee_canister_id, 100_000_000_000_000); 15 | pic.install_canister(callee_canister_id, callee_wasm, vec![], None); 16 | 17 | let canister_id = pic.create_canister(); 18 | pic.add_cycles(canister_id, 100_000_000_000_000); 19 | pic.install_canister(canister_id, wasm, vec![], None); 20 | // Make the callee canister a controller of the main canister. 21 | // We will impersonate a management canister call to set the env var later. 22 | pic.set_controllers(canister_id, None, vec![callee_canister_id]) 23 | .expect("failed to set controllers"); 24 | 25 | let _: () = update(&pic, canister_id, "call_management_canister", ()).unwrap(); 26 | 27 | // The required env var is not set, so the call will fail. 28 | let required_env_var = "ICP_CANISTER_ID:bindgen_callee"; 29 | let res: Result<(), _> = update(&pic, canister_id, "call_bindgen_callee", ()); 30 | assert!(res.unwrap_err().reject_message.contains(&format!( 31 | "env var `{}` is not set. Canister controller can set it using tools like icp-cli.", 32 | required_env_var 33 | ))); 34 | 35 | // Set a invalid value for the required env var. 36 | // Then the call will fail for a different reason. 37 | let effective_principal = 38 | pocket_ic::common::rest::RawEffectivePrincipal::CanisterId(canister_id.as_slice().to_vec()); 39 | let invalid_value = "Not a Principal"; 40 | let settings = CanisterSettings { 41 | environment_variables: Some(vec![EnvironmentVariable { 42 | name: required_env_var.into(), 43 | value: invalid_value.to_string(), 44 | }]), 45 | ..Default::default() 46 | }; 47 | let args = UpdateSettingsArgs { 48 | canister_id, 49 | settings, 50 | }; 51 | let _: () = pocket_ic::call_candid_as( 52 | &pic, 53 | Principal::management_canister(), 54 | effective_principal.clone(), 55 | callee_canister_id, 56 | "update_settings", 57 | (args,), 58 | ) 59 | .expect("failed to call update_settings for setting env var"); 60 | let res: Result<(), _> = update(&pic, canister_id, "call_bindgen_callee", ()); 61 | assert!(res.unwrap_err().reject_message.contains(&format!( 62 | "failed to parse Principal from env var `{}`, value `{}`", 63 | required_env_var, invalid_value 64 | ))); 65 | 66 | // Set the required env var. 67 | // This is expected to be set automatically by `icp-cli`. 68 | let settings = CanisterSettings { 69 | environment_variables: Some(vec![EnvironmentVariable { 70 | name: required_env_var.into(), 71 | value: callee_canister_id.to_string(), 72 | }]), 73 | ..Default::default() 74 | }; 75 | let args = UpdateSettingsArgs { 76 | canister_id, 77 | settings, 78 | }; 79 | 80 | let _: () = pocket_ic::call_candid_as( 81 | &pic, 82 | Principal::management_canister(), 83 | effective_principal, 84 | callee_canister_id, 85 | "update_settings", 86 | (args,), 87 | ) 88 | .expect("failed to call update_settings for setting env var"); 89 | let _: () = update(&pic, canister_id, "call_bindgen_callee", ()).unwrap(); 90 | } 91 | -------------------------------------------------------------------------------- /e2e-tests/tests/bitcoin_canister.rs: -------------------------------------------------------------------------------- 1 | use candid::{IDLArgs, Principal}; 2 | use candid_parser::parse_idl_args; 3 | use cargo_metadata::MetadataCommand; 4 | use ic_cdk::bitcoin_canister::Network; 5 | use std::path::PathBuf; 6 | 7 | mod test_utilities; 8 | use test_utilities::{cargo_build_canister, pic_base, update}; 9 | 10 | #[test] 11 | fn test_bitcoin_canister() { 12 | // Mainnet 13 | let mainnet_id = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 4, 1, 1]); 14 | let mainnet_init_args = r#"( 15 | record { 16 | api_access = variant { enabled }; 17 | lazily_evaluate_fee_percentiles = variant { enabled }; 18 | blocks_source = principal "aaaaa-aa"; 19 | fees = record { 20 | get_current_fee_percentiles = 10_000_000 : nat; 21 | get_utxos_maximum = 10_000_000_000 : nat; 22 | get_block_headers_cycles_per_ten_instructions = 10 : nat; 23 | get_current_fee_percentiles_maximum = 100_000_000 : nat; 24 | send_transaction_per_byte = 20_000_000 : nat; 25 | get_balance = 10_000_000 : nat; 26 | get_utxos_cycles_per_ten_instructions = 10 : nat; 27 | get_block_headers_base = 50_000_000 : nat; 28 | get_utxos_base = 50_000_000 : nat; 29 | get_balance_maximum = 100_000_000 : nat; 30 | send_transaction_base = 5_000_000_000 : nat; 31 | get_block_headers_maximum = 10_000_000_000 : nat; 32 | }; 33 | network = variant { mainnet }; 34 | stability_threshold = 100 : nat; 35 | syncing = variant { enabled }; 36 | burn_cycles = variant { enabled }; 37 | disable_api_if_not_fully_synced = variant { enabled }; 38 | }, 39 | )"#; 40 | test_network(Network::Mainnet, mainnet_id, mainnet_init_args); 41 | // Testnet 42 | let testnet_id = Principal::from_slice(&[0, 0, 0, 0, 1, 160, 0, 1, 1, 1]); 43 | let testnet_init_args = r#"( 44 | record { 45 | api_access = variant { enabled }; 46 | lazily_evaluate_fee_percentiles = variant { enabled }; 47 | blocks_source = principal "aaaaa-aa"; 48 | fees = record { 49 | get_current_fee_percentiles = 4_000_000 : nat; 50 | get_utxos_maximum = 4_000_000_000 : nat; 51 | get_block_headers_cycles_per_ten_instructions = 10 : nat; 52 | get_current_fee_percentiles_maximum = 40_000_000 : nat; 53 | send_transaction_per_byte = 8_000_000 : nat; 54 | get_balance = 4_000_000 : nat; 55 | get_utxos_cycles_per_ten_instructions = 10 : nat; 56 | get_block_headers_base = 20_000_000 : nat; 57 | get_utxos_base = 20_000_000 : nat; 58 | get_balance_maximum = 40_000_000 : nat; 59 | send_transaction_base = 2_000_000_000 : nat; 60 | get_block_headers_maximum = 4_000_000_000 : nat; 61 | }; 62 | network = variant { testnet }; 63 | stability_threshold = 144 : nat; 64 | syncing = variant { enabled }; 65 | burn_cycles = variant { enabled }; 66 | disable_api_if_not_fully_synced = variant { enabled }; 67 | }, 68 | )"#; 69 | test_network(Network::Testnet, testnet_id, testnet_init_args); 70 | // Regtest 71 | let regtest_id = testnet_id; 72 | let regtest_init_args = r#"( 73 | record { 74 | api_access = variant { enabled }; 75 | lazily_evaluate_fee_percentiles = variant { enabled }; 76 | blocks_source = principal "aaaaa-aa"; 77 | fees = record { 78 | get_current_fee_percentiles = 0 : nat; 79 | get_utxos_maximum = 0 : nat; 80 | get_block_headers_cycles_per_ten_instructions = 0 : nat; 81 | get_current_fee_percentiles_maximum = 0 : nat; 82 | send_transaction_per_byte = 0 : nat; 83 | get_balance = 0 : nat; 84 | get_utxos_cycles_per_ten_instructions = 0 : nat; 85 | get_block_headers_base = 0 : nat; 86 | get_utxos_base = 0 : nat; 87 | get_balance_maximum = 0 : nat; 88 | send_transaction_base = 0 : nat; 89 | get_block_headers_maximum = 0 : nat; 90 | }; 91 | network = variant { regtest }; 92 | stability_threshold = 144 : nat; 93 | syncing = variant { enabled }; 94 | burn_cycles = variant { enabled }; 95 | disable_api_if_not_fully_synced = variant { enabled }; 96 | }, 97 | )"#; 98 | test_network(Network::Regtest, regtest_id, regtest_init_args); 99 | } 100 | 101 | fn test_network(network: Network, btc_id: Principal, init_args: &str) { 102 | let wasm = cargo_build_canister("bitcoin_canister"); 103 | // The Bitcoin canisters can still function without connecting to a `bitcoind` node. 104 | // The interface check and the cycles cost logic are still valid. 105 | let pic = pic_base().with_bitcoin_subnet().build(); 106 | let canister_id = pic.create_canister(); 107 | pic.add_cycles(canister_id, 10_000_000_000_000u128); // 10 T 108 | pic.install_canister(canister_id, wasm, vec![], None); 109 | 110 | let btc_canister_wasm = std::fs::read(cache_btc_canister_wasm()).unwrap(); 111 | let _ = pic.create_canister_with_id(None, None, btc_id).unwrap(); 112 | pic.add_cycles(btc_id, 10_000_000_000_000u128); 113 | let args: IDLArgs = parse_idl_args(init_args).expect("failed to parse IDL args"); 114 | let encoded_args = args.to_bytes().expect("failed to encode IDL args"); 115 | pic.install_canister(btc_id, btc_canister_wasm.clone(), encoded_args, None); 116 | let () = update(&pic, canister_id, "execute_non_query_methods", (network,)).unwrap(); 117 | } 118 | 119 | fn cache_btc_canister_wasm() -> PathBuf { 120 | const EXPECTED_TAG: &str = "release%2F2024-08-30"; // The slash is encoded as %2F in the URL 121 | let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 122 | let cargo_toml_path = dir.join("Cargo.toml"); 123 | let target_dir = MetadataCommand::new() 124 | .manifest_path(&cargo_toml_path) 125 | .exec() 126 | .expect("failed to run cargo metadata") 127 | .target_directory; 128 | let artifact_dir = target_dir.join("e2e-tests-artifacts"); 129 | std::fs::create_dir_all(&artifact_dir).expect("failed to create artifact directory"); 130 | let tag_file = artifact_dir.join("ic-btc-canister-tag"); 131 | let binary_file = artifact_dir.join("ic-btc-canister.wasm.gz"); 132 | if let Ok(tag) = std::fs::read_to_string(&tag_file) { 133 | if tag == EXPECTED_TAG && binary_file.exists() { 134 | return binary_file.into(); 135 | } 136 | } 137 | let url = format!( 138 | " https://github.com/dfinity/bitcoin-canister/releases/download/{EXPECTED_TAG}/ic-btc-canister.wasm.gz"); 139 | let gz_bytes = reqwest::blocking::get(url) 140 | .expect("failed to download ic-btc-canister.wasm.gz") 141 | .bytes() 142 | .expect("failed to get bytes of ic-btc-canister.wasm.gz") 143 | .to_vec(); 144 | std::fs::write(&binary_file, gz_bytes).expect("failed to write binary file"); 145 | std::fs::write(tag_file, EXPECTED_TAG).expect("failed to write tag file"); 146 | binary_file.into() 147 | } 148 | -------------------------------------------------------------------------------- /e2e-tests/tests/call.rs: -------------------------------------------------------------------------------- 1 | mod test_utilities; 2 | use test_utilities::{cargo_build_canister, pic_base, update}; 3 | 4 | #[test] 5 | fn call() { 6 | let wasm = cargo_build_canister("call"); 7 | let pic = pic_base().build(); 8 | let canister_id = pic.create_canister(); 9 | pic.add_cycles(canister_id, 100_000_000_000_000); 10 | pic.install_canister(canister_id, wasm, vec![], None); 11 | let _: () = update(&pic, canister_id, "call_foo", ()).unwrap(); 12 | let _: () = update(&pic, canister_id, "call_echo", ()).unwrap(); 13 | let _: () = update(&pic, canister_id, "retry_calls", ()).unwrap(); 14 | let _: () = update(&pic, canister_id, "join_calls", ()).unwrap(); 15 | let _: () = update( 16 | &pic, 17 | canister_id, 18 | "insufficient_liquid_cycle_balance_error", 19 | (), 20 | ) 21 | .unwrap(); 22 | 23 | let _: () = update(&pic, canister_id, "call_error_ext", ()).unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /e2e-tests/tests/chunk.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use sha2::Digest; 3 | 4 | mod test_utilities; 5 | use test_utilities::{cargo_build_canister, pic_base, update}; 6 | 7 | #[test] 8 | fn test_chunk() { 9 | let wasm = cargo_build_canister("chunk"); 10 | let pic = pic_base().build(); 11 | let canister_id = pic.create_canister(); 12 | pic.add_cycles(canister_id, 100_000_000_000_000); 13 | pic.install_canister(canister_id, wasm, vec![], None); 14 | let (target_canister_id,): (Principal,) = 15 | update(&pic, canister_id, "call_create_canister", ()).unwrap(); 16 | 17 | let wasm_module = b"\x00asm\x01\x00\x00\x00".to_vec(); 18 | let wasm_module_hash = sha2::Sha256::digest(&wasm_module).to_vec(); 19 | let chunk1 = wasm_module[..4].to_vec(); 20 | let chunk2 = wasm_module[4..].to_vec(); 21 | let hash1_expected = sha2::Sha256::digest(&chunk1).to_vec(); 22 | let hash2_expected = sha2::Sha256::digest(&chunk2).to_vec(); 23 | 24 | let (hash1_return,): (Vec,) = update( 25 | &pic, 26 | canister_id, 27 | "call_upload_chunk", 28 | (target_canister_id, chunk1.clone()), 29 | ) 30 | .unwrap(); 31 | assert_eq!(&hash1_return, &hash1_expected); 32 | 33 | let () = update( 34 | &pic, 35 | canister_id, 36 | "call_clear_chunk_store", 37 | (target_canister_id,), 38 | ) 39 | .unwrap(); 40 | 41 | let (_hash1_return,): (Vec,) = update( 42 | &pic, 43 | canister_id, 44 | "call_upload_chunk", 45 | (target_canister_id, chunk1), 46 | ) 47 | .unwrap(); 48 | let (_hash2_return,): (Vec,) = update( 49 | &pic, 50 | canister_id, 51 | "call_upload_chunk", 52 | (target_canister_id, chunk2), 53 | ) 54 | .unwrap(); 55 | 56 | let (hashes,): (Vec>,) = update( 57 | &pic, 58 | canister_id, 59 | "call_stored_chunks", 60 | (target_canister_id,), 61 | ) 62 | .unwrap(); 63 | // the hashes returned are not guaranteed to be in order 64 | assert_eq!(hashes.len(), 2); 65 | assert!(hashes.contains(&hash1_expected)); 66 | assert!(hashes.contains(&hash2_expected)); 67 | 68 | let () = update( 69 | &pic, 70 | canister_id, 71 | "call_install_chunked_code", 72 | ( 73 | target_canister_id, 74 | // the order of the hashes matters 75 | vec![hash1_expected, hash2_expected], 76 | wasm_module_hash, 77 | ), 78 | ) 79 | .unwrap(); 80 | } 81 | -------------------------------------------------------------------------------- /e2e-tests/tests/http_request.rs: -------------------------------------------------------------------------------- 1 | use candid::{Encode, Principal}; 2 | use pocket_ic::common::rest::{ 3 | CanisterHttpHeader, CanisterHttpReply, CanisterHttpRequest, CanisterHttpResponse, 4 | MockCanisterHttpResponse, 5 | }; 6 | use pocket_ic::PocketIc; 7 | 8 | mod test_utilities; 9 | use test_utilities::{cargo_build_canister, pic_base}; 10 | 11 | #[test] 12 | fn test_http_request() { 13 | let wasm = cargo_build_canister("http_request"); 14 | let pic = pic_base().build(); 15 | 16 | let canister_id = pic.create_canister(); 17 | pic.add_cycles(canister_id, 3_000_000_000_000u128); 18 | pic.install_canister(canister_id, wasm, vec![], None); 19 | 20 | test_one_http_request(&pic, canister_id, "get_without_transform"); 21 | test_one_http_request(&pic, canister_id, "post"); 22 | test_one_http_request(&pic, canister_id, "head"); 23 | test_one_http_request(&pic, canister_id, "get_with_transform"); 24 | test_one_http_request(&pic, canister_id, "get_with_transform_closure"); 25 | test_one_http_request(&pic, canister_id, "non_replicated"); 26 | } 27 | 28 | fn test_one_http_request(pic: &PocketIc, canister_id: Principal, method: &str) { 29 | let call_id = pic 30 | .submit_call( 31 | canister_id, 32 | Principal::anonymous(), 33 | method, 34 | Encode!(&()).unwrap(), 35 | ) 36 | .unwrap(); 37 | let canister_http_requests = tick_until_next_request(pic); 38 | assert_eq!(canister_http_requests.len(), 1); 39 | let request = &canister_http_requests[0]; 40 | pic.mock_canister_http_response(MockCanisterHttpResponse { 41 | subnet_id: request.subnet_id, 42 | request_id: request.request_id, 43 | response: CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { 44 | status: 200, 45 | headers: vec![CanisterHttpHeader { 46 | name: "response_header_name".to_string(), 47 | value: "response_header_value".to_string(), 48 | }], 49 | body: vec![42], 50 | }), 51 | additional_responses: vec![], 52 | }); 53 | pic.await_call(call_id).unwrap(); 54 | } 55 | 56 | fn tick_until_next_request(pic: &PocketIc) -> Vec { 57 | for _ in 0..10 { 58 | let requests = pic.get_canister_http(); 59 | if !requests.is_empty() { 60 | return requests; 61 | } 62 | pic.tick(); 63 | } 64 | vec![] 65 | } 66 | -------------------------------------------------------------------------------- /e2e-tests/tests/macros.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use prost::Message; 3 | 4 | mod canister { 5 | include!(concat!(env!("OUT_DIR"), "/canister.rs")); 6 | } 7 | use canister::*; 8 | 9 | mod test_utilities; 10 | use test_utilities::{cargo_build_canister, pic_base, update}; 11 | 12 | #[test] 13 | fn call_macros() { 14 | let wasm = cargo_build_canister("macros"); 15 | let pic = pic_base().build(); 16 | let canister_id = pic.create_canister(); 17 | pic.add_cycles(canister_id, 100_000_000_000_000); 18 | pic.install_canister(canister_id, wasm, vec![], None); 19 | let _: () = update(&pic, canister_id, "arg0", ()).unwrap(); 20 | let _: () = update(&pic, canister_id, "arg1", (1u32,)).unwrap(); 21 | let _: () = update(&pic, canister_id, "arg2", (1u32, 2u32)).unwrap(); 22 | let sender = Principal::anonymous(); 23 | let res = pic 24 | .update_call(canister_id, sender, "ret0", vec![]) 25 | .unwrap(); 26 | assert_eq!(res, vec![0]); 27 | let res = pic 28 | .update_call(canister_id, sender, "ret1", vec![]) 29 | .unwrap(); 30 | assert_eq!(res, vec![42]); 31 | let res = pic 32 | .update_call(canister_id, sender, "ret2", vec![]) 33 | .unwrap(); 34 | assert_eq!(res, vec![1, 2]); 35 | let res = pic 36 | .update_call( 37 | canister_id, 38 | sender, 39 | "method_one", 40 | MethodOneRequest { 41 | input: "Hello".to_string(), 42 | } 43 | .encode_to_vec(), 44 | ) 45 | .unwrap(); 46 | assert_eq!(res, MethodOneResponse { result: 5i32 }.encode_to_vec()); 47 | let res = pic 48 | .update_call( 49 | canister_id, 50 | sender, 51 | "method_two", 52 | MethodTwoRequest { values: vec![1.0] }.encode_to_vec(), 53 | ) 54 | .unwrap(); 55 | assert_eq!( 56 | res, 57 | MethodTwoResponse { 58 | success: true, 59 | message: "Hello world!".to_string() 60 | } 61 | .encode_to_vec() 62 | ); 63 | let _: (u32,) = update(&pic, canister_id, "manual_reply", ()).unwrap(); 64 | let (res,): (u32,) = update(&pic, canister_id, "generic", (1u32,)).unwrap(); 65 | assert_eq!(res, 2); 66 | 67 | let rej = pic 68 | .update_call(canister_id, sender, "with_guards", vec![1]) 69 | .unwrap_err(); 70 | assert_eq!(rej.reject_message, "guard1 failed"); 71 | let rej = pic 72 | .update_call(canister_id, sender, "with_guards", vec![3]) 73 | .unwrap_err(); 74 | assert_eq!(rej.reject_message, "guard2 failed"); 75 | let _res = pic 76 | .update_call(canister_id, sender, "with_guards", vec![15]) 77 | .unwrap(); 78 | 79 | // The entry-point expects an `opt nat32` value. 80 | // Here we send some blob that decoder need to skip. 81 | // The call is expected to: 82 | // * succeed: when the blob is relatively small 83 | // * fail: when the blob is too large 84 | let _: () = update( 85 | &pic, 86 | canister_id, 87 | "default_skipping_quota", 88 | (vec![42; 1400],), 89 | ) 90 | .unwrap(); 91 | let res: Result<(), _> = update( 92 | &pic, 93 | canister_id, 94 | "default_skipping_quota", 95 | (vec![42; 1500],), 96 | ); 97 | assert!(res 98 | .unwrap_err() 99 | .reject_message 100 | .contains("Skipping cost exceeds the limit")); 101 | } 102 | -------------------------------------------------------------------------------- /e2e-tests/tests/management_canister.rs: -------------------------------------------------------------------------------- 1 | mod test_utilities; 2 | use test_utilities::{cargo_build_canister, pic_base, update}; 3 | 4 | #[test] 5 | fn test_management_canister() { 6 | let wasm = cargo_build_canister("management_canister"); 7 | // Setup pocket-ic with an II subnet which is required by the "provisional" test. 8 | let pic = pic_base().with_ii_subnet().build(); 9 | 10 | let canister_id = pic.create_canister(); 11 | let subnet_id = pic.get_subnet(canister_id).unwrap(); 12 | pic.add_cycles(canister_id, 10_000_000_000_000u128); // 10 T 13 | pic.install_canister(canister_id, wasm.clone(), vec![], None); 14 | let () = update(&pic, canister_id, "basic", ()).unwrap(); 15 | let () = update(&pic, canister_id, "env_var", ()).unwrap(); 16 | let () = update(&pic, canister_id, "ecdsa", ()).unwrap(); 17 | let () = update(&pic, canister_id, "schnorr", ()).unwrap(); 18 | let () = update(&pic, canister_id, "metrics", (subnet_id,)).unwrap(); 19 | let () = update(&pic, canister_id, "subnet", (subnet_id,)).unwrap(); 20 | let () = update(&pic, canister_id, "snapshots", ()).unwrap(); 21 | 22 | // provisional 23 | // Install the test canister on the II subnet, so that it can provisional create canisters on the same subnet. 24 | let ii_subnet_id = pic.topology().get_ii().unwrap(); 25 | let canister_id_on_ii = pic.create_canister_on_subnet(None, None, ii_subnet_id); 26 | pic.add_cycles(canister_id_on_ii, 10_000_000_000_000u128); // 10 T 27 | pic.install_canister(canister_id_on_ii, wasm, vec![], None); 28 | let () = update(&pic, canister_id_on_ii, "provisional", ()).unwrap(); 29 | 30 | // vetkd 31 | const VETKD_TRANSPORT_SECRET_KEY_SEED: [u8; 32] = [13; 32]; 32 | let transport_key = 33 | ic_vetkd_utils::TransportSecretKey::from_seed(VETKD_TRANSPORT_SECRET_KEY_SEED.to_vec()) 34 | .expect("Failed to generate transport secret key"); 35 | let transport_public_key = transport_key.public_key(); 36 | let () = update(&pic, canister_id, "vetkd", (transport_public_key,)).unwrap(); 37 | } 38 | -------------------------------------------------------------------------------- /e2e-tests/tests/simple_kv_store.rs: -------------------------------------------------------------------------------- 1 | use pocket_ic::query_candid; 2 | use serde_bytes::ByteBuf; 3 | 4 | mod test_utilities; 5 | use test_utilities::{cargo_build_canister, pic_base, update}; 6 | 7 | /// Checks that a canister that uses [`ic_cdk::storage::stable_save`] 8 | /// and [`ic_cdk::storage::stable_restore`] functions can keep its data 9 | /// across upgrades. 10 | #[test] 11 | fn test_storage_roundtrip() { 12 | let wasm = cargo_build_canister("simple_kv_store"); 13 | let pic = pic_base().build(); 14 | let canister_id = pic.create_canister(); 15 | pic.add_cycles(canister_id, 2_000_000_000_000); 16 | pic.install_canister(canister_id, wasm.clone(), vec![], None); 17 | 18 | let () = update(&pic, canister_id, "insert", (&"candid", &b"did")) 19 | .expect("failed to insert 'candid'"); 20 | 21 | pic.upgrade_canister(canister_id, wasm, vec![], None) 22 | .expect("failed to upgrade the simple_kv_store canister"); 23 | 24 | let (result,): (Option,) = 25 | query_candid(&pic, canister_id, "lookup", (&"candid",)).expect("failed to lookup 'candid'"); 26 | assert_eq!(result, Some(ByteBuf::from(b"did".to_vec()))); 27 | } 28 | -------------------------------------------------------------------------------- /e2e-tests/tests/test_utilities.rs: -------------------------------------------------------------------------------- 1 | use candid::utils::{ArgumentDecoder, ArgumentEncoder}; 2 | use candid::Principal; 3 | use cargo_metadata::MetadataCommand; 4 | use pocket_ic::common::rest::RawEffectivePrincipal; 5 | use pocket_ic::{call_candid, PocketIc, PocketIcBuilder, RejectResponse}; 6 | use std::path::PathBuf; 7 | use std::process::{Command, Stdio}; 8 | use std::sync::Once; 9 | 10 | /// Builds a canister with the specified name from the current 11 | /// package and returns the WebAssembly module. 12 | pub fn cargo_build_canister(bin_name: &str) -> Vec { 13 | static LOG_INIT: Once = Once::new(); 14 | LOG_INIT.call_once(env_logger::init); 15 | let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 16 | 17 | let cargo_toml_path = dir.join("Cargo.toml"); 18 | 19 | let target_dir = MetadataCommand::new() 20 | .manifest_path(&cargo_toml_path) 21 | .no_deps() 22 | .exec() 23 | .expect("failed to run cargo metadata") 24 | .target_directory; 25 | 26 | // We use a different target path to stop the native cargo build 27 | // cache being invalidated every time we run this function 28 | let wasm_target_dir = target_dir.join("canister-build"); 29 | 30 | let mut cmd = Command::new("cargo"); 31 | let target = match std::env::var("WASM64") { 32 | Ok(_) => { 33 | cmd.args([ 34 | "+nightly-2025-05-09", // 1.88.0 35 | "build", 36 | "-Z", 37 | "build-std=std,panic_abort", 38 | "--target", 39 | "wasm64-unknown-unknown", 40 | ]); 41 | "wasm64-unknown-unknown" 42 | } 43 | Err(_) => { 44 | cmd.args(["build", "--target", "wasm32-unknown-unknown"]); 45 | "wasm32-unknown-unknown" 46 | } 47 | }; 48 | 49 | let cmd = cmd 50 | .args([ 51 | "--bin", 52 | bin_name, 53 | "--profile", 54 | "canister-release", 55 | "--manifest-path", 56 | &cargo_toml_path.to_string_lossy(), 57 | "--target-dir", 58 | wasm_target_dir.as_ref(), 59 | ]) 60 | .stdout(Stdio::inherit()) 61 | .stderr(Stdio::inherit()); 62 | 63 | let output = cmd.output().expect("failed to locate cargo"); 64 | assert!(output.status.success(), "failed to compile the wasm binary"); 65 | 66 | let wasm_path = wasm_target_dir 67 | .join(target) 68 | .join("canister-release") 69 | .join(bin_name) 70 | .with_extension("wasm"); 71 | 72 | std::fs::read(&wasm_path).unwrap_or_else(|e| { 73 | panic!( 74 | "failed to read compiled Wasm file from {:?}: {}", 75 | &wasm_path, e 76 | ) 77 | }) 78 | } 79 | 80 | // The linter complains "function `update` is never used" 81 | // because not EVERY test uses this function. 82 | pub fn update( 83 | env: &PocketIc, 84 | canister_id: Principal, 85 | method: &str, 86 | input: Input, 87 | ) -> Result 88 | where 89 | Input: ArgumentEncoder, 90 | Output: for<'a> ArgumentDecoder<'a>, 91 | { 92 | call_candid(env, canister_id, RawEffectivePrincipal::None, method, input) 93 | } 94 | 95 | /// Creates a PocketIcBuilder with the base configuration for e2e tests. 96 | /// 97 | /// The PocketIc server binary is cached for reuse. 98 | pub fn pic_base() -> PocketIcBuilder { 99 | let pocket_ic_server = check_pocket_ic_server(); 100 | PocketIcBuilder::new() 101 | .with_server_binary(pocket_ic_server) 102 | .with_application_subnet() 103 | } 104 | 105 | fn check_pocket_ic_server() -> PathBuf { 106 | let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 107 | let cargo_toml_path = dir.join("Cargo.toml"); 108 | let metadata = MetadataCommand::new() 109 | .manifest_path(&cargo_toml_path) 110 | .exec() 111 | .expect("failed to run cargo metadata"); 112 | let e2e_tests_package = metadata 113 | .packages 114 | .iter() 115 | .find(|m| m.name.as_ref() == "ic-cdk-e2e-tests") 116 | .expect("ic-cdk-e2e-tests not found in Cargo.toml"); 117 | let pocket_ic_tag = e2e_tests_package 118 | .dependencies 119 | .iter() 120 | .find(|d| d.name == "pocket-ic") 121 | .expect("pocket-ic not found in Cargo.toml") 122 | .source 123 | .as_ref() 124 | .expect("pocket-ic source not found in Cargo.toml") 125 | .repr 126 | .split_once("tag=") 127 | .expect("`tag=` not found in pocket-ic source") 128 | .1; 129 | let target_dir = metadata.target_directory; 130 | let artifact_dir = target_dir.join("e2e-tests-artifacts"); 131 | let tag_path = artifact_dir.join("pocket-ic-tag"); 132 | let server_binary_path = artifact_dir.join("pocket-ic"); 133 | if let Ok(tag) = std::fs::read_to_string(&tag_path) { 134 | if tag == pocket_ic_tag && server_binary_path.exists() { 135 | return server_binary_path.into(); 136 | } 137 | } 138 | panic!("pocket-ic server not found or tag mismatch, please run `scripts/download_pocket_ic_server.sh` in the project root"); 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use super::*; 144 | 145 | #[test] 146 | fn test_pocket_ic() { 147 | let _pic = pic_base(); 148 | } 149 | 150 | #[test] 151 | fn test_update() { 152 | let pic = pic_base().build(); 153 | let canister_id = pic.create_canister(); 154 | pic.add_cycles(canister_id, 2_000_000_000_000); 155 | pic.install_canister( 156 | canister_id, 157 | b"\x00asm\x01\x00\x00\x00".to_vec(), 158 | vec![], 159 | None, 160 | ); 161 | assert!(update::<(), ()>(&pic, canister_id, "insert", ()).is_err()); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /e2e-tests/tests/timers.rs: -------------------------------------------------------------------------------- 1 | use pocket_ic::{query_candid, PocketIc}; 2 | use std::time::Duration; 3 | 4 | mod test_utilities; 5 | use test_utilities::{cargo_build_canister, pic_base, update}; 6 | 7 | #[test] 8 | fn test_timers() { 9 | let wasm = cargo_build_canister("timers"); 10 | let pic = pic_base().build(); 11 | let canister_id = pic.create_canister(); 12 | pic.add_cycles(canister_id, 2_000_000_000_000); 13 | pic.install_canister(canister_id, wasm, vec![], None); 14 | 15 | update::<(), ()>(&pic, canister_id, "schedule", ()).expect("Failed to call schedule"); 16 | advance_seconds(&pic, 5); 17 | 18 | update::<_, ()>(&pic, canister_id, "schedule_long", ()).expect("Failed to call schedule_long"); 19 | advance_seconds(&pic, 5); 20 | update::<_, ()>(&pic, canister_id, "cancel_long", ()).expect("Failed to call cancel_long"); 21 | advance_seconds(&pic, 5); 22 | update::<_, ()>(&pic, canister_id, "start_repeating", ()) 23 | .expect("Failed to call start_repeating"); 24 | advance_seconds(&pic, 3); 25 | update::<_, ()>(&pic, canister_id, "stop_repeating", ()) 26 | .expect("Failed to call stop_repeating"); 27 | advance_seconds(&pic, 2); 28 | 29 | let (events,): (Vec,) = 30 | query_candid(&pic, canister_id, "get_events", ()).expect("Failed to call get_events"); 31 | assert_eq!( 32 | events[..], 33 | ["1", "2", "3", "4", "repeat", "repeat", "repeat"] 34 | ); 35 | } 36 | 37 | #[test] 38 | fn test_timers_can_cancel_themselves() { 39 | let pic = pic_base().build(); 40 | let wasm = cargo_build_canister("timers"); 41 | let canister_id = pic.create_canister(); 42 | pic.add_cycles(canister_id, 2_000_000_000_000); 43 | pic.install_canister(canister_id, wasm, vec![], None); 44 | 45 | update::<_, ()>(&pic, canister_id, "set_self_cancelling_timer", ()) 46 | .expect("Failed to call set_self_cancelling_timer"); 47 | update::<_, ()>(&pic, canister_id, "set_self_cancelling_periodic_timer", ()) 48 | .expect("Failed to call set_self_cancelling_periodic_timer"); 49 | 50 | advance_seconds(&pic, 1); 51 | 52 | let (events,): (Vec,) = 53 | query_candid(&pic, canister_id, "get_events", ()).expect("Failed to call get_events"); 54 | assert_eq!( 55 | events, 56 | ["timer cancelled self", "periodic timer cancelled self"] 57 | ); 58 | } 59 | 60 | #[test] 61 | fn test_scheduling_many_timers() { 62 | let wasm = cargo_build_canister("timers"); 63 | // Must be more than the queue limit (500) 64 | let timers_to_schedule = 1_000; 65 | let pic = pic_base().build(); 66 | let canister_id = pic.create_canister(); 67 | pic.add_cycles(canister_id, 100_000_000_000_000u128); 68 | pic.install_canister(canister_id, wasm, vec![], None); 69 | 70 | let () = update( 71 | &pic, 72 | canister_id, 73 | "schedule_n_timers", 74 | (timers_to_schedule,), 75 | ) 76 | .expect("Error calling schedule_n_timers"); 77 | 78 | // Up to 20 timers will be executed per round 79 | // Be conservative that advance 2 times the minimum number of rounds 80 | const TIMERS_PER_ROUND: u32 = 20; 81 | advance_seconds(&pic, 2 * timers_to_schedule / TIMERS_PER_ROUND); 82 | 83 | let (executed_timers,): (u32,) = query_candid(&pic, canister_id, "executed_timers", ()) 84 | .expect("Error querying executed_timers"); 85 | 86 | assert_eq!(timers_to_schedule, executed_timers); 87 | } 88 | 89 | fn advance_seconds(pic: &PocketIc, seconds: u32) { 90 | for _ in 0..seconds { 91 | pic.advance_time(Duration::from_secs(1)); 92 | pic.tick(); 93 | } 94 | } 95 | 96 | #[test] 97 | fn test_set_global_timers() { 98 | let wasm = cargo_build_canister("timers"); 99 | let pic = pic_base().build(); 100 | 101 | let canister_id = pic.create_canister(); 102 | pic.add_cycles(canister_id, 2_000_000_000_000); 103 | pic.install_canister(canister_id, wasm, vec![], None); 104 | 105 | // Set a 9s timer at t0, it expires at t1 = t0 + 9s 106 | let t0 = pic.get_time().as_nanos_since_unix_epoch(); 107 | let t1 = t0 + 9_000_000_000; 108 | update::<_, ()>(&pic, canister_id, "schedule_long", ()).expect("Failed to call schedule_long"); 109 | 110 | // 5 seconds later, the 9s timer is still active 111 | advance_seconds(&pic, 5); 112 | 113 | // Set the expiration time of the timer to t2 = t1 + 5s 114 | let t2 = t1 + 5_000_000_000; 115 | let (previous,) = 116 | update::<(u64,), (u64,)>(&pic, canister_id, "global_timer_set", (t2,)).unwrap(); 117 | assert!(previous.abs_diff(t1) < 2); // time error no more than 1 nanosecond 118 | 119 | // Deactivate the timer 120 | let (previous,) = 121 | update::<(u64,), (u64,)>(&pic, canister_id, "global_timer_set", (0,)).unwrap(); 122 | assert!(previous.abs_diff(t2) < 2); // time error no more than 1 nanosecond 123 | } 124 | 125 | #[test] 126 | fn test_async_timers() { 127 | let wasm = cargo_build_canister("timers"); 128 | let pic = pic_base().build(); 129 | 130 | let canister_id = pic.create_canister(); 131 | pic.add_cycles(canister_id, 2_000_000_000_000); 132 | pic.install_canister(canister_id, wasm, vec![], None); 133 | 134 | update::<(), ()>(&pic, canister_id, "async_await", ()).unwrap(); 135 | advance_seconds(&pic, 5); 136 | 137 | let (events,): (Vec,) = 138 | query_candid(&pic, canister_id, "get_events", ()).expect("Failed to call get_events"); 139 | assert_eq!(events.len(), 8); 140 | assert_eq!(events[..4], ["0", "1", "method outer", "2",]); 141 | assert_eq!( 142 | events[4..] 143 | .iter() 144 | .filter(|e| *e == "method spawned") 145 | .count(), 146 | 1 147 | ); 148 | assert_eq!( 149 | events[4..] 150 | .iter() 151 | .filter(|e| *e == "method concurrent") 152 | .count(), 153 | 3 154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [unreleased] 8 | 9 | ### New API Design 10 | 11 | The `ic-cdk-bindgen` crate introduces a completely redesigned API to integrate seamlessly with `ic-cdk` v0.18 and later: 12 | - The new API centers around the `Config` struct, requiring explicit specification of canister name and candid path. 13 | - The bindgen now supports two modes: 14 | - `static_callee`: The canister ID is known at compile time. 15 | - `dynamic_callee`: The canister ID will be fetched via ICP environment variables at runtime. 16 | - The "Type Selector" config can be set to customize how Candid types are translated to Rust types. 17 | - Removed implicit handling of `dfx` environment variables. See the "Use with `dfx`" section in the crate documentation for more info. 18 | 19 | ## [0.1.3] - 2024-02-27 20 | 21 | ### Added 22 | 23 | - Resolve CANISTER_CANDID_PATH and CANISTER_ID from standardized environment variables (uppercase canister names). (#467) 24 | - The support for legacy (non-uppercase) env vars is kept. 25 | - It will be removed in next major release (v0.2). 26 | 27 | ## [0.1.2] - 2023-11-23 28 | 29 | ### Changed 30 | 31 | - Change `candid` dependency to the new `candid_parser` library. (#448) 32 | More details here: https://github.com/dfinity/candid/blob/master/Changelog.md#2023-11-16-rust-0100 33 | 34 | ## [0.1.1] - 2023-09-18 35 | 36 | ### Changed 37 | 38 | - Update `candid` dependency to 0.9.6 which change the Rust bindings. (#424) 39 | 40 | ## [0.1.0] - 2023-07-13 41 | 42 | ### Added 43 | 44 | - First release. (#416) 45 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-bindgen" 3 | version = "0.2.0-alpha.2" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Internet Computer Binding Generator." 10 | readme = "README.md" 11 | categories = ["api-bindings", "development-tools::ffi"] 12 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 13 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 14 | 15 | [dependencies] 16 | candid.workspace = true 17 | candid_parser.workspace = true 18 | thiserror.workspace = true 19 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-cdk-bindgen/README.md: -------------------------------------------------------------------------------- 1 | # ic-cdk-bindgen 2 | 3 | Bindings generator from Candid to Rust for `ic-cdk`. 4 | 5 | `ic-cdk-bindgen` is designed to be used for build-time code generation as part of a Cargo build-script. 6 | 7 | ## Example 8 | 9 | The goal is to conveniently make inter-canister calls to the canister `callee`. 10 | 11 | First, add `ic-cdk-bindgen` as a build dependency to `Cargo.toml`. 12 | 13 | ```bash 14 | cargo add --build ic-cdk-bindgen 15 | ``` 16 | 17 | Next, add the Candid interface file of the `callee` canister (e.g., at `candid/callee.did`). 18 | 19 | To generate Rust code from `callee.did`, we use `ic-cdk-bindgen` in the crate's `build.rs` build-script : 20 | 21 | ```rust,no_run 22 | // build.rs 23 | fn main() { 24 | let CALLEE_CANISTER_ID : candid::Principal = todo!(); 25 | ic_cdk_bindgen::Config::new("callee", "candid/callee.did") 26 | .static_callee(CALLEE_CANISTER_ID) 27 | .generate(); 28 | } 29 | ``` 30 | 31 | Finally, in the canister source code (e.g. `lib.rs`, `main.rs`), include the generated code: 32 | 33 | ```rust,ignore 34 | #[allow(dead_code, unused_imports)] 35 | mod callee { 36 | include!(concat!(env!("OUT_DIR"), "/callee.rs")); 37 | } 38 | 39 | #[ic_cdk::update] 40 | async fn invoke_callee() { 41 | // In modern IDE/editors like VSCode, you can often use "Go to Definition" or similar features 42 | // to quickly navigate to the generated bindings. 43 | let _result = callee::some_method().await; 44 | } 45 | ``` 46 | 47 | ## Dynamic Callee Mode 48 | 49 | The example above demonstrates the "Static callee" mode, where the canister ID is known at compile time. 50 | In this mode, the generated code includes the hardcoded canister ID, making it suitable for deployments 51 | where the canister ID is fixed and known ahead of time. 52 | 53 | The ICP Environment Variables feature enables a workflow where the canister ID can be set via 54 | environment variables, allowing for more flexible deployments. The "Dynamic callee" mode is designed for this workflow. 55 | 56 | For example, the `icp-cli` CLI tool sets an Env Var if there is a canister named `callee` in the project: 57 | - name: `ICP_CANISTER_ID:callee` 58 | - value: The text representation of the Principal 59 | 60 | Just apply a one line change in the `build.rs` script: 61 | 62 | ```rust,no_run 63 | // build.rs 64 | fn main() { 65 | ic_cdk_bindgen::Config::new("callee", "candid/callee.did") 66 | .dynamic_callee("ICP_CANISTER_ID:callee") // <--- Change made here 67 | .generate(); 68 | } 69 | ``` 70 | 71 | Then the generated code will use the canister ID from the environment variable at runtime. 72 | 73 | ## Type Selector Config 74 | 75 | The `candid` project specifies a "Type Selector" configuration language that controls the details of Rust code generation. 76 | You can use type selectors to customize how Candid types are translated to Rust types. 77 | 78 | First, create a TOML file for your canister (e.g., `callee.toml`) with a `rust` table at the top level. For example: 79 | 80 | ```toml 81 | # callee.toml 82 | [rust] 83 | visibility = "pub(crate)" 84 | attributes = "#[derive(CandidType, Deserialize, Clone, Debug)]" 85 | blob.use_type = "Vec" 86 | change_details.variant.creation.name = "CreationRecord" 87 | ``` 88 | 89 | Then, add the type selector configuration to your build script using `set_type_selector_config`: 90 | 91 | ```rust,no_run 92 | // build.rs 93 | fn main() { 94 | ic_cdk_bindgen::Config::new("callee", "candid/callee.did") 95 | .set_type_selector_config("path/to/callee.toml") 96 | .dynamic_callee("ICP_CANISTER_ID:callee") 97 | .generate(); 98 | } 99 | ``` 100 | 101 | The updated bindings can be seen by using the "Go to definition" or similar features from your IDE/editors. 102 | 103 | For more details on type selector syntax and capabilities, refer to the [specification](https://github.com/dfinity/candid/blob/master/spec/Type-selector.md#rust-binding-configuration). 104 | 105 | 106 | ## Use with `dfx` 107 | 108 | The previous version of `ic-cdk-bindgen` was designed to only work with `dfx` CLI tool. 109 | It relied on the `dfx` to set up some environment variables (note: this is the general UNIX environment variable), 110 | so that bindgen could learn the path to the Candid file and the canister ID at build time. 111 | Such `dfx` specific handling is removed in the updated version of `ic-cdk-bindgen`. 112 | 113 | To use the updated `ic-cdk-bindgen` with `dfx`, it is recommended to handle the `dfx` environment variables manually in your `build.rs` script. 114 | 115 | ```rust,no_run 116 | // build.rs 117 | fn main() { 118 | let name = "callee"; 119 | let normalized_name = name.replace('-', "_").to_uppercase(); 120 | let canister_id_var_name = format!("CANISTER_ID_{}", normalized_name); 121 | let canister_id_str = std::env::var(&canister_id_var_name).unwrap(); 122 | let canister_id = candid::Principal::from_text(&canister_id_str).unwrap(); 123 | let candid_path_var_name = format!("CANISTER_CANDID_PATH_{}", normalized_name); 124 | let candid_path = std::env::var(&candid_path_var_name).unwrap(); 125 | ic_cdk_bindgen::Config::new(name, candid_path) 126 | .static_callee(canister_id) 127 | .generate(); 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/src/templates/dynamic_callee.hbs: -------------------------------------------------------------------------------- 1 | use {{candid_crate}}::{CandidType, Deserialize, Principal}; 2 | use ic_cdk::call::{Call, CallResult}; 3 | // START -- Types 4 | {{type_defs}} 5 | // END -- Types 6 | /// Get the canister ID via the environment variable `{{env_var_name}}`. 7 | #[inline] 8 | pub fn __get_canister_id() -> Principal { 9 | if !::ic_cdk::api::env_var_name_exists("{{env_var_name}}") { 10 | panic!("env var `{{env_var_name}}` is not set. Canister controller can set it using tools like icp-cli."); 11 | } 12 | let canister_id_str = ::ic_cdk::api::env_var_value("{{env_var_name}}"); 13 | Principal::from_text(&canister_id_str).unwrap_or_else(|e| { 14 | panic!("failed to parse Principal from env var `{{env_var_name}}`, value `{}`: {}", canister_id_str, e) 15 | }) 16 | } 17 | // START -- Methods 18 | {{#if methods}} 19 | {{#each methods}} 20 | {{#each this.docs}} 21 | {{../../doc_comment_prefix}}{{this}} 22 | {{/each}} 23 | pub async fn {{this.name}}({{#each this.args}}{{this.0}}: &{{this.1}}, {{/each}}) -> CallResult<{{#if (eq (len this.rets) 1)}}{{this.rets.0}}{{else}}({{#each this.rets}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}){{/if}}> { 24 | Ok(Call::bounded_wait(__get_canister_id(), "{{escape_debug this.original_name}}"){{#if (eq (len this.args) 0)}}{{else}}{{#if (eq (len this.args) 1)}}.with_arg({{this.args.0.0}}){{else}}.with_args(&({{#each this.args}}{{this.0}}{{#unless @last}}, {{/unless}}{{/each}})){{/if}}{{/if}}.await?.candid()?) 25 | } 26 | {{/each}} 27 | {{/if}} 28 | // END -- Methods 29 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/src/templates/static_callee.hbs: -------------------------------------------------------------------------------- 1 | use {{candid_crate}}::{CandidType, Deserialize, Principal}; 2 | use ic_cdk::call::{Call, CallResult}; 3 | // START -- Types 4 | {{type_defs}} 5 | // END -- Types 6 | /// Canister ID: `{{canister_id}}` 7 | pub const CANISTER_ID : Principal = Principal::from_slice(&[{{principal_slice canister_id}}]); 8 | // START -- Methods 9 | {{#if methods}} 10 | {{#each methods}} 11 | {{#each this.docs}} 12 | {{../../doc_comment_prefix}}{{this}} 13 | {{/each}} 14 | pub async fn {{this.name}}({{#each this.args}}{{this.0}}: &{{this.1}}, {{/each}}) -> CallResult<{{#if (eq (len this.rets) 1)}}{{this.rets.0}}{{else}}({{#each this.rets}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}){{/if}}> { 15 | Ok(Call::bounded_wait(CANISTER_ID, "{{escape_debug this.original_name}}"){{#if (eq (len this.args) 0)}}{{else}}{{#if (eq (len this.args) 1)}}.with_arg({{this.args.0.0}}){{else}}.with_args(&({{#each this.args}}{{this.0}}{{#unless @last}}, {{/unless}}{{/each}})){{/if}}{{/if}}.await?.candid()?) 16 | } 17 | {{/each}} 18 | {{/if}} 19 | // END -- Methods 20 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/src/templates/types_only.hbs: -------------------------------------------------------------------------------- 1 | use {{candid_crate}}::{CandidType, Deserialize, Principal}; 2 | use ic_cdk::call::{Call, CallResult}; 3 | // START -- Types 4 | {{type_defs}} 5 | // END -- Types 6 | -------------------------------------------------------------------------------- /ic-cdk-executor/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ## [2.0.0-beta.1] - 2025-10-09 11 | 12 | ### Changed 13 | 14 | - Breaking: The API has been significantly revised. 15 | - `spawn` has been replaced with `spawn_local` and `spawn_migratory`. Local tasks are cancelled when the method ends. 16 | - Contexts are now 'tracking', meaning that they know when canister methods start and end. Calls to `ic0.call_*` must be accompanied by `extend_current_method_context`; `in_callback_executor_context_for` takes this value. 17 | - Cancellation is no longer done per-task with the waker, but per-method with the function `cancel_all_tasks_attached_to_current_method`. 18 | 19 | ## [1.0.2] - 2025-08-18 20 | 21 | ### Changed 22 | 23 | - Moved the panic handler logic into the executor. 24 | 25 | ### Fixed 26 | 27 | - Removed a panic from the case where a callback context closure doesn't do anything. The only way this usually occurs is if you upgrade a canister without stopping it first. A message has been added warning you not to do this. 28 | 29 | ## [1.0.1] - 2025-06-30 30 | 31 | ### Changed 32 | 33 | - Added a check against spawning tasks during trap recovery. 34 | - Added a check against invoking wakers outside an executor context. 35 | 36 | ### Fixed 37 | 38 | - Removed a panic when a dangling waker is invoked. 39 | 40 | ## [1.0.0] - 2025-05-13 41 | 42 | This release is a copy-paste of the executor from ic-cdk 0.18.1. 43 | 44 | ## [0.1.0] - 2025-05-13 45 | 46 | This release is a copy-paste of the executor from ic-cdk 0.17.1. 47 | -------------------------------------------------------------------------------- /ic-cdk-executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-executor" 3 | version = "2.0.0-beta.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | description = "Async executor for `ic-cdk`" 10 | categories = ["asynchronous", "rust-patterns"] 11 | keywords = ["internet-computer", "dfinity", "canister", "cdk"] 12 | # DO NOT change this field, not even to update the link, unless you are updating every existing version of the package. 13 | # Update the link by updating the links-pin tag. 14 | links = "ic-cdk async executor, see https://github.com/dfinity/cdk-rs/blob/links-pin/TROUBLESHOOTING.md" 15 | 16 | [dependencies] 17 | ic0.workspace = true 18 | slotmap.workspace = true 19 | smallvec = "1.15.1" 20 | 21 | [package.metadata.docs.rs] 22 | targets = ["wasm32-unknown-unknown"] 23 | -------------------------------------------------------------------------------- /ic-cdk-executor/README.md: -------------------------------------------------------------------------------- 1 | # `ic-cdk-executor` 2 | 3 | An async executor for [`ic-cdk`](https://docs.rs/ic-cdk). Most users should not use this crate directly. It is useful primarily for those who are writing their own CDK or a runtime host for non-Rust languages. 4 | 5 | ## Contexts 6 | 7 | The expected boilerplate for a canister method or other entrypoint (*not* including callbacks) looks like this: 8 | 9 | ```rust 10 | pub extern "C" fn function() { 11 | in_tracking_executor_context(|| { 12 | // method goes here 13 | }); 14 | } 15 | ``` 16 | 17 | The `in_tracking_executor_context` function permits you to call `spawn_*` functions. As little code as possible 18 | should exist outside the block, because `in_tracking_executor_context` additionally sets up the panic handler. 19 | 20 | The above applies to update contexts. Query contexts, including `inspect_message`, should use 21 | `in_tracking_query_executor_context`. 22 | 23 | The expected boilerplate for an inter-canister call callback looks like this: 24 | 25 | ```rust 26 | unsafe extern "C" fn callback(env: usize) { 27 | let method = unpack_env(env); 28 | in_callback_executor_context_for(method, || { 29 | // wake the call future 30 | }); 31 | } 32 | unsafe extern "C" fn cleanup(env: usize) { 33 | let method = unpack_env(env); 34 | in_trap_recovery_context_for(method, || { 35 | cancel_all_tasks_attached_to_current_method(); 36 | }); 37 | } 38 | ``` 39 | 40 | In async contexts, all scheduled tasks are run *after* the closure passed to the context function 41 | returns, but *before* the context function itself returns. 42 | 43 | The `method` parameter must be retrieved *before* making inter-canister calls via the `extend_current_method_context` 44 | function. Calling this function from the callback instead will trap. 45 | 46 | ## Protection 47 | 48 | Tasks can be either *protected* or *migratory*. Protected tasks are attached to the method that spawned them, 49 | when awoken will not resume until that method continues, and will be canceled if the method returns before they complete. 50 | Migratory tasks are not attached to any method, and will resume in whatever method wakes them. 51 | -------------------------------------------------------------------------------- /ic-cdk-executor/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // dummy build script, required by `package.links` 3 | } 4 | -------------------------------------------------------------------------------- /ic-cdk-executor/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An async executor for [`ic-cdk`](https://docs.rs/ic-cdk). Most users should not use this crate directly. It is useful 2 | //! primarily for those who are writing their own CDK or a runtime host for non-Rust languages. 3 | //! 4 | //! ## Contexts 5 | //! 6 | //! The expected boilerplate for a canister method or other entrypoint (*not* including callbacks) looks like this: 7 | //! 8 | //! ``` 9 | //! # use ic_cdk_executor::*; 10 | //! pub extern "C" fn function() { 11 | //! in_tracking_executor_context(|| { 12 | //! // method goes here 13 | //! }); 14 | //! } 15 | //! ``` 16 | //! 17 | //! The [`in_tracking_executor_context`] function permits you to call `spawn_*` functions. As little code as possible 18 | //! should exist outside the block, because [`in_tracking_executor_context`] additionally sets up the panic handler. 19 | //! 20 | //! The above applies to update contexts. Query contexts, including `canister_inspect_message`, should use 21 | //! [`in_tracking_query_executor_context`]. 22 | //! 23 | //! The expected boilerplate for an inter-canister call callback looks like this: 24 | //! 25 | //! ``` 26 | //! # use ic_cdk_executor::*; 27 | //! # fn unpack_env(env: usize) -> MethodHandle { unimplemented!() } 28 | //! unsafe extern "C" fn callback(env: usize) { 29 | //! let method = unpack_env(env); 30 | //! in_callback_executor_context_for(method, || { 31 | //! // wake the call future 32 | //! }); 33 | //! } 34 | //! unsafe extern "C" fn cleanup(env: usize) { 35 | //! let method = unpack_env(env); 36 | //! in_trap_recovery_context_for(method, || { 37 | //! cancel_all_tasks_attached_to_current_method(); 38 | //! }); 39 | //! } 40 | //! ``` 41 | //! 42 | //! In async contexts, all scheduled tasks are run *after* the closure passed to the context function 43 | //! returns, but *before* the context function itself returns. 44 | //! 45 | //! The `method` parameter must be retrieved *before* making inter-canister calls via the [`extend_current_method_context`] 46 | //! function. Calling this function from the callback instead will trap. 47 | //! 48 | //! ## Protection 49 | //! 50 | //! Tasks can be either *protected* or *migratory*. Protected tasks are attached to the method that spawned them, 51 | //! when awoken will not resume until that method continues, and will be canceled if the method returns before they complete. 52 | //! Migratory tasks are not attached to any method, and will resume in whatever method wakes them. 53 | mod machinery; 54 | 55 | #[doc(inline)] 56 | pub use machinery::{ 57 | MethodHandle, TaskHandle, cancel_all_tasks_attached_to_current_method, cancel_task, 58 | extend_current_method_context, in_callback_executor_context_for, in_tracking_executor_context, 59 | in_tracking_query_executor_context, in_trap_recovery_context_for, is_recovering_from_trap, 60 | spawn_migratory, spawn_protected, 61 | }; 62 | -------------------------------------------------------------------------------- /ic-cdk-macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file will no longer be updated. 4 | 5 | Please check [`ic-cdk` changelog](../ic-cdk/CHANGELOG.md). 6 | 7 | --- 8 | 9 | All notable changes to this project will be documented in this file. 10 | 11 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 12 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 13 | 14 | ### Fixed 15 | 16 | - `cargo build` should no longer give a confusing linkage error on Linux. 17 | 18 | ## [0.13.2] - 2024-04-08 19 | 20 | ### Changed 21 | 22 | - `ic-cdk-macros` will have the same version as the `ic-cdk`. 23 | 24 | ## [0.9.0] - 2024-03-01 25 | 26 | ### Fixed 27 | 28 | - The change in yanked version v0.8.5 contains breaking change. 29 | 30 | ## [0.8.5] - 2024-03-01 (yanked) 31 | 32 | ### Added 33 | 34 | - Allow setting decoding quota for canister entry points and inter-canister calls. (#465) 35 | 36 | ## [0.8.4] - 2024-01-12 37 | 38 | ### Fixed 39 | 40 | - The README file is now more informative and used as the front page of the doc site. 41 | 42 | ## [0.8.3] - 2023-12-13 43 | 44 | ### Added 45 | 46 | - `#[query(hidden = true)]`/`#[update(hidden = true)]` attribute to exclude exporting certain entry points in Candid generated by `export_candid!()`. (#451) 47 | 48 | ## [0.8.2] - 2023-11-23 49 | 50 | ### Changed 51 | 52 | - Upgrade `candid` to `0.10`. (#448) 53 | 54 | ## [0.8.1] - 2023-10-02 55 | 56 | ### Fixed 57 | 58 | - Macros no longer use global state in the names of functions. JetBrains IDEs should no longer produce spurious errors. (#430) 59 | 60 | ## [0.8.0] - 2023-09-18 61 | 62 | ### Changed 63 | 64 | - Remove `export_candid` feature. (#424) 65 | 66 | ### Fixed 67 | 68 | - Export composite_query to Candid. (#419) 69 | 70 | ## [0.7.1] - 2023-07-27 71 | 72 | ### Fixed 73 | 74 | - Only update/query macros can take guard function. (#417) 75 | 76 | ## [0.7.0] - 2023-07-13 77 | 78 | ### Added 79 | 80 | - `export_candid` macro. (#386) 81 | 82 | ### Changed 83 | 84 | - Remove `import` macro. (#390) 85 | 86 | ## [0.6.10] - 2023-03-01 87 | 88 | ### Changed 89 | 90 | - Update lint settings. 91 | -------------------------------------------------------------------------------- /ic-cdk-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-macros" 3 | version = "0.19.0-beta.3" # sync with ic-cdk 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Canister Developer Kit macros." 10 | homepage = "https://docs.rs/ic-cdk-macros" 11 | documentation = "https://docs.rs/ic-cdk-macros" 12 | readme = "README.md" 13 | categories = ["api-bindings", "data-structures", "no-std", "development-tools::ffi"] 14 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 15 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [dependencies] 21 | candid.workspace = true 22 | darling = "0.20.11" 23 | proc-macro2 = "1.0" 24 | quote.workspace = true 25 | syn = { workspace = true, features = ["fold", "full", "extra-traits"] } 26 | -------------------------------------------------------------------------------- /ic-cdk-macros/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-cdk-macros/README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://docs.rs/ic-cdk-macros/badge.svg)](https://docs.rs/ic-cdk-macros/) 2 | [![Crates.io](https://img.shields.io/crates/v/ic-cdk-macros.svg)](https://crates.io/crates/ic-cdk-macros) 3 | [![License](https://img.shields.io/crates/l/ic-cdk-macros.svg)](https://github.com/dfinity/cdk-rs/blob/main/LICENSE) 4 | [![Downloads](https://img.shields.io/crates/d/ic-cdk-macros.svg)](https://crates.io/crates/ic-cdk-macros) 5 | [![CI](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml) 6 | 7 | # ic-cdk-macros 8 | 9 | This crate contains a collection of procedural macros that are utilized within the `ic-cdk` crate. 10 | 11 | The macros are re-exported in `ic-cdk`, and you can find their documentation [there](https://docs.rs/ic-cdk/latest/ic_cdk). 12 | 13 | --- 14 | 15 | The macros fall into two categories: 16 | 17 | * To register functions as canister entry points 18 | * To export Candid definitions 19 | 20 | ## Register functions as canister entry points 21 | 22 | These macros are directly related to the [Internet Computer Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#entry-points). 23 | 24 | * [`init`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.init.html) 25 | * [`pre_upgrade`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.pre_upgrade.html) 26 | * [`post_upgrade`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.post_upgrade.html) 27 | * [`inspect_message`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.inspect_message.html) 28 | * [`heartbeat`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.heartbeat.html) 29 | * [`on_low_wasm_memory`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.on_low_wasm_memory.html) 30 | * [`update`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.update.html) 31 | * [`query`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.query.html) 32 | 33 | ## Export Candid definitions 34 | 35 | * [`export_candid`](https://docs.rs/ic-cdk/latest/ic_cdk/macro.export_candid.html) 36 | 37 | Check [Generating Candid files for Rust canisters](https://internetcomputer.org/docs/current/developer-docs/backend/candid/generating-candid/) for more details. 38 | -------------------------------------------------------------------------------- /ic-cdk-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn( 3 | elided_lifetimes_in_paths, 4 | missing_debug_implementations, 5 | unsafe_op_in_unsafe_fn, 6 | clippy::undocumented_unsafe_blocks, 7 | clippy::missing_safety_doc 8 | )] 9 | 10 | use proc_macro::TokenStream; 11 | use syn::Error; 12 | 13 | mod export; 14 | 15 | fn handle_debug_and_errors( 16 | cb: F, 17 | name: &str, 18 | attr: TokenStream, 19 | item: TokenStream, 20 | ) -> TokenStream 21 | where 22 | F: FnOnce( 23 | proc_macro2::TokenStream, 24 | proc_macro2::TokenStream, 25 | ) -> Result, 26 | { 27 | if std::env::var_os("IC_CDK_DEBUG").is_some() { 28 | eprintln!("--- IC_CDK_MACROS DEBUG ---"); 29 | eprintln!("{name}\n attr: {attr}\n item: {item}"); 30 | } 31 | 32 | let result = cb(attr.into(), item.into()); 33 | 34 | if std::env::var_os("IC_CDK_DEBUG").is_some() { 35 | eprintln!("--------- RESULT ---------"); 36 | if let Ok(ref stream) = result { 37 | eprintln!("{stream}"); 38 | } 39 | eprintln!("---------------------------"); 40 | } 41 | 42 | result.map_or_else(|e| e.to_compile_error().into(), Into::into) 43 | } 44 | 45 | #[proc_macro] 46 | pub fn export_candid(input: TokenStream) -> TokenStream { 47 | let input: proc_macro2::TokenStream = input.into(); 48 | quote::quote! { 49 | ::candid::export_service!(#input); 50 | 51 | #[unsafe(no_mangle)] 52 | pub fn get_candid_pointer() -> *mut std::os::raw::c_char { 53 | let c_string = std::ffi::CString::new(__export_service()).unwrap(); 54 | c_string.into_raw() 55 | } 56 | } 57 | .into() 58 | } 59 | 60 | #[proc_macro_attribute] 61 | pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream { 62 | handle_debug_and_errors(export::ic_query, "ic_query", attr, item) 63 | } 64 | 65 | #[proc_macro_attribute] 66 | pub fn update(attr: TokenStream, item: TokenStream) -> TokenStream { 67 | handle_debug_and_errors(export::ic_update, "ic_update", attr, item) 68 | } 69 | 70 | #[proc_macro_attribute] 71 | pub fn init(attr: TokenStream, item: TokenStream) -> TokenStream { 72 | handle_debug_and_errors(export::ic_init, "ic_init", attr, item) 73 | } 74 | 75 | #[proc_macro_attribute] 76 | pub fn pre_upgrade(attr: TokenStream, item: TokenStream) -> TokenStream { 77 | handle_debug_and_errors(export::ic_pre_upgrade, "ic_pre_upgrade", attr, item) 78 | } 79 | 80 | #[proc_macro_attribute] 81 | pub fn post_upgrade(attr: TokenStream, item: TokenStream) -> TokenStream { 82 | handle_debug_and_errors(export::ic_post_upgrade, "ic_post_upgrade", attr, item) 83 | } 84 | 85 | #[proc_macro_attribute] 86 | pub fn heartbeat(attr: TokenStream, item: TokenStream) -> TokenStream { 87 | handle_debug_and_errors(export::ic_heartbeat, "ic_heartbeat", attr, item) 88 | } 89 | 90 | #[proc_macro_attribute] 91 | pub fn inspect_message(attr: TokenStream, item: TokenStream) -> TokenStream { 92 | handle_debug_and_errors(export::ic_inspect_message, "ic_inspect_message", attr, item) 93 | } 94 | 95 | #[proc_macro_attribute] 96 | pub fn on_low_wasm_memory(attr: TokenStream, item: TokenStream) -> TokenStream { 97 | handle_debug_and_errors( 98 | export::ic_on_low_wasm_memory, 99 | "ic_on_low_wasm_memory", 100 | attr, 101 | item, 102 | ) 103 | } 104 | -------------------------------------------------------------------------------- /ic-cdk-timers/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [unreleased] 8 | 9 | ## [1.0.0-beta.1] - 2025-10-09 10 | 11 | - `ic-cdk-timers` no longer has a dependency on `ic-cdk` and no longer needs to be upgraded when `ic-cdk` is upgraded. 12 | - Breaking: Timer bodies have been changed from `FnOnce`/`FnMut` to `Future`/`AsyncFnMut`, so `spawn` is no longer required to enter an async context from a timer. For `set_timer`, `|| {}` should be changed to `async {}`, and for `set_timer_interval`, `|| {}` should be changed to `async || {}`. 13 | - If you have any immediate `spawn` calls, you can remove them and run the async code directly. (You do not have to.) 14 | 15 | ## [0.12.2] - 2025-06-25 16 | 17 | - Upgrade `ic0` to v1.0. 18 | 19 | ## [0.12.1] - 2025-06-17 20 | 21 | - Upgrade `ic0` to v0.25. 22 | 23 | ## [0.12.0] - 2025-04-22 24 | 25 | - Upgrade `ic-cdk` to v0.18. 26 | 27 | ## [0.11.0] - 2024-11-04 28 | 29 | ### Changed 30 | 31 | - Upgrade `ic-cdk` to v0.17. 32 | 33 | ## [0.10.0] - 2024-08-27 34 | 35 | ### Changed 36 | 37 | - Upgrade `ic-cdk` to v0.16. 38 | 39 | ## [0.9.0] - 2024-07-01 40 | 41 | ### Changed 42 | 43 | - Upgrade `ic-cdk` to v0.15. 44 | 45 | ## [0.8.0] - 2024-05-17 46 | 47 | ### Changed 48 | 49 | - Upgrade `ic-cdk` to v0.14. 50 | 51 | ## [0.7.0] - 2024-03-01 52 | 53 | ### Changed 54 | 55 | - Upgrade `ic-cdk` to v0.13. 56 | 57 | ## [0.6.0] - 2023-11-23 58 | 59 | ### Changed 60 | 61 | - Upgrade `ic-cdk` to v0.12. 62 | 63 | ## [0.5.0] - 2023-09-18 64 | 65 | ### Changed 66 | 67 | - Upgrade `ic-cdk` to v0.11. 68 | 69 | ## [0.4.0] - 2023-07-13 70 | 71 | ### Changed 72 | 73 | - Upgrade `ic-cdk` to v0.10. 74 | 75 | ## [0.3.0] - 2023-06-20 76 | 77 | ### Changed 78 | 79 | - Upgrade `ic-cdk` to v0.9. 80 | 81 | ## [0.2.0] - 2023-05-26 82 | 83 | ### Changed 84 | 85 | - Upgrade `ic-cdk` to v0.8. 86 | 87 | ## [0.1.2] - 2023-03-01 88 | 89 | ## [0.1.1] - 2023-02-22 90 | 91 | ### Fixed 92 | 93 | - Broken references to `ic_cdk::api::time`. 94 | 95 | ## [0.1.0] - 2023-02-03 96 | 97 | ### Added 98 | 99 | - Initial release of the `ic-cdk-timers` library. 100 | -------------------------------------------------------------------------------- /ic-cdk-timers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-timers" 3 | version = "1.0.0-beta.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Timers library for the Rust CDK." 10 | homepage = "https://docs.rs/ic-cdk" 11 | documentation = "https://docs.rs/ic-cdk-timers" 12 | readme = "README.md" 13 | categories = ["api-bindings", "data-structures", "no-std", "development-tools::ffi"] 14 | keywords = ["internet-computer", "dfinity", "canister", "cdk"] 15 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 16 | 17 | [dependencies] 18 | ic0.workspace = true 19 | ic-cdk-executor.workspace = true 20 | slotmap.workspace = true 21 | 22 | [dev-dependencies] 23 | ic-cdk.workspace = true 24 | 25 | [package.metadata.docs.rs] 26 | default-target = "wasm32-unknown-unknown" 27 | rustc-args = ["--cfg=docsrs"] 28 | -------------------------------------------------------------------------------- /ic-cdk-timers/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-cdk-timers/README.md: -------------------------------------------------------------------------------- 1 | Rust CDK Timers Library 2 | ======================= 3 | 4 | [![Documentation](https://docs.rs/ic-cdk-timers/badge.svg)](https://docs.rs/ic-cdk-timers/) 5 | [![Crates.io](https://img.shields.io/crates/v/ic-cdk-timers.svg)](https://crates.io/crates/ic-cdk-timers) 6 | [![License](https://img.shields.io/crates/l/ic-cdk-timers.svg)](https://github.com/dfinity/cdk-rs/blob/main/src/ic-cdk-timers/LICENSE) 7 | [![Downloads](https://img.shields.io/crates/d/ic-cdk-timers.svg)](https://crates.io/crates/ic-cdk-timers) 8 | [![CI](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml) 9 | 10 | This crate provides a library to schedule multiple and periodic tasks on the Internet Computer. 11 | 12 | Example 13 | ------- 14 | 15 | In `Cargo.toml`: 16 | 17 | ```toml 18 | [dependencies] 19 | ic-cdk-timers = "0.9.0" 20 | ``` 21 | 22 | To schedule a one-shot task to be executed 1s later: 23 | 24 | ```rust 25 | ic_cdk_timers::set_timer(Duration::from_secs(1), || ic_cdk::println!("Hello from the future!")); 26 | ``` 27 | 28 | References 29 | ---------- 30 | 31 | 1. Internet Computer Developer Guide: [Periodic Tasks and Timers](https://internetcomputer.org/docs/current/developer-docs/backend/periodic-tasks) 32 | 2. Example: [Periodic Tasks and Timers](https://github.com/dfinity/examples/tree/master/rust/periodic_tasks) (compares timers and heartbeats). 33 | -------------------------------------------------------------------------------- /ic-cdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk" 3 | version = "0.19.0-beta.3" # sync with ic-cdk-macros and the doc comment in README.md 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Canister Developer Kit for the Internet Computer." 10 | homepage = "https://docs.rs/ic-cdk" 11 | documentation = "https://docs.rs/ic-cdk" 12 | readme = "README.md" 13 | categories = ["api-bindings", "data-structures", "no-std", "development-tools::ffi"] 14 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 15 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 16 | 17 | [dependencies] 18 | candid.workspace = true 19 | ic0.workspace = true 20 | ic-cdk-executor.workspace = true 21 | # Pin ic-cdk-macros to a specific version. 22 | # This actually create a 1-to-1 mapping between ic-cdk and ic-cdk-macros. 23 | # Dependents won't accidentaly upgrading ic-cdk-macros only but not ic-cdk. 24 | # ic-cdk-macros is a hidden dependency, re-exported by ic-cdk. 25 | # It should not be included by users direcly. 26 | ic-cdk-macros = { path = "../ic-cdk-macros", version = "=0.19.0-beta.3" } 27 | ic-error-types = "0.2.0" 28 | ic-management-canister-types.workspace = true 29 | pin-project-lite = "0.2.16" 30 | serde.workspace = true 31 | serde_bytes.workspace = true 32 | slotmap.workspace = true 33 | thiserror.workspace = true 34 | 35 | [dev-dependencies] 36 | anyhow = "1" 37 | candid_parser.workspace = true 38 | futures = "0.3" 39 | rstest = "0.12.0" 40 | trybuild = "1.0" 41 | 42 | [features] 43 | transform-closure = [] 44 | 45 | [package.metadata.docs.rs] 46 | features = ["transform-closure"] 47 | default-target = "wasm32-unknown-unknown" 48 | rustdoc-args = ["--cfg=docsrs"] 49 | -------------------------------------------------------------------------------- /ic-cdk/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-cdk/README.md: -------------------------------------------------------------------------------- 1 | [![Documentation](https://docs.rs/ic-cdk/badge.svg)](https://docs.rs/ic-cdk/) 2 | [![Crates.io](https://img.shields.io/crates/v/ic-cdk.svg)](https://crates.io/crates/ic-cdk) 3 | [![License](https://img.shields.io/crates/l/ic-cdk.svg)](https://github.com/dfinity/cdk-rs/blob/main/LICENSE) 4 | [![Downloads](https://img.shields.io/crates/d/ic-cdk.svg)](https://crates.io/crates/ic-cdk) 5 | [![CI](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/dfinity/cdk-rs/actions/workflows/ci.yml) 6 | 7 | # ic-cdk 8 | 9 | Canister Developer Kit for the Internet Computer. 10 | 11 | ## Background 12 | 13 | On the Internet Computer, smart contracts come in the form of canisters which are WebAssembly modules. 14 | 15 | Canisters expose entry points which can be called both by other canisters and by parties external to the IC. 16 | 17 | This library aims to provide a Rust-ergonomic abstraction to implement Canister entry points. 18 | 19 | ## Getting Started 20 | 21 | In Cargo.toml: 22 | 23 | ```toml 24 | [lib] 25 | crate-type = ["cdylib"] 26 | 27 | [dependencies] 28 | ic-cdk = "0.18" 29 | candid = "0.10" # required if you want to define Candid data types 30 | ``` 31 | 32 | Then in Rust source code: 33 | 34 | ```rust 35 | #[ic_cdk::query] 36 | fn hello() -> String { 37 | "world".to_string() 38 | } 39 | ``` 40 | 41 | This will register a **query** entry point named `hello`. 42 | 43 | ## Compilation 44 | 45 | ### Stable Target: `wasm32-unknown-unknown` 46 | 47 | ```sh 48 | cargo build --target wasm32-unknown-unknown 49 | ``` 50 | 51 | ### Experimental Target: `wasm64-unknown-unknown` 52 | 53 | No changes to the source code are required. However, setting up the Rust toolchain for Wasm64 support requires some additional steps. 54 | 55 | 1. Install nightly toolchain: 56 | ```bash 57 | rustup toolchain install nightly 58 | ``` 59 | 2. Add rust-src component: 60 | ```bash 61 | rustup component add rust-src --toolchain nightly 62 | ``` 63 | 3. Build with necessary flags: 64 | ```bash 65 | cargo +nightly build -Z build-std=std,panic_abort --target wasm64-unknown-unknown 66 | ``` 67 | 68 | ## Macros 69 | 70 | This library re-exports macros defined in `ic-cdk-macros` crate. 71 | 72 | The macros fall into two categories: 73 | 74 | * To register functions as canister entry points 75 | * To export Candid definitions 76 | 77 | ### Register functions as canister entry points 78 | 79 | These macros are directly related to the [Internet Computer Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec#entry-points). 80 | 81 | * [`init`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.init.html) 82 | * [`pre_upgrade`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.pre_upgrade.html) 83 | * [`post_upgrade`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.post_upgrade.html) 84 | * [`inspect_message`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.inspect_message.html) 85 | * [`heartbeat`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.heartbeat.html) 86 | * [`on_low_wasm_memory`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.on_low_wasm_memory.html) 87 | * [`update`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.update.html) 88 | * [`query`](https://docs.rs/ic-cdk/latest/ic_cdk/attr.query.html) 89 | 90 | Canister entry points can be `async`. The CDK embeds an asynchronous executor. Unfortunately anything `tokio`-specific cannot be used. 91 | Use the [`spawn`](https://docs.rs/ic-cdk/latest/ic_cdk/futures/fn.spawn.html) function to run more asynchronous functions in 92 | the background. Panics can cause async tasks to cancel partway through; read the documentation for the 93 | [`futures`](https://docs.rs/ic-cdk/latest/ic_cdk/futures/index.html) module for more information. 94 | 95 | ### Export Candid definitions 96 | 97 | * [`export_candid`](https://docs.rs/ic-cdk/latest/ic_cdk/macro.export_candid.html) 98 | 99 | Check [Generating Candid files for Rust canisters](https://internetcomputer.org/docs/current/developer-docs/backend/candid/generating-candid/) for more details. 100 | 101 | ## More examples 102 | 103 | The [examples repository](https://github.com/dfinity/examples/tree/master/rust) offers numerous Rust examples demonstrating how to build functional Rust canisters. 104 | 105 | ## Manage Data Structures in Stable Memory 106 | 107 | For managing larger datasets and multiple data structures in stable memory, consider using the [`ic-stable-structures`](https://crates.io/crates/ic-stable-structures) crate. While the `ic_cdk::storage::{stable_save, stable_restore}` API is straightforward, it may not be efficient for larger datasets. The `ic-stable-structures` crate provides more scalable solutions for such scenarios. 108 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/bitcoin/mod.rs: -------------------------------------------------------------------------------- 1 | //! The IC Bitcoin API. 2 | //! 3 | //! Check [Bitcoin integration](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api) for more details. 4 | 5 | use crate::api::call::{CallResult, call_with_payment128}; 6 | use candid::Principal; 7 | 8 | mod types; 9 | pub use types::*; 10 | 11 | const GET_UTXO_MAINNET: u128 = 10_000_000_000; 12 | const GET_UTXO_TESTNET: u128 = 4_000_000_000; 13 | 14 | const GET_CURRENT_FEE_PERCENTILES_MAINNET: u128 = 100_000_000; 15 | const GET_CURRENT_FEE_PERCENTILES_TESTNET: u128 = 40_000_000; 16 | 17 | const GET_BALANCE_MAINNET: u128 = 100_000_000; 18 | const GET_BALANCE_TESTNET: u128 = 40_000_000; 19 | 20 | const SEND_TRANSACTION_SUBMISSION_MAINNET: u128 = 5_000_000_000; 21 | const SEND_TRANSACTION_SUBMISSION_TESTNET: u128 = 2_000_000_000; 22 | 23 | const SEND_TRANSACTION_PAYLOAD_MAINNET: u128 = 20_000_000; 24 | const SEND_TRANSACTION_PAYLOAD_TESTNET: u128 = 8_000_000; 25 | 26 | const GET_BLOCK_HEADERS_MAINNET: u128 = 4_000_000_000; 27 | const GET_BLOCK_HEADERS_TESTNET: u128 = 4_000_000_000; 28 | 29 | /// See [IC method `bitcoin_get_balance`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_balance). 30 | /// 31 | /// This call requires cycles payment. 32 | /// This method handles the cycles cost under the hood. 33 | /// Check [API fees & Pricing](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api-fees--pricing) for more details. 34 | pub async fn bitcoin_get_balance(arg: GetBalanceRequest) -> CallResult<(Satoshi,)> { 35 | let cycles = match arg.network { 36 | BitcoinNetwork::Mainnet => GET_BALANCE_MAINNET, 37 | BitcoinNetwork::Testnet => GET_BALANCE_TESTNET, 38 | BitcoinNetwork::Regtest => 0, 39 | }; 40 | call_with_payment128( 41 | Principal::management_canister(), 42 | "bitcoin_get_balance", 43 | (arg,), 44 | cycles, 45 | ) 46 | .await 47 | } 48 | 49 | /// See [IC method `bitcoin_get_utxos`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_utxos). 50 | /// 51 | /// This call requires cycles payment. 52 | /// This method handles the cycles cost under the hood. 53 | /// Check [API fees & Pricing](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api-fees--pricing) for more details. 54 | pub async fn bitcoin_get_utxos(arg: GetUtxosRequest) -> CallResult<(GetUtxosResponse,)> { 55 | let cycles = match arg.network { 56 | BitcoinNetwork::Mainnet => GET_UTXO_MAINNET, 57 | BitcoinNetwork::Testnet => GET_UTXO_TESTNET, 58 | BitcoinNetwork::Regtest => 0, 59 | }; 60 | call_with_payment128( 61 | Principal::management_canister(), 62 | "bitcoin_get_utxos", 63 | (arg,), 64 | cycles, 65 | ) 66 | .await 67 | } 68 | 69 | fn send_transaction_fee(arg: &SendTransactionRequest) -> u128 { 70 | let (submission, payload) = match arg.network { 71 | BitcoinNetwork::Mainnet => ( 72 | SEND_TRANSACTION_SUBMISSION_MAINNET, 73 | SEND_TRANSACTION_PAYLOAD_MAINNET, 74 | ), 75 | BitcoinNetwork::Testnet => ( 76 | SEND_TRANSACTION_SUBMISSION_TESTNET, 77 | SEND_TRANSACTION_PAYLOAD_TESTNET, 78 | ), 79 | BitcoinNetwork::Regtest => (0, 0), 80 | }; 81 | submission + payload * arg.transaction.len() as u128 82 | } 83 | 84 | /// See [IC method `bitcoin_send_transaction`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction). 85 | /// 86 | /// This call requires cycles payment. 87 | /// This method handles the cycles cost under the hood. 88 | /// Check [API fees & Pricing](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api-fees--pricing) for more details. 89 | pub async fn bitcoin_send_transaction(arg: SendTransactionRequest) -> CallResult<()> { 90 | let cycles = send_transaction_fee(&arg); 91 | call_with_payment128( 92 | Principal::management_canister(), 93 | "bitcoin_send_transaction", 94 | (arg,), 95 | cycles, 96 | ) 97 | .await 98 | } 99 | 100 | /// See [IC method `bitcoin_get_current_fee_percentiles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles). 101 | /// 102 | /// This call requires cycles payment. 103 | /// This method handles the cycles cost under the hood. 104 | /// Check [API fees & Pricing](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api-fees--pricing) for more details. 105 | pub async fn bitcoin_get_current_fee_percentiles( 106 | arg: GetCurrentFeePercentilesRequest, 107 | ) -> CallResult<(Vec,)> { 108 | let cycles = match arg.network { 109 | BitcoinNetwork::Mainnet => GET_CURRENT_FEE_PERCENTILES_MAINNET, 110 | BitcoinNetwork::Testnet => GET_CURRENT_FEE_PERCENTILES_TESTNET, 111 | BitcoinNetwork::Regtest => 0, 112 | }; 113 | call_with_payment128( 114 | Principal::management_canister(), 115 | "bitcoin_get_current_fee_percentiles", 116 | (arg,), 117 | cycles, 118 | ) 119 | .await 120 | } 121 | 122 | /// See [IC method `bitcoin_get_block_headers`](https://internetcomputer.org/docs/current/references/ic-interface-spec#ic-bitcoin_get_block_headers). 123 | /// 124 | /// This call requires cycles payment. 125 | /// This method handles the cycles cost under the hood. 126 | /// Check [API fees & Pricing](https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/bitcoin-how-it-works/#api-fees--pricing) for more details. 127 | pub async fn bitcoin_get_block_headers( 128 | arg: GetBlockHeadersRequest, 129 | ) -> CallResult<(GetBlockHeadersResponse,)> { 130 | let cycles = match arg.network { 131 | BitcoinNetwork::Mainnet => GET_BLOCK_HEADERS_MAINNET, 132 | BitcoinNetwork::Testnet => GET_BLOCK_HEADERS_TESTNET, 133 | BitcoinNetwork::Regtest => 0, 134 | }; 135 | call_with_payment128( 136 | Principal::management_canister(), 137 | "bitcoin_get_block_headers", 138 | (arg,), 139 | cycles, 140 | ) 141 | .await 142 | } 143 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/bitcoin/types.rs: -------------------------------------------------------------------------------- 1 | use candid::CandidType; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// 10^8 Satoshi = 1 Bitcoin. 5 | pub type Satoshi = u64; 6 | 7 | /// Bitcoin Network. 8 | #[derive( 9 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, 10 | )] 11 | pub enum BitcoinNetwork { 12 | /// Mainnet. 13 | #[serde(rename = "mainnet")] 14 | Mainnet, 15 | /// Testnet. 16 | #[serde(rename = "testnet")] 17 | Testnet, 18 | /// Regtest. 19 | /// 20 | /// This is only available when developing with local replica. 21 | #[serde(rename = "regtest")] 22 | Regtest, 23 | } 24 | 25 | impl Default for BitcoinNetwork { 26 | fn default() -> Self { 27 | Self::Regtest 28 | } 29 | } 30 | 31 | /// Bitcoin Address. 32 | pub type BitcoinAddress = String; 33 | 34 | /// Block Hash. 35 | pub type BlockHash = Vec; 36 | 37 | /// Element in the Response of [`bitcoin_get_current_fee_percentiles`](super::bitcoin_get_current_fee_percentiles). 38 | pub type MillisatoshiPerByte = u64; 39 | 40 | /// Identifier of [`Utxo`]. 41 | #[derive( 42 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 43 | )] 44 | pub struct Outpoint { 45 | /// Transaction Identifier. 46 | pub txid: Vec, 47 | /// A implicit index number. 48 | pub vout: u32, 49 | } 50 | 51 | /// Unspent transaction output (UTXO). 52 | #[derive( 53 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 54 | )] 55 | pub struct Utxo { 56 | /// See [`Outpoint`]. 57 | pub outpoint: Outpoint, 58 | /// Value in the units of satoshi. 59 | pub value: Satoshi, 60 | /// Height in the chain. 61 | pub height: u32, 62 | } 63 | 64 | /// Filter for requesting UTXOs. 65 | #[derive( 66 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, 67 | )] 68 | pub enum UtxoFilter { 69 | /// Minimum number of confirmations. There is an upper bound of 144. Typically set to a value around 6 in practice. 70 | #[serde(rename = "min_confirmations")] 71 | MinConfirmations(u32), 72 | /// Page reference. 73 | /// 74 | /// DON'T construct it from scratch. 75 | /// Only get it from the `next_page` field of [`GetUtxosResponse`]. 76 | #[serde(rename = "page")] 77 | Page(Vec), 78 | } 79 | 80 | /// Argument type of [`bitcoin_get_balance`](super::bitcoin_get_balance). 81 | #[derive( 82 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 83 | )] 84 | pub struct GetBalanceRequest { 85 | /// See [`BitcoinAddress`]. 86 | pub address: BitcoinAddress, 87 | /// See [`BitcoinNetwork`]. 88 | pub network: BitcoinNetwork, 89 | /// Minimum number of confirmations. There is an upper bound of 144. Typically set to a value around 6 in practice. 90 | pub min_confirmations: Option, 91 | } 92 | 93 | /// Argument type of [`bitcoin_get_utxos`](super::bitcoin_get_utxos). 94 | #[derive( 95 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 96 | )] 97 | pub struct GetUtxosRequest { 98 | /// See [`BitcoinAddress`]. 99 | pub address: BitcoinAddress, 100 | /// See [`BitcoinNetwork`]. 101 | pub network: BitcoinNetwork, 102 | /// See [`UtxoFilter`]. 103 | pub filter: Option, 104 | } 105 | 106 | /// Response type of [`bitcoin_get_utxos`](super::bitcoin_get_utxos). 107 | #[derive( 108 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 109 | )] 110 | pub struct GetUtxosResponse { 111 | /// List of UTXOs. 112 | pub utxos: Vec, 113 | /// Hash of the tip block. 114 | pub tip_block_hash: BlockHash, 115 | /// Height of the tip height. 116 | pub tip_height: u32, 117 | /// Page reference when the response needs to be paginated. 118 | /// 119 | /// To be used in [`UtxoFilter::Page`]. 120 | pub next_page: Option>, 121 | } 122 | 123 | /// Argument type of [`bitcoin_send_transaction`](super::bitcoin_send_transaction). 124 | #[derive( 125 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 126 | )] 127 | pub struct SendTransactionRequest { 128 | /// The serialized transaction data. 129 | /// 130 | /// Several checks are performed. 131 | /// See [IC method `bitcoin_send_transaction`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction). 132 | pub transaction: Vec, 133 | /// See [`BitcoinNetwork`]. 134 | pub network: BitcoinNetwork, 135 | } 136 | 137 | /// Argument type of [`bitcoin_get_current_fee_percentiles`](super::bitcoin_get_current_fee_percentiles). 138 | #[derive( 139 | CandidType, 140 | Serialize, 141 | Deserialize, 142 | Debug, 143 | PartialEq, 144 | Eq, 145 | PartialOrd, 146 | Ord, 147 | Hash, 148 | Clone, 149 | Copy, 150 | Default, 151 | )] 152 | pub struct GetCurrentFeePercentilesRequest { 153 | /// See [`BitcoinNetwork`]. 154 | pub network: BitcoinNetwork, 155 | } 156 | 157 | /// Argument type of [`bitcoin_get_block_headers`](super::bitcoin_get_block_headers). 158 | #[derive( 159 | CandidType, 160 | Serialize, 161 | Deserialize, 162 | Debug, 163 | PartialEq, 164 | Eq, 165 | PartialOrd, 166 | Ord, 167 | Hash, 168 | Clone, 169 | Copy, 170 | Default, 171 | )] 172 | pub struct GetBlockHeadersRequest { 173 | /// The starting block height for the request. 174 | pub start_height: u32, 175 | /// The ending block height for the request, or `None` for the current tip. 176 | pub end_height: Option, 177 | /// See [`BitcoinNetwork`]. 178 | pub network: BitcoinNetwork, 179 | } 180 | 181 | /// Response type of [`bitcoin_get_block_headers`](super::bitcoin_get_block_headers). 182 | #[derive( 183 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 184 | )] 185 | pub struct GetBlockHeadersResponse { 186 | /// The tip of the blockchain when this request was filled. 187 | pub tip_height: u32, 188 | /// The requested block headers. 189 | pub block_headers: Vec>, 190 | } 191 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/ecdsa/mod.rs: -------------------------------------------------------------------------------- 1 | //! Threshold ECDSA signing API. 2 | 3 | use crate::api::call::{CallResult, call, call_with_payment128}; 4 | use candid::Principal; 5 | 6 | mod types; 7 | pub use types::*; 8 | 9 | const SIGN_WITH_ECDSA_FEE: u128 = 26_153_846_153; 10 | 11 | /// Return a SEC1 encoded ECDSA public key for the given canister using the given derivation path. 12 | /// 13 | /// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-ecdsa_public_key). 14 | pub async fn ecdsa_public_key( 15 | arg: EcdsaPublicKeyArgument, 16 | ) -> CallResult<(EcdsaPublicKeyResponse,)> { 17 | call(Principal::management_canister(), "ecdsa_public_key", (arg,)).await 18 | } 19 | 20 | /// Return a new ECDSA signature of the given `message_hash` that can be separately verified against a derived ECDSA public key. 21 | /// 22 | /// See [IC method `sign_with_ecdsa`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_ecdsa). 23 | /// 24 | /// This call requires cycles payment. 25 | /// This method handles the cycles cost under the hood. 26 | /// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details. 27 | pub async fn sign_with_ecdsa(arg: SignWithEcdsaArgument) -> CallResult<(SignWithEcdsaResponse,)> { 28 | call_with_payment128( 29 | Principal::management_canister(), 30 | "sign_with_ecdsa", 31 | (arg,), 32 | SIGN_WITH_ECDSA_FEE, 33 | ) 34 | .await 35 | } 36 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/ecdsa/types.rs: -------------------------------------------------------------------------------- 1 | use candid::CandidType; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::super::main::CanisterId; 5 | 6 | /// Argument type of [`ecdsa_public_key`](super::ecdsa_public_key). 7 | #[derive( 8 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 9 | )] 10 | pub struct EcdsaPublicKeyArgument { 11 | /// Canister id, default to the canister id of the caller if None. 12 | pub canister_id: Option, 13 | /// A vector of variable length byte strings. 14 | pub derivation_path: Vec>, 15 | /// See [`EcdsaKeyId`]. 16 | pub key_id: EcdsaKeyId, 17 | } 18 | 19 | /// Response Type of [`ecdsa_public_key`](super::ecdsa_public_key). 20 | #[derive( 21 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 22 | )] 23 | pub struct EcdsaPublicKeyResponse { 24 | /// An ECDSA public key encoded in SEC1 compressed form. 25 | pub public_key: Vec, 26 | /// Can be used to deterministically derive child keys of the `public_key`. 27 | pub chain_code: Vec, 28 | } 29 | 30 | /// Argument type of [`sign_with_ecdsa`](super::sign_with_ecdsa). 31 | #[derive( 32 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 33 | )] 34 | pub struct SignWithEcdsaArgument { 35 | /// Hash of the message with length of 32 bytes. 36 | pub message_hash: Vec, 37 | /// A vector of variable length byte strings. 38 | pub derivation_path: Vec>, 39 | /// See [`EcdsaKeyId`]. 40 | pub key_id: EcdsaKeyId, 41 | } 42 | 43 | /// Response type of [`sign_with_ecdsa`](super::sign_with_ecdsa). 44 | #[derive( 45 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 46 | )] 47 | pub struct SignWithEcdsaResponse { 48 | /// Encoded as the concatenation of the SEC1 encodings of the two values r and s. 49 | pub signature: Vec, 50 | } 51 | 52 | /// ECDSA key ID. 53 | #[derive( 54 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 55 | )] 56 | pub struct EcdsaKeyId { 57 | /// See [`EcdsaCurve`]. 58 | pub curve: EcdsaCurve, 59 | /// Name. 60 | pub name: String, 61 | } 62 | 63 | /// ECDSA Curve. 64 | #[derive( 65 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, 66 | )] 67 | pub enum EcdsaCurve { 68 | /// secp256k1 69 | #[serde(rename = "secp256k1")] 70 | Secp256k1, 71 | } 72 | 73 | impl Default for EcdsaCurve { 74 | fn default() -> Self { 75 | Self::Secp256k1 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/http_request/mod.rs: -------------------------------------------------------------------------------- 1 | //! Canister HTTP request. 2 | 3 | use crate::api::call::{CallResult, call_with_payment128}; 4 | use candid::Principal; 5 | #[cfg(feature = "transform-closure")] 6 | use slotmap::{DefaultKey, Key, KeyData, SlotMap}; 7 | #[cfg(feature = "transform-closure")] 8 | use std::cell::RefCell; 9 | 10 | mod types; 11 | pub use types::*; 12 | 13 | /// Make an HTTP request to a given URL and return the HTTP response, possibly after a transformation. 14 | /// 15 | /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request). 16 | /// 17 | /// This call requires cycles payment. The required cycles is a function of the request size and `max_response_bytes`. 18 | /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details. 19 | pub async fn http_request( 20 | arg: CanisterHttpRequestArgument, 21 | cycles: u128, 22 | ) -> CallResult<(HttpResponse,)> { 23 | call_with_payment128( 24 | Principal::management_canister(), 25 | "http_request", 26 | (arg,), 27 | cycles, 28 | ) 29 | .await 30 | } 31 | 32 | #[cfg(feature = "transform-closure")] 33 | thread_local! { 34 | #[allow(clippy::type_complexity)] 35 | static TRANSFORMS_LEGACY: RefCell HttpResponse>>> = RefCell::default(); 36 | } 37 | 38 | #[cfg(feature = "transform-closure")] 39 | #[cfg_attr( 40 | target_family = "wasm", 41 | unsafe(export_name = "canister_query http_transform_legacy") 42 | )] 43 | #[cfg_attr( 44 | not(target_family = "wasm"), 45 | unsafe(export_name = "canister_query_ic_cdk_internal.http_transform_legacy") 46 | )] 47 | extern "C" fn http_transform() { 48 | ic_cdk_executor::in_tracking_query_executor_context(|| { 49 | use crate::api::{ 50 | call::{ArgDecoderConfig, arg_data, reply}, 51 | caller, 52 | }; 53 | if caller() != Principal::management_canister() { 54 | crate::trap("This function is internal to ic-cdk and should not be called externally."); 55 | } 56 | let (args,): (TransformArgs,) = arg_data(ArgDecoderConfig::default()); 57 | let int = u64::from_be_bytes(args.context[..].try_into().unwrap()); 58 | let key = DefaultKey::from(KeyData::from_ffi(int)); 59 | let func = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(key)); 60 | let Some(func) = func else { 61 | crate::trap(format!("Missing transform function for request {int}")); 62 | }; 63 | let transformed = func(args.response); 64 | reply((transformed,)); 65 | }); 66 | } 67 | 68 | /// Make an HTTP request to a given URL and return the HTTP response, after a transformation. 69 | /// 70 | /// Do not set the `transform` field of `arg`. To use a Candid function, call [`http_request`] instead. 71 | /// 72 | /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request). 73 | /// 74 | /// This call requires cycles payment. The required cycles is a function of the request size and `max_response_bytes`. 75 | /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details. 76 | #[cfg(feature = "transform-closure")] 77 | #[cfg_attr(docsrs, doc(cfg(feature = "transform-closure")))] 78 | pub async fn http_request_with_closure( 79 | arg: CanisterHttpRequestArgument, 80 | cycles: u128, 81 | transform_func: impl FnOnce(HttpResponse) -> HttpResponse + 'static, 82 | ) -> CallResult<(HttpResponse,)> { 83 | assert!( 84 | arg.transform.is_none(), 85 | "`CanisterHttpRequestArgument`'s `transform` field must be `None` when using a closure" 86 | ); 87 | let transform_func = Box::new(transform_func) as _; 88 | let key = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().insert(transform_func)); 89 | struct DropGuard(DefaultKey); 90 | impl Drop for DropGuard { 91 | fn drop(&mut self) { 92 | TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(self.0)); 93 | } 94 | } 95 | let key = DropGuard(key); 96 | let context = key.0.data().as_ffi().to_be_bytes().to_vec(); 97 | let arg = CanisterHttpRequestArgument { 98 | transform: Some(TransformContext { 99 | function: TransformFunc(candid::Func { 100 | method: " http_transform_legacy".into(), 101 | principal: crate::id(), 102 | }), 103 | context, 104 | }), 105 | ..arg 106 | }; 107 | http_request(arg, cycles).await 108 | } 109 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/http_request/types.rs: -------------------------------------------------------------------------------- 1 | use crate::id; 2 | use candid::CandidType; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | mod transform { 6 | #![allow(missing_docs)] 7 | 8 | // The struct `TransformFunc` is defined by a macro. 9 | // Adding doc comment directly above the macro doesn't work. 10 | // The workaround is to re-export it and document there. 11 | // TODO: upgrade Rust toolchain (https://dfinity.atlassian.net/browse/SDK-1183) 12 | use super::*; 13 | 14 | candid::define_function!(pub TransformFunc : (TransformArgs) -> (HttpResponse) query); 15 | } 16 | 17 | /// "transform" function of type: `func (http_response) -> (http_response) query` 18 | pub use transform::TransformFunc; 19 | 20 | /// Type used for encoding/decoding: 21 | /// `record { 22 | /// response : http_response; 23 | /// context : blob; 24 | /// }` 25 | #[derive(CandidType, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 26 | pub struct TransformArgs { 27 | /// Raw response from remote service, to be transformed 28 | pub response: HttpResponse, 29 | 30 | /// Context for response transformation 31 | #[serde(with = "serde_bytes")] 32 | pub context: Vec, 33 | } 34 | 35 | /// Type used for encoding/decoding: 36 | /// `record { 37 | /// function : func (record {response : http_response; context : blob}) -> (http_response) query; 38 | /// context : blob; 39 | /// }` 40 | #[derive(CandidType, Clone, Debug, Deserialize, PartialEq, Eq)] 41 | pub struct TransformContext { 42 | /// Reference function with signature: `func (record {response : http_response; context : blob}) -> (http_response) query;`. 43 | pub function: TransformFunc, 44 | 45 | /// Context to be passed to `transform` function to transform HTTP response for consensus 46 | #[serde(with = "serde_bytes")] 47 | pub context: Vec, 48 | } 49 | 50 | impl TransformContext { 51 | /// Constructs a `TransformContext` from a name and context. The principal is assumed to be the [current canister's](id). 52 | pub fn from_name(candid_function_name: String, context: Vec) -> Self { 53 | Self { 54 | context, 55 | function: TransformFunc(candid::Func { 56 | method: candid_function_name, 57 | principal: id(), 58 | }), 59 | } 60 | } 61 | } 62 | 63 | /// HTTP header. 64 | #[derive( 65 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 66 | )] 67 | pub struct HttpHeader { 68 | /// Name 69 | pub name: String, 70 | /// Value 71 | pub value: String, 72 | } 73 | 74 | /// HTTP method. 75 | /// 76 | /// Currently support following methods. 77 | #[derive( 78 | CandidType, 79 | Serialize, 80 | Deserialize, 81 | Debug, 82 | PartialEq, 83 | Eq, 84 | PartialOrd, 85 | Ord, 86 | Hash, 87 | Clone, 88 | Copy, 89 | Default, 90 | )] 91 | pub enum HttpMethod { 92 | /// GET 93 | #[serde(rename = "get")] 94 | #[default] 95 | GET, 96 | /// POST 97 | #[serde(rename = "post")] 98 | POST, 99 | /// HEAD 100 | #[serde(rename = "head")] 101 | HEAD, 102 | } 103 | 104 | /// Argument type of [`http_request`](super::http_request). 105 | #[derive(CandidType, Deserialize, Debug, PartialEq, Eq, Clone, Default)] 106 | pub struct CanisterHttpRequestArgument { 107 | /// The requested URL. 108 | pub url: String, 109 | /// The maximal size of the response in bytes. If None, 2MiB will be the limit. 110 | /// This value affects the cost of the http request and it is highly recommended 111 | /// to set it as low as possible to avoid unnecessary extra costs. 112 | /// See also the [pricing section of HTTP outcalls documentation](https://internetcomputer.org/docs/current/developer-docs/integrations/http_requests/http_requests-how-it-works#pricing). 113 | pub max_response_bytes: Option, 114 | /// The method of HTTP request. 115 | pub method: HttpMethod, 116 | /// List of HTTP request headers and their corresponding values. 117 | pub headers: Vec, 118 | /// Optionally provide request body. 119 | pub body: Option>, 120 | /// Name of the transform function which is `func (transform_args) -> (http_response) query`. 121 | /// Set to `None` if you are using `http_request_with` or `http_request_with_cycles_with`. 122 | pub transform: Option, 123 | } 124 | 125 | /// The returned HTTP response. 126 | #[derive( 127 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 128 | )] 129 | pub struct HttpResponse { 130 | /// The response status (e.g., 200, 404). 131 | pub status: candid::Nat, 132 | /// List of HTTP response headers and their corresponding values. 133 | pub headers: Vec, 134 | /// The response’s body. 135 | pub body: Vec, 136 | } 137 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/mod.rs: -------------------------------------------------------------------------------- 1 | //! Functions and types for calling [the IC management canister][1]. 2 | //! 3 | //! This module is a direct translation from the [interface description][2]. 4 | //! 5 | //! The functions and types defined in this module serves these purposes: 6 | //! * Make it easy to construct correct request data. 7 | //! * Handle the response ergonomically. 8 | //! * For those calls require cycles payments, the cycles amount is an explicit argument. 9 | //! 10 | //! [1]: https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-management-canister 11 | //! [2]: https://internetcomputer.org/assets/files/ic-a45d11feb0ba0494055083f9d2d21ddf.did 12 | #![allow(deprecated)] 13 | #[deprecated( 14 | since = "0.18.0", 15 | note = "The `api::management_canister::bitcoin` module is deprecated. Please use the `bitcoin_canister` module at the crate root." 16 | )] 17 | pub mod bitcoin; 18 | #[deprecated( 19 | since = "0.18.0", 20 | note = "The `api::management_canister::ecdsa` module is deprecated. Please use the `management_canister` module at the crate root." 21 | )] 22 | pub mod ecdsa; 23 | #[deprecated( 24 | since = "0.18.0", 25 | note = "The `api::management_canister::http_request` module is deprecated. Please use the `management_canister` module at the crate root." 26 | )] 27 | pub mod http_request; 28 | #[deprecated( 29 | since = "0.18.0", 30 | note = "The `api::management_canister::main` module is deprecated. Please use the `management_canister` module at the crate root." 31 | )] 32 | pub mod main; 33 | #[deprecated( 34 | since = "0.18.0", 35 | note = "The `api::management_canister::provisional` module is deprecated. Please use the `management_canister` module at the crate root." 36 | )] 37 | pub mod provisional; 38 | #[deprecated( 39 | since = "0.18.0", 40 | note = "The `api::management_canister::schnorr` module is deprecated. Please use the `management_canister` module at the crate root." 41 | )] 42 | pub mod schnorr; 43 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/provisional.rs: -------------------------------------------------------------------------------- 1 | //! Provisional functions only available in local development instances. 2 | 3 | use crate::api::call::{CallResult, call}; 4 | use candid::{CandidType, Nat, Principal}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | pub use super::main::{CanisterId, CanisterIdRecord, CanisterSettings}; 8 | 9 | /// Argument type of [`provisional_create_canister_with_cycles`]. 10 | #[derive( 11 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 12 | )] 13 | pub struct ProvisionalCreateCanisterWithCyclesArgument { 14 | /// The created canister will have this amount of cycles. 15 | pub amount: Option, 16 | /// See [`CanisterSettings`]. 17 | pub settings: Option, 18 | } 19 | 20 | /// Argument type of [`provisional_top_up_canister`]. 21 | #[derive( 22 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, 23 | )] 24 | pub struct ProvisionalTopUpCanisterArgument { 25 | /// Canister ID. 26 | pub canister_id: CanisterId, 27 | /// Amount of cycles to be added. 28 | pub amount: Nat, 29 | } 30 | 31 | /// Create a new canister with specified amount of cycles balance. 32 | /// 33 | /// This method is only available in local development instances. 34 | /// 35 | /// See [IC method `provisional_create_canister_with_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_create_canister_with_cycles). 36 | pub async fn provisional_create_canister_with_cycles( 37 | arg: ProvisionalCreateCanisterWithCyclesArgument, 38 | ) -> CallResult<(CanisterIdRecord,)> { 39 | call( 40 | Principal::management_canister(), 41 | "provisional_create_canister_with_cycles", 42 | (arg,), 43 | ) 44 | .await 45 | } 46 | 47 | /// Add cycles to a canister. 48 | /// 49 | /// This method is only available in local development instances. 50 | /// 51 | /// See [IC method `provisional_top_up_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-provisional_top_up_canister). 52 | pub async fn provisional_top_up_canister(arg: ProvisionalTopUpCanisterArgument) -> CallResult<()> { 53 | call( 54 | Principal::management_canister(), 55 | "provisional_top_up_canister", 56 | (arg,), 57 | ) 58 | .await 59 | } 60 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/schnorr/mod.rs: -------------------------------------------------------------------------------- 1 | //! Threshold Schnorr signing API. 2 | 3 | use crate::api::call::{CallResult, call, call_with_payment128}; 4 | use candid::Principal; 5 | 6 | mod types; 7 | pub use types::*; 8 | 9 | // Source: https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-production-key 10 | const SIGN_WITH_SCHNORR_FEE: u128 = 26_153_846_153; 11 | 12 | /// Return a SEC1 encoded Schnorr public key for the given canister using the given derivation path. 13 | /// 14 | /// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-schnorr_public_key). 15 | pub async fn schnorr_public_key( 16 | arg: SchnorrPublicKeyArgument, 17 | ) -> CallResult<(SchnorrPublicKeyResponse,)> { 18 | call( 19 | Principal::management_canister(), 20 | "schnorr_public_key", 21 | (arg,), 22 | ) 23 | .await 24 | } 25 | 26 | /// Return a new Schnorr signature of the given message that can be separately verified against a derived Schnorr public key. 27 | /// 28 | /// See [IC method `sign_with_schnorr`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_schnorr). 29 | /// 30 | /// This call requires cycles payment. 31 | /// This method handles the cycles cost under the hood. 32 | /// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details. 33 | pub async fn sign_with_schnorr( 34 | arg: SignWithSchnorrArgument, 35 | ) -> CallResult<(SignWithSchnorrResponse,)> { 36 | call_with_payment128( 37 | Principal::management_canister(), 38 | "sign_with_schnorr", 39 | (arg,), 40 | SIGN_WITH_SCHNORR_FEE, 41 | ) 42 | .await 43 | } 44 | -------------------------------------------------------------------------------- /ic-cdk/src/api/management_canister/schnorr/types.rs: -------------------------------------------------------------------------------- 1 | use candid::CandidType; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::super::main::CanisterId; 5 | 6 | /// Argument Type of [`schnorr_public_key`](super::schnorr_public_key). 7 | #[derive( 8 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 9 | )] 10 | pub struct SchnorrPublicKeyArgument { 11 | /// Canister id, default to the canister id of the caller if None. 12 | pub canister_id: Option, 13 | /// A vector of variable length byte strings. 14 | pub derivation_path: Vec>, 15 | /// See [`SchnorrKeyId`]. 16 | pub key_id: SchnorrKeyId, 17 | } 18 | 19 | /// Response Type of [`schnorr_public_key`](super::schnorr_public_key). 20 | #[derive( 21 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 22 | )] 23 | pub struct SchnorrPublicKeyResponse { 24 | /// An Schnorr public key encoded in SEC1 compressed form. 25 | pub public_key: Vec, 26 | /// Can be used to deterministically derive child keys of the `public_key`. 27 | pub chain_code: Vec, 28 | } 29 | 30 | /// Argument Type of [`sign_with_schnorr`](super::sign_with_schnorr). 31 | #[derive( 32 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 33 | )] 34 | pub struct SignWithSchnorrArgument { 35 | /// Message to be signed. 36 | pub message: Vec, 37 | /// A vector of variable length byte strings. 38 | pub derivation_path: Vec>, 39 | /// See [`SchnorrKeyId`]. 40 | pub key_id: SchnorrKeyId, 41 | } 42 | 43 | /// Response Type of [`sign_with_schnorr`](super::sign_with_schnorr). 44 | #[derive( 45 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 46 | )] 47 | pub struct SignWithSchnorrResponse { 48 | /// The encoding of the signature depends on the key ID's algorithm. 49 | pub signature: Vec, 50 | } 51 | 52 | /// Schnorr key ID. 53 | #[derive( 54 | CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default, 55 | )] 56 | pub struct SchnorrKeyId { 57 | /// See [`SchnorrAlgorithm`]. 58 | pub algorithm: SchnorrAlgorithm, 59 | /// Name. 60 | pub name: String, 61 | } 62 | 63 | /// Schnorr Algorithm. 64 | #[derive( 65 | CandidType, 66 | Serialize, 67 | Deserialize, 68 | Debug, 69 | PartialEq, 70 | Eq, 71 | PartialOrd, 72 | Ord, 73 | Hash, 74 | Clone, 75 | Copy, 76 | Default, 77 | )] 78 | pub enum SchnorrAlgorithm { 79 | /// BIP-340 secp256k1. 80 | #[serde(rename = "bip340secp256k1")] 81 | #[default] 82 | Bip340secp256k1, 83 | /// ed25519. 84 | #[serde(rename = "ed25519")] 85 | Ed25519, 86 | } 87 | -------------------------------------------------------------------------------- /ic-cdk/src/api/stable/canister.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// A standard implementation of [`StableMemory`]. 4 | /// 5 | /// Useful for creating [`StableWriter`] and [`StableReader`]. 6 | #[derive(Default, Debug, Copy, Clone)] 7 | pub struct CanisterStableMemory {} 8 | 9 | impl StableMemory for CanisterStableMemory { 10 | fn stable_size(&self) -> u64 { 11 | ic0::stable64_size() 12 | } 13 | 14 | fn stable_grow(&self, new_pages: u64) -> Result { 15 | match ic0::stable64_grow(new_pages) { 16 | u64::MAX => Err(StableMemoryError::OutOfMemory), 17 | x => Ok(x), 18 | } 19 | } 20 | 21 | fn stable_write(&self, offset: u64, buf: &[u8]) { 22 | ic0::stable64_write(buf, offset); 23 | } 24 | 25 | fn stable_read(&self, offset: u64, buf: &mut [u8]) { 26 | ic0::stable64_read(buf, offset); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ic-cdk/src/futures/internals.rs: -------------------------------------------------------------------------------- 1 | //! Internals used by the attribute macros. 2 | //! 3 | //! You do not need to use this module unless you are deliberately avoiding the attribute macros. 4 | 5 | /// Execute an update function in a context that allows calling [`spawn`](super::spawn). 6 | /// 7 | /// You do not need to worry about this function unless you are avoiding the attribute macros. 8 | /// 9 | /// Background tasks will be polled in the process (and will not be run otherwise). 10 | /// Panics if called inside an existing executor context. 11 | pub fn in_executor_context(f: impl FnOnce() -> R) -> R { 12 | ic_cdk_executor::in_tracking_executor_context(f) 13 | } 14 | 15 | /// Execute a composite query function in a context that allows calling [`spawn`](super::spawn). 16 | /// 17 | /// You do not need to worry about this function unless you are avoiding the attribute macros. 18 | /// 19 | /// Background composite query tasks will be polled in the process (and will not be run otherwise). 20 | /// Panics if called inside an existing executor context. 21 | pub fn in_query_executor_context(f: impl FnOnce() -> R) -> R { 22 | ic_cdk_executor::in_tracking_query_executor_context(f) 23 | } 24 | -------------------------------------------------------------------------------- /ic-cdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn( 3 | elided_lifetimes_in_paths, 4 | missing_debug_implementations, 5 | missing_docs, 6 | unsafe_op_in_unsafe_fn, 7 | clippy::undocumented_unsafe_blocks, 8 | clippy::missing_safety_doc 9 | )] 10 | #![cfg_attr(docsrs, feature(doc_cfg))] 11 | 12 | #[cfg(target_feature = "atomics")] 13 | compile_error!("This version of the CDK does not support multithreading."); 14 | 15 | pub mod api; 16 | pub mod bitcoin_canister; 17 | pub mod call; 18 | pub mod futures; 19 | mod macros; 20 | pub mod management_canister; 21 | pub mod stable; 22 | pub mod storage; 23 | 24 | use std::future::Future; 25 | 26 | #[doc(inline)] 27 | pub use api::trap; 28 | 29 | #[doc(hidden)] 30 | #[allow(deprecated)] 31 | pub use api::{ 32 | call::{call, notify}, 33 | caller, id, print, 34 | }; 35 | 36 | #[doc(inline)] 37 | pub use macros::*; 38 | 39 | /// Format and then print the formatted message 40 | #[cfg(target_family = "wasm")] 41 | #[macro_export] 42 | macro_rules! println { 43 | ($fmt:expr) => ($crate::api::debug_print(format!($fmt))); 44 | ($fmt:expr, $($arg:tt)*) => ($crate::api::debug_print(format!($fmt, $($arg)*))); 45 | } 46 | 47 | /// Format and then print the formatted message 48 | #[cfg(not(target_family = "wasm"))] 49 | #[macro_export] 50 | macro_rules! println { 51 | ($fmt:expr) => (std::println!($fmt)); 52 | ($fmt:expr, $($arg:tt)*) => (std::println!($fmt, $($arg)*)); 53 | } 54 | 55 | /// Format and then print the formatted message 56 | #[cfg(target_family = "wasm")] 57 | #[macro_export] 58 | macro_rules! eprintln { 59 | ($fmt:expr) => ($crate::api::debug_print(format!($fmt))); 60 | ($fmt:expr, $($arg:tt)*) => ($crate::api::debug_print(format!($fmt, $($arg)*))); 61 | } 62 | 63 | /// Format and then print the formatted message 64 | #[cfg(not(target_family = "wasm"))] 65 | #[macro_export] 66 | macro_rules! eprintln { 67 | ($fmt:expr) => (std::eprintln!($fmt)); 68 | ($fmt:expr, $($arg:tt)*) => (std::eprintln!($fmt, $($arg)*)); 69 | } 70 | 71 | #[doc(hidden)] 72 | #[deprecated( 73 | since = "0.18.0", 74 | note = "Use ic_cdk::futures::spawn_017_compat. Alternatively, migrate to ic_cdk::futures::spawn; 75 | code execution order will change, see https://github.com/dfinity/cdk-rs/blob/0.18.3/ic-cdk/V18_GUIDE.md#futures-ordering-changes" 76 | )] 77 | pub fn spawn>(fut: F) { 78 | crate::futures::spawn_017_compat(fut); 79 | } 80 | -------------------------------------------------------------------------------- /ic-cdk/src/storage.rs: -------------------------------------------------------------------------------- 1 | //! Tools for managing stable storage of data in a canister. 2 | use crate::stable; 3 | 4 | /// Saves the storage into the stable memory. 5 | /// 6 | /// This will override any value previously stored in stable memory. 7 | pub fn stable_save(t: T) -> Result<(), candid::Error> 8 | where 9 | T: candid::utils::ArgumentEncoder, 10 | { 11 | candid::write_args(&mut stable::StableWriter::default(), t) 12 | } 13 | 14 | /// Restores a value from the stable memory to the storage. 15 | /// 16 | /// There can only be one value in stable memory, currently. 17 | pub fn stable_restore() -> Result 18 | where 19 | T: for<'de> candid::utils::ArgumentDecoder<'de>, 20 | { 21 | let bytes = stable::stable_bytes(); 22 | 23 | let mut de = candid::de::IDLDeserialize::new(bytes.as_slice()).map_err(|e| format!("{e:?}"))?; 24 | let res = candid::utils::ArgumentDecoder::decode(&mut de).map_err(|e| format!("{e:?}"))?; 25 | Ok(res) 26 | } 27 | -------------------------------------------------------------------------------- /ic-cdk/tests/bitcoin.did: -------------------------------------------------------------------------------- 1 | type network = variant { 2 | mainnet; 3 | testnet; // Bitcoin testnet4. 4 | regtest; 5 | }; 6 | 7 | type satoshi = nat64; 8 | 9 | type address = text; 10 | 11 | type block_hash = blob; 12 | 13 | type block_header = blob; 14 | 15 | type block_height = nat32; 16 | 17 | type outpoint = record { 18 | txid : blob; 19 | vout : nat32; 20 | }; 21 | 22 | type utxo = record { 23 | outpoint : outpoint; 24 | value : satoshi; 25 | height : block_height; 26 | }; 27 | 28 | type flag = variant { 29 | enabled; 30 | disabled; 31 | }; 32 | 33 | type init_config = record { 34 | stability_threshold : opt nat; 35 | network : opt network; 36 | blocks_source : opt principal; 37 | syncing : opt flag; 38 | fees : opt fees; 39 | api_access : opt flag; 40 | disable_api_if_not_fully_synced : opt flag; 41 | watchdog_canister : opt opt principal; 42 | burn_cycles : opt flag; 43 | lazily_evaluate_fee_percentiles : opt flag; 44 | }; 45 | 46 | type config = record { 47 | stability_threshold : nat; 48 | network : network; 49 | blocks_source : principal; 50 | syncing : flag; 51 | fees : fees; 52 | api_access : flag; 53 | disable_api_if_not_fully_synced : flag; 54 | watchdog_canister : opt principal; 55 | burn_cycles : flag; 56 | lazily_evaluate_fee_percentiles : flag; 57 | }; 58 | 59 | type fees = record { 60 | get_utxos_base : nat; 61 | get_utxos_cycles_per_ten_instructions : nat; 62 | get_utxos_maximum : nat; 63 | get_balance : nat; 64 | get_balance_maximum : nat; 65 | get_current_fee_percentiles : nat; 66 | get_current_fee_percentiles_maximum : nat; 67 | send_transaction_base : nat; 68 | send_transaction_per_byte : nat; 69 | get_block_headers_base : nat; 70 | get_block_headers_cycles_per_ten_instructions : nat; 71 | get_block_headers_maximum : nat; 72 | }; 73 | 74 | type get_balance_request = record { 75 | network : network; 76 | address : address; 77 | min_confirmations : opt nat32; 78 | }; 79 | 80 | type get_utxos_request = record { 81 | network : network; 82 | address : address; 83 | filter : opt variant { 84 | min_confirmations : nat32; 85 | page : blob; 86 | }; 87 | }; 88 | 89 | type get_utxos_response = record { 90 | utxos : vec utxo; 91 | tip_block_hash : block_hash; 92 | tip_height : block_height; 93 | next_page : opt blob; 94 | }; 95 | 96 | type get_current_fee_percentiles_request = record { 97 | network : network; 98 | }; 99 | 100 | type send_transaction_request = record { 101 | network : network; 102 | transaction : blob; 103 | }; 104 | 105 | type millisatoshi_per_byte = nat64; 106 | 107 | type set_config_request = record { 108 | stability_threshold : opt nat; 109 | syncing : opt flag; 110 | fees : opt fees; 111 | api_access : opt flag; 112 | disable_api_if_not_fully_synced : opt flag; 113 | watchdog_canister : opt opt principal; 114 | burn_cycles : opt flag; 115 | lazily_evaluate_fee_percentiles : opt flag; 116 | }; 117 | 118 | type get_block_headers_request = record { 119 | start_height : block_height; 120 | end_height : opt block_height; 121 | network : network; 122 | }; 123 | 124 | type get_block_headers_response = record { 125 | tip_height : block_height; 126 | block_headers : vec block_header; 127 | }; 128 | 129 | // service bitcoin : (init_config) -> { 130 | service bitcoin : { 131 | bitcoin_get_balance : (get_balance_request) -> (satoshi); 132 | 133 | // bitcoin_get_balance_query : (get_balance_request) -> (satoshi) query; 134 | 135 | bitcoin_get_utxos : (get_utxos_request) -> (get_utxos_response); 136 | 137 | // bitcoin_get_utxos_query : (get_utxos_request) -> (get_utxos_response) query; 138 | 139 | bitcoin_get_current_fee_percentiles : (get_current_fee_percentiles_request) -> (vec millisatoshi_per_byte); 140 | 141 | bitcoin_get_block_headers : (get_block_headers_request) -> (get_block_headers_response); 142 | 143 | bitcoin_send_transaction : (send_transaction_request) -> (); 144 | 145 | // get_config : () -> (config) query; 146 | 147 | // set_config : (set_config_request) -> (); 148 | }; 149 | -------------------------------------------------------------------------------- /ic-cdk/tests/bitcoin_candid_equality.rs: -------------------------------------------------------------------------------- 1 | //! This test checks that the generated candid interface is equal to the one in the bitcoin.did file. 2 | //! 3 | //! The bitcoin.did file comes from https://github.com/dfinity/bitcoin-canister/blob/master/canister/candid.did 4 | //! 5 | //! Following items in bitcoin.did are commented out because they are not implemented in the bitcoin_canister module: 6 | //! - The init args `(init_config)`. 7 | //! - The `*_query` methods. Calling them from inter-canister calls would result in "<_query> cannot be called in replicated mode" rejection. 8 | //! - The `get_config` and `set_config` methods. 9 | 10 | #![allow(unused)] 11 | use candid::candid_method; 12 | use ic_cdk::bitcoin_canister::*; 13 | 14 | #[candid_method(update)] 15 | fn bitcoin_get_balance(_: GetBalanceRequest) -> Satoshi { 16 | unimplemented!() 17 | } 18 | 19 | #[candid_method(update)] 20 | fn bitcoin_get_utxos(_: GetUtxosRequest) -> GetUtxosResponse { 21 | unimplemented!() 22 | } 23 | 24 | #[candid_method(update)] 25 | fn bitcoin_get_current_fee_percentiles( 26 | _: GetCurrentFeePercentilesRequest, 27 | ) -> Vec { 28 | unimplemented!() 29 | } 30 | 31 | #[candid_method(update)] 32 | fn bitcoin_get_block_headers(_: GetBlockHeadersRequest) -> GetBlockHeadersResponse { 33 | unimplemented!() 34 | } 35 | 36 | #[candid_method(update)] 37 | fn bitcoin_send_transaction(_: SendTransactionRequest) { 38 | unimplemented!() 39 | } 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use candid_parser::utils::{CandidSource, service_equal}; 44 | use ic_cdk::bitcoin_canister::*; 45 | 46 | #[test] 47 | fn candid_equality_test() { 48 | let declared_interface_str = 49 | std::fs::read_to_string("tests/bitcoin.did").expect("failed to read bitcoin.did file"); 50 | let declared_interface = CandidSource::Text(&declared_interface_str); 51 | 52 | candid::export_service!(); 53 | let implemented_interface_str = __export_service(); 54 | let implemented_interface = CandidSource::Text(&implemented_interface_str); 55 | 56 | let result = service_equal(declared_interface, implemented_interface); 57 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/lifecycle_functions_should_have_no_return.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{heartbeat, init, inspect_message, on_low_wasm_memory, post_upgrade, pre_upgrade}; 2 | 3 | #[init] 4 | fn init() -> u32 {} 5 | 6 | #[pre_upgrade] 7 | fn pre_upgrade() -> u32 {} 8 | 9 | #[post_upgrade] 10 | fn post_upgrade() -> u32 {} 11 | 12 | #[heartbeat] 13 | fn heartbeat() -> u32 {} 14 | 15 | #[inspect_message] 16 | fn inspect_message() -> u32 {} 17 | 18 | #[on_low_wasm_memory] 19 | fn on_low_wasm_memory() -> u32 {} 20 | 21 | fn main() {} 22 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/lifecycle_functions_should_have_no_return.stderr: -------------------------------------------------------------------------------- 1 | error: #[init] function cannot have a return value. 2 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:3:1 3 | | 4 | 3 | #[init] 5 | | ^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: #[pre_upgrade] function cannot have a return value. 10 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:6:1 11 | | 12 | 6 | #[pre_upgrade] 13 | | ^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in the attribute macro `pre_upgrade` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error: #[post_upgrade] function cannot have a return value. 18 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:9:1 19 | | 20 | 9 | #[post_upgrade] 21 | | ^^^^^^^^^^^^^^^ 22 | | 23 | = note: this error originates in the attribute macro `post_upgrade` (in Nightly builds, run with -Z macro-backtrace for more info) 24 | 25 | error: #[heartbeat] function cannot have a return value. 26 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:12:1 27 | | 28 | 12 | #[heartbeat] 29 | | ^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in the attribute macro `heartbeat` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | 33 | error: #[inspect_message] function cannot have a return value. 34 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:15:1 35 | | 36 | 15 | #[inspect_message] 37 | | ^^^^^^^^^^^^^^^^^^ 38 | | 39 | = note: this error originates in the attribute macro `inspect_message` (in Nightly builds, run with -Z macro-backtrace for more info) 40 | 41 | error: #[on_low_wasm_memory] function cannot have a return value. 42 | --> tests/compile_fail/lifecycle_functions_should_have_no_return.rs:18:1 43 | | 44 | 18 | #[on_low_wasm_memory] 45 | | ^^^^^^^^^^^^^^^^^^^^^ 46 | | 47 | = note: this error originates in the attribute macro `on_low_wasm_memory` (in Nightly builds, run with -Z macro-backtrace for more info) 48 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_generic.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::query; 2 | 3 | #[query] 4 | fn method(_arg: T) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_generic.stderr: -------------------------------------------------------------------------------- 1 | error: #[query] must be above a function with no generic parameters. 2 | --> tests/compile_fail/no_generic.rs:4:10 3 | | 4 | 4 | fn method(_arg: T) {} 5 | | ^ 6 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_guard_function_for_lifecycle.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{heartbeat, init, inspect_message, on_low_wasm_memory, post_upgrade, pre_upgrade}; 2 | 3 | fn guard_function() -> Result<(), String> { 4 | unimplemented!() 5 | } 6 | 7 | #[init(guard = "guard_function")] 8 | fn init() {} 9 | 10 | #[pre_upgrade(guard = "guard_function")] 11 | fn pre_upgrade() {} 12 | 13 | #[post_upgrade(guard = "guard_function")] 14 | fn post_upgrade() {} 15 | 16 | #[heartbeat(guard = "guard_function")] 17 | fn heartbeat() {} 18 | 19 | #[inspect_message(guard = "guard_function")] 20 | fn inspect_message() {} 21 | 22 | #[on_low_wasm_memory(guard = "guard_function")] 23 | fn on_low_wasm_memory() {} 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_guard_function_for_lifecycle.stderr: -------------------------------------------------------------------------------- 1 | error: #[init] cannot have guard function(s). 2 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:7:8 3 | | 4 | 7 | #[init(guard = "guard_function")] 5 | | ^^^^^ 6 | 7 | error: #[pre_upgrade] cannot have guard function(s). 8 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:10:15 9 | | 10 | 10 | #[pre_upgrade(guard = "guard_function")] 11 | | ^^^^^ 12 | 13 | error: #[post_upgrade] cannot have guard function(s). 14 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:13:16 15 | | 16 | 13 | #[post_upgrade(guard = "guard_function")] 17 | | ^^^^^ 18 | 19 | error: #[heartbeat] cannot have guard function(s). 20 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:16:13 21 | | 22 | 16 | #[heartbeat(guard = "guard_function")] 23 | | ^^^^^ 24 | 25 | error: #[inspect_message] cannot have guard function(s). 26 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:19:19 27 | | 28 | 19 | #[inspect_message(guard = "guard_function")] 29 | | ^^^^^ 30 | 31 | error: #[on_low_wasm_memory] cannot have guard function(s). 32 | --> tests/compile_fail/no_guard_function_for_lifecycle.rs:22:22 33 | | 34 | 22 | #[on_low_wasm_memory(guard = "guard_function")] 35 | | ^^^^^ 36 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_self.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::query; 2 | 3 | #[query] 4 | fn method(self) {} 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/no_self.stderr: -------------------------------------------------------------------------------- 1 | error: #[query] cannot be above functions with `self` as a parameter. 2 | --> tests/compile_fail/no_self.rs:4:11 3 | | 4 | 4 | fn method(self) {} 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/only_function.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::query; 2 | 3 | #[query] 4 | struct S; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_fail/only_function.stderr: -------------------------------------------------------------------------------- 1 | error: #[query] must be above a function. 2 | expected `fn` 3 | --> tests/compile_fail/only_function.rs:4:1 4 | | 5 | 4 | struct S; 6 | | ^^^^^^ 7 | -------------------------------------------------------------------------------- /ic-cdk/tests/compile_test.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/pass/*.rs"); 5 | t.compile_fail("tests/compile_fail/*.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /ic-cdk/tests/pass/blank_methods.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{ 2 | heartbeat, init, inspect_message, on_low_wasm_memory, post_upgrade, pre_upgrade, query, update, 3 | }; 4 | 5 | #[init] 6 | fn init() {} 7 | 8 | #[pre_upgrade] 9 | fn pre_upgrade() {} 10 | 11 | #[post_upgrade] 12 | fn post_upgrade() {} 13 | 14 | #[update] 15 | fn update() {} 16 | 17 | #[update(hidden)] 18 | fn update_hidden() {} 19 | 20 | #[update(hidden = true)] 21 | fn update_hidden_true() {} 22 | 23 | #[update(hidden = false)] 24 | fn update_hidden_false() {} 25 | 26 | #[update(manual_reply)] 27 | fn update_manual_reply() {} 28 | 29 | #[update(manual_reply = true)] 30 | fn update_manual_reply_true() {} 31 | 32 | #[update(manual_reply = false)] 33 | fn update_manual_reply_false() {} 34 | 35 | #[query] 36 | fn query() {} 37 | 38 | #[query(hidden)] 39 | fn query_hidden() {} 40 | 41 | #[query(hidden = true)] 42 | fn query_hidden_true() {} 43 | 44 | #[query(hidden = false)] 45 | fn query_hidden_false() {} 46 | 47 | #[query(manual_reply)] 48 | fn query_manual_reply() {} 49 | 50 | #[query(manual_reply = true)] 51 | fn query_manual_reply_true() {} 52 | 53 | #[query(manual_reply = false)] 54 | fn query_manual_reply_false() {} 55 | 56 | #[query(composite)] 57 | fn query_composite() {} 58 | 59 | #[query(composite = true)] 60 | fn query_composite_true() {} 61 | 62 | #[query(composite = false)] 63 | fn query_composite_false() {} 64 | 65 | #[heartbeat] 66 | fn heartbeat() {} 67 | 68 | #[inspect_message] 69 | fn inspect_message() {} 70 | 71 | #[on_low_wasm_memory] 72 | fn on_low_wasm_memory() {} 73 | 74 | fn main() {} 75 | -------------------------------------------------------------------------------- /ic-cdk/tests/pass/guard.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{query, update}; 2 | 3 | fn guard1() -> Result<(), String> { 4 | Ok(()) 5 | } 6 | 7 | fn guard2() -> Result<(), String> { 8 | Ok(()) 9 | } 10 | 11 | #[update(guard = "guard1")] 12 | fn update_1_guard() {} 13 | 14 | #[update(guard = "guard1", guard = "guard2")] 15 | fn update_2_guards() {} 16 | 17 | #[query(guard = "guard1")] 18 | fn query_1_guard() {} 19 | 20 | #[query(guard = "guard1", guard = "guard2")] 21 | fn query_2_guards() {} 22 | 23 | fn main() {} 24 | -------------------------------------------------------------------------------- /ic-cdk/tests/pass/method_arg_same_name.rs: -------------------------------------------------------------------------------- 1 | use ic_cdk::{query, update}; 2 | 3 | #[update] 4 | fn foo(foo: i32) -> i32 { 5 | foo 6 | } 7 | 8 | #[query] 9 | fn bar(bar: i32) -> i32 { 10 | bar 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /ic-management-canister-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [unreleased] 9 | 10 | ### Changed 11 | 12 | - Removed `SettingsChange` variant from `ChangeDetails`. 13 | - Added `from_cansiter_id` field to `LoadSnapshotRecord`. 14 | - Added `ready_for_migration` and `version` fields to `CanisterStatusResult`. 15 | - Added `registry_version` field to `SubnetInfoResult`. 16 | 17 | Following changes are for the Candid interface changes proposed in this [forum post](https://forum.dfinity.org/t/proposal-making-variant-fields-optional-for-schema-evolution/57898#p-203913-proposed-solution-2). 18 | - The `details` field in `Change` is optional. 19 | - The `source` and `globals` fields in `ReadCanisterSnapshotMetadataResult` are optional. 20 | 21 | ### Added 22 | 23 | - `CanisterMetadataArgs` and `CanisterMetadataResult` for the new method `canister_metadata`. 24 | 25 | ## [0.4.1] - 2025-09-04 26 | 27 | ### Fixed 28 | 29 | - Used `candid:Reserved` inside `TakenFromCanister` and `MetadataUpload` variants in `SnapshotSource`. 30 | - Renamed `MainMemory` to `WasmMemory` in `SnapshotDataKind` and `SnapshotDataOffset`. 31 | - Added `source` field to `LoadSnapshotRecord`. 32 | 33 | While this is technically a breaking change in the Rust type system, we are treating it as a patch fix. 34 | This is because the affected types and methods are for new, unreleased features (snapshot download/upload). 35 | Therefore, no existing services or canisters should be impacted by this change. 36 | 37 | ## [0.4.0] - 2025-08-25 38 | 39 | ### Changed 40 | 41 | - Added `environment_variable` field to `CanisterSettings` and `DefiniteCanisterSettings`. 42 | - Added the type `EnvironmentVariable`. 43 | - Added `settings_change` variant to `ChangeDetails`. 44 | - Added `environment_variables_hash` field to `CreationRecord`. 45 | - Added `is_replicated` field to `HttpRequestArgs`. 46 | 47 | ## [0.3.3] - 2025-08-20 48 | 49 | ### Fixed 50 | 51 | - The `exported_globals` field in the `ReadCanisterSnapshotMetadataResult` and `UploadCanisterSnapshotMetadataArgs` structs has been renamed to `globals`. 52 | - The associated type `ExportedGlobal` has been renamed to `SnapshotMetadataGlobal`. 53 | 54 | While this is technically a breaking change in the Rust type system, we are treating it as a patch fix. 55 | This is because the affected types and methods are for new, unreleased features (snapshot download/upload). 56 | Therefore, no existing services or canisters should be impacted by this change. 57 | 58 | ## [0.3.2] - 2025-07-25 59 | 60 | ### Added 61 | 62 | - Types for canister snapshot download/upload. 63 | 64 | ## [0.3.1] - 2025-05-09 65 | 66 | ### Added 67 | 68 | - Types for `vetkd_public_key` and `vetkd_derive_key`. 69 | 70 | ## [0.3.0] - 2025-03-17 71 | 72 | ### Changed 73 | 74 | - Added `wasm_memory_threshold` field to `CanisterSettings` and `DefiniteCanisterSettings`. 75 | - Added the `memory_metrics` field to `CanisterStatusResult`. 76 | - Added the type `MemoryMetrics`. 77 | 78 | ### Added 79 | 80 | - Implemented trait that convert from `EcdsaCurve` and `SchnorrAlgorithm` into `u32`. 81 | 82 | ## [0.2.1] - 2025-02-28 83 | 84 | ### Added 85 | 86 | - Types for `fetch_canister_logs`. 87 | - `CanisterIdRecord`, an alias for various argument and result types to enhance inter-operability. 88 | 89 | ### Fixed 90 | 91 | - Doc: `HttpRequestArgs::max_response_bytes` is capped at 2MB, not 2MiB. 92 | 93 | ## [0.2.0] - 2025-02-18 94 | 95 | ### Changed 96 | 97 | - Added `aux` field in `SignWithSchnorrArgs`, introducing `SchnorrAux` and `Bip341` types. 98 | - Fixed `NodeMetrics` which should have a field `num_block_failures_total`, not `num_blocks_failures_total`. 99 | 100 | ## [0.1.0] - 2023-01-22 101 | 102 | ### Added 103 | 104 | - Initial release of the `ic-management-canister-types` library. 105 | -------------------------------------------------------------------------------- /ic-management-canister-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-management-canister-types" 3 | version = "0.4.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | rust-version.workspace = true 8 | license.workspace = true 9 | description = "Types for calling the IC management canister" 10 | homepage = "https://docs.rs/ic-management-canister-types" 11 | documentation = "https://docs.rs/ic-management-canister-types" 12 | readme = "README.md" 13 | categories = ["api-bindings", "data-structures"] 14 | keywords = ["internet-computer", "types", "dfinity", "canister"] 15 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 16 | 17 | [dependencies] 18 | candid.workspace = true 19 | serde.workspace = true 20 | serde_bytes.workspace = true 21 | 22 | [dev-dependencies] 23 | candid_parser.workspace = true 24 | -------------------------------------------------------------------------------- /ic-management-canister-types/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-management-canister-types/README.md: -------------------------------------------------------------------------------- 1 | # ic-management-canister-types 2 | 3 | Types for calling [the IC management canister][1]. 4 | 5 | This module is a direct translation from its Candid interface description. 6 | 7 | [1]: https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-management-canister 8 | 9 | ## Correctness 10 | 11 | This crate ensures type definition correctness through the [`candid_equality.rs`](tests/candid_equality.rs) test. 12 | 13 | The test defines a dummy Canister covering all Management Canister entry points available for inter-canister calls. 14 | 15 | It then asserts the equality of the dummy canister's interface with the specified interface in [`ic.did`](tests/ic.did). 16 | 17 | The [`ic.did`](tests/ic.did) is sourced from the [Internet Computer Interface Specification](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-candid). 18 | 19 | Some methods are excluded (commented out) as follows: 20 | - Bitcoin API: These functionalities are planned to migrate from the Management Canister to the [Bitcoin Canister](https://github.com/dfinity/bitcoin-canister). 21 | - `fetch_canister_logs`: This method is only available for ingress messages (using an agent) and cannot be invoked in inter-canister calls. 22 | -------------------------------------------------------------------------------- /ic-management-canister-types/tests/candid_equality.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use candid::candid_method; 3 | use ic_management_canister_types::*; 4 | 5 | #[candid_method(update)] 6 | fn create_canister(_: CreateCanisterArgs) -> CreateCanisterResult { 7 | unimplemented!() 8 | } 9 | 10 | #[candid_method(update)] 11 | fn update_settings(_: UpdateSettingsArgs) { 12 | unimplemented!() 13 | } 14 | 15 | #[candid_method(update)] 16 | fn upload_chunk(_: UploadChunkArgs) -> UploadChunkResult { 17 | unimplemented!() 18 | } 19 | 20 | #[candid_method(update)] 21 | fn clear_chunk_store(_: ClearChunkStoreArgs) { 22 | unimplemented!() 23 | } 24 | 25 | #[candid_method(update)] 26 | fn stored_chunks(_: StoredChunksArgs) -> StoredChunksResult { 27 | unimplemented!() 28 | } 29 | 30 | #[candid_method(update)] 31 | fn install_code(_: InstallCodeArgs) { 32 | unimplemented!() 33 | } 34 | 35 | #[candid_method(update)] 36 | fn install_chunked_code(_: InstallChunkedCodeArgs) { 37 | unimplemented!() 38 | } 39 | 40 | #[candid_method(update)] 41 | fn uninstall_code(_: UninstallCodeArgs) { 42 | unimplemented!() 43 | } 44 | 45 | #[candid_method(update)] 46 | fn start_canister(_: StartCanisterArgs) { 47 | unimplemented!() 48 | } 49 | 50 | #[candid_method(update)] 51 | fn stop_canister(_: StopCanisterArgs) { 52 | unimplemented!() 53 | } 54 | 55 | #[candid_method(query)] 56 | fn canister_status(_: CanisterStatusArgs) -> CanisterStatusResult { 57 | unimplemented!() 58 | } 59 | 60 | #[candid_method(update)] 61 | fn canister_info(_: CanisterInfoArgs) -> CanisterInfoResult { 62 | unimplemented!() 63 | } 64 | 65 | #[candid_method(update)] 66 | fn canister_metadata(_: CanisterMetadataArgs) -> CanisterMetadataResult { 67 | unimplemented!() 68 | } 69 | 70 | #[candid_method(update)] 71 | fn subnet_info(_: SubnetInfoArgs) -> SubnetInfoResult { 72 | unimplemented!() 73 | } 74 | 75 | #[candid_method(update)] 76 | fn delete_canister(_: DeleteCanisterArgs) { 77 | unimplemented!() 78 | } 79 | 80 | #[candid_method(update)] 81 | fn deposit_cycles(_: DepositCyclesArgs) { 82 | unimplemented!() 83 | } 84 | 85 | #[candid_method(update)] 86 | fn raw_rand() -> RawRandResult { 87 | unimplemented!() 88 | } 89 | 90 | #[candid_method(update)] 91 | fn http_request(_: HttpRequestArgs) -> HttpRequestResult { 92 | unimplemented!() 93 | } 94 | 95 | #[candid_method(update)] 96 | fn ecdsa_public_key(_: EcdsaPublicKeyArgs) -> EcdsaPublicKeyResult { 97 | unimplemented!() 98 | } 99 | 100 | #[candid_method(update)] 101 | fn sign_with_ecdsa(_: SignWithEcdsaArgs) -> SignWithEcdsaResult { 102 | unimplemented!() 103 | } 104 | 105 | #[candid_method(update)] 106 | fn schnorr_public_key(_: SchnorrPublicKeyArgs) -> SchnorrPublicKeyResult { 107 | unimplemented!() 108 | } 109 | 110 | #[candid_method(update)] 111 | fn sign_with_schnorr(_: SignWithSchnorrArgs) -> SignWithSchnorrResult { 112 | unimplemented!() 113 | } 114 | 115 | #[candid_method(update)] 116 | fn vetkd_public_key(_: VetKDPublicKeyArgs) -> VetKDPublicKeyResult { 117 | unimplemented!() 118 | } 119 | 120 | #[candid_method(update)] 121 | fn vetkd_derive_key(_: VetKDDeriveKeyArgs) -> VetKDDeriveKeyResult { 122 | unimplemented!() 123 | } 124 | 125 | #[candid_method(update)] 126 | fn node_metrics_history(_: NodeMetricsHistoryArgs) -> NodeMetricsHistoryResult { 127 | unimplemented!() 128 | } 129 | 130 | #[candid_method(update)] 131 | fn provisional_create_canister_with_cycles( 132 | _: ProvisionalCreateCanisterWithCyclesArgs, 133 | ) -> ProvisionalCreateCanisterWithCyclesResult { 134 | unimplemented!() 135 | } 136 | 137 | #[candid_method(update)] 138 | fn provisional_top_up_canister(_: ProvisionalTopUpCanisterArgs) { 139 | unimplemented!() 140 | } 141 | 142 | #[candid_method(update)] 143 | fn take_canister_snapshot(_: TakeCanisterSnapshotArgs) -> TakeCanisterSnapshotResult { 144 | unimplemented!() 145 | } 146 | 147 | #[candid_method(update)] 148 | fn load_canister_snapshot(_: LoadCanisterSnapshotArgs) { 149 | unimplemented!() 150 | } 151 | 152 | #[candid_method(update)] 153 | fn list_canister_snapshots(_: ListCanisterSnapshotsArgs) -> ListCanisterSnapshotsResult { 154 | unimplemented!() 155 | } 156 | 157 | #[candid_method(update)] 158 | fn delete_canister_snapshot(_: DeleteCanisterSnapshotArgs) { 159 | unimplemented!() 160 | } 161 | 162 | #[candid_method(update)] 163 | fn read_canister_snapshot_metadata( 164 | _: ReadCanisterSnapshotMetadataArgs, 165 | ) -> ReadCanisterSnapshotMetadataResult { 166 | unimplemented!() 167 | } 168 | 169 | #[candid_method(update)] 170 | fn read_canister_snapshot_data(_: ReadCanisterSnapshotDataArgs) -> ReadCanisterSnapshotDataResult { 171 | unimplemented!() 172 | } 173 | 174 | #[candid_method(update)] 175 | fn upload_canister_snapshot_metadata( 176 | _: UploadCanisterSnapshotMetadataArgs, 177 | ) -> UploadCanisterSnapshotMetadataResult { 178 | unimplemented!() 179 | } 180 | 181 | #[candid_method(update)] 182 | fn upload_canister_snapshot_data(_: UploadCanisterSnapshotDataArgs) { 183 | unimplemented!() 184 | } 185 | 186 | #[candid_method(query)] 187 | fn fetch_canister_logs(_: FetchCanisterLogsArgs) -> FetchCanisterLogsResult { 188 | unimplemented!() 189 | } 190 | 191 | #[cfg(test)] 192 | mod test { 193 | use candid_parser::utils::{CandidSource, service_equal}; 194 | use ic_management_canister_types::*; 195 | 196 | #[test] 197 | fn candid_equality_test() { 198 | let declared_interface_str = 199 | std::fs::read_to_string("tests/ic.did").expect("Failed to read ic.did file"); 200 | let filtered_interface_str = declared_interface_str 201 | .lines() 202 | // Bitcoin APIs are deprecated from the management canister, so we filter them out. 203 | .filter(|line| !line.trim_start().starts_with("bitcoin_")) 204 | .collect::>() 205 | .join("\n"); 206 | let declared_interface = CandidSource::Text(&filtered_interface_str); 207 | 208 | candid::export_service!(); 209 | let implemented_interface_str = __export_service(); 210 | let implemented_interface = CandidSource::Text(&implemented_interface_str); 211 | 212 | let result = service_equal(declared_interface, implemented_interface); 213 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /ic0/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [unreleased] 9 | 10 | ## [1.0.1] - 2025-09-11 11 | 12 | ### Added 13 | 14 | - Added `env_var_*` API. 15 | 16 | ## [1.0.0] - 2025-06-25 17 | 18 | ### Changed 19 | 20 | - Introduced new safe API in the crate root. The raw C bindings have been moved to `ic0::sys`. 21 | 22 | ### Migration guide 23 | 24 | - Replace `ic0::*` with `ic0::sys::*`, e.g., `ic0::msg_arg_data_size()` -> `ic0::sys::msg_arg_data_size()`. 25 | 26 | ## [0.25.1] - 2025-06-04 27 | 28 | Changelog introduced 29 | -------------------------------------------------------------------------------- /ic0/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic0" 3 | version = "1.0.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Internet Computer System API Binding." 10 | readme = "README.md" 11 | categories = ["api-bindings", "development-tools::ffi"] 12 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 13 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 14 | 15 | [dev-dependencies] 16 | quote.workspace = true 17 | syn = { workspace = true, features = ["parsing", "full", "extra-traits"] } 18 | 19 | # This is not a real example but a utility for auto-generating ic0.rs 20 | [[example]] 21 | name = "ic0build" 22 | path = "util/ic0build.rs" 23 | 24 | [[example]] 25 | name = "work" 26 | path = "util/work.rs" 27 | -------------------------------------------------------------------------------- /ic0/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic0/README.md: -------------------------------------------------------------------------------- 1 | # ic0 2 | 3 | Internet Computer System API binding. 4 | 5 | ## What 6 | 7 | `ic0` is simply a safe Rust translation of the System API as described in the [IC interface specification][1]. The unsafe direct imports can be found in the `ic0::sys` module. 8 | 9 | ## Update 10 | 11 | `ic0` keeps in step with the IC interface specification. Particularly, `ic0` is directly generated from the [system API][1] in that repo. 12 | 13 | When interface-spec releases a new version that modify [system API][1]: 14 | 15 | 1. replace `ic0.txt` in the root of this project; 16 | 2. copy any new function headers to `manual_safety_comments.txt`, and add a safety comment for the function; 17 | 3. execute `cargo run --example=ic0build`; 18 | 19 | `src/sys.rs` should be updated. 20 | 21 | [1]: https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-imports 22 | -------------------------------------------------------------------------------- /ic0/ic0.txt: -------------------------------------------------------------------------------- 1 | ic0.msg_arg_data_size : () -> I; // I U RQ NRQ CQ Ry CRy F 2 | ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) -> (); // I U RQ NRQ CQ Ry CRy F 3 | ic0.msg_caller_size : () -> I; // * 4 | ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // * 5 | ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt 6 | ic0.msg_reject_msg_size : () -> I ; // Rt CRt 7 | ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) -> (); // Rt CRt 8 | 9 | ic0.msg_deadline : () -> i64; // U Q CQ Ry Rt CRy CRt 10 | 11 | ic0.msg_reply_data_append : (src : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt 12 | ic0.msg_reply : () -> (); // U RQ NRQ CQ Ry Rt CRy CRt 13 | ic0.msg_reject : (src : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt 14 | 15 | ic0.msg_cycles_available128 : (dst : I) -> (); // U RQ Rt Ry 16 | ic0.msg_cycles_refunded128 : (dst : I) -> (); // Rt Ry 17 | ic0.msg_cycles_accept128 : (max_amount_high : i64, max_amount_low: i64, dst : I) 18 | -> (); // U RQ Rt Ry 19 | 20 | ic0.cycles_burn128 : (amount_high : i64, amount_low : i64, dst : I) 21 | -> (); // I G U RQ Ry Rt C T 22 | 23 | ic0.canister_self_size : () -> I; // * 24 | ic0.canister_self_copy : (dst : I, offset : I, size : I) -> (); // * 25 | ic0.canister_cycle_balance128 : (dst : I) -> (); // * 26 | ic0.canister_liquid_cycle_balance128 : (dst : I) -> (); // * 27 | ic0.canister_status : () -> i32; // * 28 | ic0.canister_version : () -> i64; // * 29 | 30 | ic0.subnet_self_size : () -> I; // * 31 | ic0.subnet_self_copy : (dst : I, offset : I, size : I) -> (); // * 32 | 33 | ic0.msg_method_name_size : () -> I; // F 34 | ic0.msg_method_name_copy : (dst : I, offset : I, size : I) -> (); // F 35 | ic0.accept_message : () -> (); // F 36 | 37 | ic0.call_new : 38 | ( callee_src : I, 39 | callee_size : I, 40 | name_src : I, 41 | name_size : I, 42 | reply_fun : I, 43 | reply_env : I, 44 | reject_fun : I, 45 | reject_env : I 46 | ) -> (); // U CQ Ry Rt CRy CRt T 47 | ic0.call_on_cleanup : (fun : I, env : I) -> (); // U CQ Ry Rt CRy CRt T 48 | ic0.call_data_append : (src : I, size : I) -> (); // U CQ Ry Rt CRy CRt T 49 | ic0.call_with_best_effort_response : (timeout_seconds : i32) -> (); // U CQ Ry Rt CRy CRt T 50 | ic0.call_cycles_add128 : (amount_high : i64, amount_low: i64) -> (); // U Ry Rt T 51 | ic0.call_perform : () -> ( err_code : i32 ); // U CQ Ry Rt CRy CRt T 52 | 53 | ic0.stable64_size : () -> (page_count : i64); // * s 54 | ic0.stable64_grow : (new_pages : i64) -> (old_page_count : i64); // * s 55 | ic0.stable64_write : (offset : i64, src : i64, size : i64) -> (); // * s 56 | ic0.stable64_read : (dst : i64, offset : i64, size : i64) -> (); // * s 57 | 58 | ic0.root_key_size : () -> I; // I G U RQ Ry Rt C T 59 | ic0.root_key_copy : (dst : I, offset : I, size : I) -> (); // I G U RQ Ry Rt C T 60 | ic0.certified_data_set : (src : I, size : I) -> (); // I G U Ry Rt T 61 | ic0.data_certificate_present : () -> i32; // * 62 | ic0.data_certificate_size : () -> I; // NRQ CQ 63 | ic0.data_certificate_copy : (dst : I, offset : I, size : I) -> (); // NRQ CQ 64 | 65 | ic0.time : () -> (timestamp : i64); // * 66 | ic0.global_timer_set : (timestamp : i64) -> i64; // I G U Ry Rt C T 67 | ic0.performance_counter : (counter_type : i32) -> (counter : i64); // * s 68 | ic0.is_controller : (src : I, size : I) -> ( result : i32); // * s 69 | ic0.in_replicated_execution : () -> (result : i32); // * s 70 | 71 | ic0.cost_call : (method_name_size: i64, payload_size : i64, dst : I) -> (); // * s 72 | ic0.cost_create_canister : (dst : I) -> (); // * s 73 | ic0.cost_http_request : (request_size : i64, max_res_bytes : i64, dst : I) -> (); // * s 74 | ic0.cost_sign_with_ecdsa : (src : I, size : I, ecdsa_curve: i32, dst : I) -> i32; // * s 75 | ic0.cost_sign_with_schnorr : (src : I, size : I, algorithm: i32, dst : I) -> i32; // * s 76 | ic0.cost_vetkd_derive_key : (src : I, size : I, vetkd_curve: i32, dst : I) -> i32; // * s 77 | 78 | ic0.env_var_count : () -> I; // * 79 | 80 | ic0.env_var_name_size : (index: I) -> I; // * 81 | ic0.env_var_name_copy : (index: I, dst: I, offset: I, size: I) -> (); // * 82 | ic0.env_var_name_exists : (name_src: I, name_size: I) -> i32; // * 83 | 84 | ic0.env_var_value_size : (name_src: I, name_size: I) -> I; // * 85 | ic0.env_var_value_copy : (name_src: I, name_size: I, dst: I, offset: I, size: I) -> (); // * 86 | 87 | ic0.debug_print : (src : I, size : I) -> (); // * s 88 | ic0.trap : (src : I, size : I) -> (); // * s 89 | -------------------------------------------------------------------------------- /ic0/util/work.rs: -------------------------------------------------------------------------------- 1 | // This file should compile on windows 2 | fn main() { 3 | unsafe { 4 | ic0::sys::trap(0, 0); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/.gitignore: -------------------------------------------------------------------------------- 1 | !Cargo.lock 2 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [0.3.5] - 2022-09-15 8 | This is the final release of this deprecated tool. 9 | 10 | ### Added 11 | - Specifying `-` as the input or output argument refers to stdin or stdout respectively (#230) 12 | 13 | ### Changed 14 | - Update clap to 3.1 (#209) 15 | - The output argument defaults to the input argument if unspecified (#230) 16 | 17 | ## [0.3.4] - 2022-02-07 18 | ### Fixed 19 | - Actually print version with --version (#196) 20 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "ic-cdk-optimizer" 5 | version = "0.3.5" 6 | authors = ["DFINITY Stiftung "] 7 | edition = "2021" 8 | description = "WASM Optimizer for the IC CDK (experimental)." 9 | homepage = "https://docs.rs/ic-cdk-optimizer" 10 | documentation = "https://docs.rs/ic-cdk-optimizer" 11 | license = "Apache-2.0" 12 | readme = "README.md" 13 | categories = ["api-bindings", "data-structures", "no-std", "command-line-utilities", "wasm"] 14 | keywords = ["internet-computer", "dfinity", "canister", "cli", "optimizer"] 15 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 16 | repository = "https://github.com/dfinity/cdk-rs" 17 | rust-version = "1.60.0" 18 | 19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 20 | 21 | [dependencies] 22 | binaryen = "0.12.0" 23 | clap = { version = "3.1.0", features = ["derive"] } 24 | humansize = "1.1.0" 25 | wabt = "0.10.0" 26 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | This tool is deprecated since Sep 2022. The last release of it was [v0.3.5](https://github.com/dfinity/cdk-rs/releases/tag/0.3.5). 3 | 4 | 5 | Use ic-wasm shrink instead to reduce size of WASM modules for Internet Computer. Check [ic-wasm](https://github.com/dfinity/ic-wasm) repo here. 6 | 7 | ## ic-cdk-optimizer 8 | Optimizer to reduce the size of CDK WASMs. 9 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use humansize::{file_size_opts, FileSize}; 3 | use std::io::{Read, Write}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | mod passes; 7 | 8 | #[derive(Parser, Debug)] 9 | #[clap(version)] 10 | struct CommandLineOpts { 11 | /// Input file to optimize. By default, or if "-", will use STDIN. 12 | #[clap(default_value("-"))] 13 | input: PathBuf, 14 | 15 | /// Output file. If unset, the original file will be overwritten. If "-", or if unset and the original was passed via STDIN, the result will go to STDOUT. 16 | #[clap(short, long)] 17 | output: Option, 18 | } 19 | 20 | fn main() { 21 | let passes = passes::create(); 22 | let opts = CommandLineOpts::parse(); 23 | let content = if opts.input != Path::new("-") { 24 | std::fs::read(&opts.input).expect("Could not read the file.") 25 | } else { 26 | let mut buff = Vec::new(); 27 | std::io::stdin() 28 | .read_to_end(&mut buff) 29 | .expect("Could not read STDIN."); 30 | buff 31 | }; 32 | 33 | eprintln!( 34 | "Original: {:>8}", 35 | content.len().file_size(file_size_opts::BINARY).unwrap() 36 | ); 37 | 38 | let original_wasm_size = content.len(); 39 | let mut wasm_size = content.len(); 40 | let mut wasm_back = content; 41 | 42 | for pass in passes { 43 | eprintln!("{}...", pass.description()); 44 | let new_wasm = pass.opt(&wasm_back).expect("Pass failed:"); 45 | if new_wasm.len() < wasm_back.len() { 46 | wasm_back = new_wasm; 47 | eprintln!( 48 | " Size: {:>8} ({:3.1}% smaller)", 49 | wasm_back.len().file_size(file_size_opts::BINARY).unwrap(), 50 | (1.0 - ((wasm_back.len() as f64) / (wasm_size as f64))) * 100.0 51 | ); 52 | } else { 53 | eprintln!("Pass did not result in smaller WASM... Skipping."); 54 | } 55 | wasm_size = wasm_back.len(); 56 | } 57 | 58 | eprintln!( 59 | "\nFinal Size: {} ({:3.1}% smaller)", 60 | wasm_back.len().file_size(file_size_opts::BINARY).unwrap(), 61 | (1.0 - ((wasm_back.len() as f64) / (original_wasm_size as f64))) * 100.0 62 | ); 63 | let outfile = opts.output.unwrap_or(opts.input); 64 | if outfile == Path::new("-") { 65 | std::io::stdout() 66 | .write_all(&wasm_back) 67 | .expect("Could not write output."); 68 | } else { 69 | std::fs::write(outfile, wasm_back).expect("Could not write output file."); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/src/passes.rs: -------------------------------------------------------------------------------- 1 | use wabt::{wasm2wat, wat2wasm}; 2 | 3 | mod binaryen; 4 | 5 | pub type PassResult = Result, Box>; 6 | 7 | pub trait OptimizationPass { 8 | fn short_name(&self) -> String; 9 | fn description(&self) -> String; 10 | fn opt(&self, wasm: &[u8]) -> PassResult; 11 | } 12 | 13 | struct RemoveDebugSymbolsPass; 14 | 15 | impl OptimizationPass for RemoveDebugSymbolsPass { 16 | fn short_name(&self) -> String { 17 | String::from("strip_data") 18 | } 19 | 20 | fn description(&self) -> String { 21 | String::from("Stripping Unused Data Segments") 22 | } 23 | 24 | fn opt(&self, wasm: &[u8]) -> PassResult { 25 | let wat = wasm2wat(&wasm)?; 26 | Ok(wat2wasm(&wat)?) 27 | } 28 | } 29 | 30 | pub fn create() -> Vec> { 31 | vec![ 32 | Box::new(RemoveDebugSymbolsPass), 33 | Box::new(binaryen::BinaryenPass), 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /legacy/ic-cdk-optimizer/src/passes/binaryen.rs: -------------------------------------------------------------------------------- 1 | use crate::passes::{OptimizationPass, PassResult}; 2 | use binaryen::{CodegenConfig, Module}; 3 | 4 | pub struct BinaryenPass; 5 | 6 | impl OptimizationPass for BinaryenPass { 7 | fn short_name(&self) -> String { 8 | String::from("binaryen") 9 | } 10 | 11 | fn description(&self) -> String { 12 | String::from("Execute a binaryen optimization pass on your WASM.") 13 | } 14 | 15 | fn opt(&self, wasm: &[u8]) -> PassResult { 16 | let mut module = 17 | Module::read(wasm).map_err(|_| String::from("Could not load module..."))?; 18 | 19 | module.optimize(&CodegenConfig { 20 | debug_info: false, 21 | optimization_level: 2, 22 | shrink_level: 2, 23 | }); 24 | 25 | Ok(module.write()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /legacy/ic-certified-assets/README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | The `ic-certified-assets` crate has been moved to the [sdk](https://github.com/dfinity/sdk) repo. 4 | -------------------------------------------------------------------------------- /legacy/ic-response-codes/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "ic-response-codes" 5 | version = "0.1.2" 6 | authors = ["DFINITY Stiftung "] 7 | edition = "2021" 8 | repository = "https://github.com/dfinity/cdk-rs" 9 | rust-version = "1.75.0" 10 | license = "Apache-2.0" 11 | description = "Internet Computer Response Codes" 12 | homepage = "https://docs.rs/ic-response-codes" 13 | documentation = "https://docs.rs/ic-response-codes" 14 | readme = "README.md" 15 | categories = ["api-bindings", "data-structures"] 16 | keywords = ["internet-computer", "types", "dfinity"] 17 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 18 | 19 | [dependencies] 20 | -------------------------------------------------------------------------------- /legacy/ic-response-codes/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /legacy/ic-response-codes/README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > 3 | > This library is discontinued and will not be updated. 4 | > 5 | > Please use [`ic-error-types`](https://crates.io/crates/ic-error-types) instead. 6 | 7 | # ic-response-codes 8 | 9 | The codes in the response of Internet Computer API request or inter-canister call. 10 | -------------------------------------------------------------------------------- /legacy/ic-response-codes/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use std::error::Error; 4 | use std::fmt::Display; 5 | 6 | /// Classifies why an API request or inter-canister call in the IC is rejected. 7 | /// 8 | /// # Note 9 | /// 10 | /// Zero (0) is not a valid reject code. 11 | /// Converting 0 into this enum will return an error. 12 | /// 13 | /// See [Reject codes](https://internetcomputer.org/docs/current/references/ic-interface-spec/#reject-codes) for more details. 14 | #[repr(u32)] 15 | #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)] 16 | pub enum RejectCode { 17 | /// Fatal system error, retry unlikely to be useful. 18 | SysFatal = 1, 19 | /// Transient system error, retry might be possible. 20 | SysTransient = 2, 21 | /// Invalid destination (e.g. canister/account does not exist). 22 | DestinationInvalid = 3, 23 | /// Explicit reject by the canister. 24 | CanisterReject = 4, 25 | /// Canister error (e.g., trap, no response). 26 | CanisterError = 5, 27 | /// Response unknown; system stopped waiting for it (e.g., timed out, or system under high load). 28 | SysUnknown = 6, 29 | 30 | /// Unrecognized reject code. 31 | /// 32 | /// # Note 33 | /// 34 | /// This variant is not part of the IC interface spec, and is used to represent 35 | /// reject codes that are not recognized by the library. 36 | /// 37 | /// This variant is needed just in case the IC introduces new reject codes in the future. 38 | /// If that happens, a Canister using existing library versions will still be able to convert 39 | /// the new reject codes to this variant without panicking. 40 | Unrecognized(u32), 41 | } 42 | 43 | impl Display for RejectCode { 44 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 45 | match self { 46 | RejectCode::SysFatal => write!(f, "SysFatal(1)"), 47 | RejectCode::SysTransient => write!(f, "SysTransient(2)"), 48 | RejectCode::DestinationInvalid => write!(f, "DestinationInvalid(3)"), 49 | RejectCode::CanisterReject => write!(f, "CanisterReject(4)"), 50 | RejectCode::CanisterError => write!(f, "CanisterError(5)"), 51 | RejectCode::SysUnknown => write!(f, "SysUnknown(6)"), 52 | RejectCode::Unrecognized(code) => write!(f, "Unrecognized({})", code), 53 | } 54 | } 55 | } 56 | 57 | /// Error type for [`RejectCode`] conversion. 58 | /// 59 | /// The only case where this error can occur is when trying to convert a 0 to a [`RejectCode`]. 60 | #[derive(Clone, Copy, Debug)] 61 | pub struct ZeroIsInvalidRejectCode; 62 | 63 | impl Display for ZeroIsInvalidRejectCode { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | write!(f, "zero is invalid reject code") 66 | } 67 | } 68 | 69 | impl Error for ZeroIsInvalidRejectCode {} 70 | 71 | impl TryFrom for RejectCode { 72 | type Error = ZeroIsInvalidRejectCode; 73 | 74 | fn try_from(code: u32) -> Result { 75 | match code { 76 | 0 => Err(ZeroIsInvalidRejectCode), 77 | 1 => Ok(RejectCode::SysFatal), 78 | 2 => Ok(RejectCode::SysTransient), 79 | 3 => Ok(RejectCode::DestinationInvalid), 80 | 4 => Ok(RejectCode::CanisterReject), 81 | 5 => Ok(RejectCode::CanisterError), 82 | 6 => Ok(RejectCode::SysUnknown), 83 | _ => Ok(RejectCode::Unrecognized(code)), 84 | } 85 | } 86 | } 87 | 88 | impl From for u32 { 89 | fn from(code: RejectCode) -> u32 { 90 | match code { 91 | RejectCode::SysFatal => 1, 92 | RejectCode::SysTransient => 2, 93 | RejectCode::DestinationInvalid => 3, 94 | RejectCode::CanisterReject => 4, 95 | RejectCode::CanisterError => 5, 96 | RejectCode::SysUnknown => 6, 97 | RejectCode::Unrecognized(code) => code, 98 | } 99 | } 100 | } 101 | 102 | impl PartialEq for RejectCode { 103 | fn eq(&self, other: &u32) -> bool { 104 | let self_as_u32: u32 = (*self).into(); 105 | self_as_u32 == *other 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /library/ic-certified-map/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## [0.4.1] - 2025-09-05 10 | 11 | ### Added 12 | - Implement `CandidType`, `Serialize`, and `Deserialize` for the `RbTree`. 13 | - Implement `Deserialize` for the `HashTree`. 14 | 15 | ## [0.4.0] - 2023-07-13 16 | 17 | ### Changed 18 | - Upgrade `ic-cdk` to v0.10 and `candid` to v0.9. 19 | 20 | ## [0.3.4] - 2023-03-01 21 | ### Added 22 | - Derive common traits for structs. 23 | 24 | ## [0.3.3] - 2023-02-22 25 | ### Fixed 26 | - Update links in doc. 27 | 28 | ## [0.3.2] - 2022-11-10 29 | ### Changed 30 | - Make `RbTree::new` and `RbTree::is_empty` both `const`. 31 | 32 | ## [0.3.1] - 2022-09-16 33 | ### Changed 34 | - Updated `sha2` dependency. 35 | 36 | ## [0.3.0] - 2022-01-13 37 | ### Added 38 | - `RbTree::iter()` method. 39 | - impls of `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromIterator`, and `Debug` for `RbTree`. 40 | 41 | ## [0.2.0] - 2021-09-16 42 | ### Added 43 | - `RbTree::value_range()` method to get a witness for a range of keys with values. 44 | 45 | ### Changed 46 | - RbTree::key_range() method returns tighter key bounds which reduces the size of witnesses. 47 | - Updated the version of candid from `0.6.19` to `0.7.1` ([#72](https://github.com/dfinity/cdk-rs/pull/72)). 48 | - Hash tree leaves can now hold both references and values ([#121](https://github.com/dfinity/cdk-rs/issues/121)). 49 | This is a BREAKING CHANGE, some clients might need to slightly change and recompile their code. 50 | 51 | ## [0.1.0] - 2021-05-04 52 | ### Added 53 | * Initial release of the library. 54 | -------------------------------------------------------------------------------- /library/ic-certified-map/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-certified-map" 3 | version = "0.4.1" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Merkleized map data structure." 10 | homepage = "https://docs.rs/ic-certified-map" 11 | documentation = "https://docs.rs/ic-certified-map" 12 | readme = "README.md" 13 | categories = [ 14 | "data-structures", 15 | "cryptography", 16 | "cryptography::cryptocurrencies", 17 | ] 18 | keywords = ["internet-computer", "types", "dfinity", "map"] 19 | include = ["src", "Cargo.toml", "CHANGELOG.md", "LICENSE", "README.md"] 20 | 21 | [dependencies] 22 | serde.workspace = true 23 | serde_bytes.workspace = true 24 | sha2.workspace = true 25 | candid.workspace = true 26 | 27 | [dev-dependencies] 28 | hex.workspace = true 29 | serde_cbor = "0.11" 30 | ic-cdk.workspace = true 31 | bincode = "1.3.3" 32 | -------------------------------------------------------------------------------- /library/ic-certified-map/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /library/ic-certified-map/README.md: -------------------------------------------------------------------------------- 1 | # Certified Map 2 | 3 | This package provides a map that can be used by Internet Computer canisters to implement _certified queries_. 4 | 5 | ## Features 6 | 7 | * Incremental certification. 8 | The canister can store thousands of entries while keeping the cost of certification relatively low. 9 | 10 | * Proofs of absence. 11 | If the requested key is not present in the map, the returned tree structure allows the caller to verify that fact. 12 | 13 | * Relatively small merkle proofs. 14 | The size overhead of the certificate is O(log N), where N is the number of entries in the map. 15 | 16 | ## Implementation Details 17 | 18 | The canister uses an augmented Red-Black binary search tree to store the entries. 19 | Each node of the search tree is annotated with the root hash of the hash tree built from the subtree rooted at this node. 20 | Each time the tree is rotated or modified, the corresponding hashes are recomputed in O(1) time. 21 | -------------------------------------------------------------------------------- /library/ic-certified-map/src/hashtree/test.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | HashTree::{Empty, Leaf}, 3 | fork, labeled, 4 | }; 5 | use std::borrow::Cow; 6 | 7 | //─┬─┬╴"a" ─┬─┬╴"x" ─╴"hello" 8 | // │ │ │ └╴Empty 9 | // │ │ └╴ "y" ─╴"world" 10 | // │ └╴"b" ──╴"good" 11 | // └─┬╴"c" ──╴Empty 12 | // └╴"d" ──╴"morning" 13 | #[test] 14 | fn test_public_spec_example() { 15 | let t = fork( 16 | fork( 17 | labeled( 18 | b"a", 19 | fork( 20 | fork(labeled(b"x", Leaf(Cow::Borrowed(b"hello"))), Empty), 21 | labeled(b"y", Leaf(Cow::Borrowed(b"world"))), 22 | ), 23 | ), 24 | labeled(b"b", Leaf(Cow::Borrowed(b"good"))), 25 | ), 26 | fork( 27 | labeled(b"c", Empty), 28 | labeled(b"d", Leaf(Cow::Borrowed(b"morning"))), 29 | ), 30 | ); 31 | 32 | let root = t.reconstruct(); 33 | assert_eq!( 34 | hex::encode(&root[..]), 35 | "eb5c5b2195e62d996b84c9bcc8259d19a83786a2f59e0878cec84c811f669aa0".to_string() 36 | ); 37 | 38 | let cbor = serde_cbor::to_vec(&t).unwrap(); 39 | let encoded = hex::encode(&cbor[..]); 40 | assert_eq!( 41 | encoded, 42 | "8301830183024161830183018302417882034568656c6c6f810083024179820345776f726c6483024162820344676f6f648301830241638100830241648203476d6f726e696e67".to_string()); 43 | 44 | let decoded = hex::decode(encoded).unwrap(); 45 | let de: super::HashTree<'_> = serde_cbor::from_slice(&decoded).unwrap(); 46 | 47 | assert_eq!(de.reconstruct(), root); 48 | assert_eq!(serde_cbor::to_vec(&de).unwrap(), cbor); 49 | } 50 | -------------------------------------------------------------------------------- /library/ic-certified-map/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This package provides a map backed by a Merkle tree that can be used 2 | //! by Internet Computer canisters to implement certified queries. 3 | //! 4 | //! You can certify your data by using the [`RbTree`] type as a map of 5 | //! known names to the values you want certified. After you record its 6 | //! [`root_hash`](RbTree::root_hash) into your canister's [certified data], 7 | //! query calls can access a [data certificate] proving that the IC certified 8 | //! the hash, under the [path] `/canister//certified_data`. 9 | //! By providing this certificate, as well as a [`witness`](RbTree::witness) 10 | //! that the value exists in the hash, you can then prove to the caller that 11 | //! the IC certified the data. 12 | //! 13 | //! [certified data]: https://docs.rs/ic-cdk/latest/ic_cdk/api/fn.set_certified_data.html 14 | //! [data certificate]: https://docs.rs/ic-cdk/latest/ic_cdk/api/fn.data_certificate.html 15 | //! [path]: https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree 16 | //! 17 | //! # Example 18 | //! 19 | //! ``` 20 | //! # use std::cell::*; 21 | //! # use ic_cdk::*; 22 | //! # use ic_certified_map::*; 23 | //! # use candid::CandidType; 24 | //! # use serde::Serialize; 25 | //! 26 | //! thread_local! { 27 | //! static COUNTER: Cell = Cell::new(0); 28 | //! static TREE: RefCell> = RefCell::new(RbTree::new()); 29 | //! } 30 | //! 31 | //! #[update] 32 | //! fn inc() { 33 | //! let count = COUNTER.with(|counter| { 34 | //! let count = counter.get() + 1; 35 | //! counter.set(count); 36 | //! count 37 | //! }); 38 | //! TREE.with(|tree| { 39 | //! let mut tree = tree.borrow_mut(); 40 | //! tree.insert("counter", leaf_hash(&count.to_be_bytes())); 41 | //! ic_cdk::api::set_certified_data(&tree.root_hash()); 42 | //! }) 43 | //! } 44 | //! 45 | //! #[derive(CandidType)] 46 | //! struct CertifiedCounter { 47 | //! count: i32, 48 | //! certificate: Vec, 49 | //! witness: Vec, 50 | //! } 51 | //! 52 | //! #[query] 53 | //! fn get() -> CertifiedCounter { 54 | //! let certificate = ic_cdk::api::data_certificate().expect("No data certificate available"); 55 | //! let witness = TREE.with(|tree| { 56 | //! let tree = tree.borrow(); 57 | //! let mut witness = vec![]; 58 | //! let mut witness_serializer = serde_cbor::Serializer::new(&mut witness); 59 | //! witness_serializer.self_describe(); 60 | //! tree.witness(b"counter").serialize(&mut witness_serializer).unwrap(); 61 | //! witness 62 | //! }); 63 | //! let count = COUNTER.with(|counter| counter.get()); 64 | //! CertifiedCounter { 65 | //! count, 66 | //! certificate, 67 | //! witness, 68 | //! } 69 | //! } 70 | //! ``` 71 | 72 | #![warn( 73 | elided_lifetimes_in_paths, 74 | missing_debug_implementations, 75 | missing_docs, 76 | unsafe_op_in_unsafe_fn, 77 | clippy::undocumented_unsafe_blocks, 78 | clippy::missing_safety_doc 79 | )] 80 | 81 | mod hashtree; 82 | mod rbtree; 83 | 84 | pub use crate::hashtree::*; 85 | pub use crate::rbtree::*; 86 | -------------------------------------------------------------------------------- /library/ic-ledger-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [unreleased] 8 | 9 | ## [0.15.0] - 2025-04-22 10 | 11 | ### Changed 12 | 13 | - Upgrade `ic-cdk` to v0.18. 14 | 15 | ## [0.14.0] - 2024-11-08 16 | 17 | ### Changed 18 | 19 | - Upgrade `ic-cdk` to v0.17. 20 | 21 | ### Added 22 | - as_bytes method to AccountIdentifier in ic-ledger-types 23 | 24 | ## [0.13.0] - 2024-08-27 25 | 26 | ### Changed 27 | 28 | - Upgrade `ic-cdk` to v0.16. 29 | 30 | ## [0.12.0] - 2024-07-01 31 | 32 | ### Changed 33 | 34 | - Upgrade `ic-cdk` to v0.15. 35 | 36 | ## [0.11.0] - 2024-05-17 37 | 38 | ### Changed 39 | 40 | - Upgrade `ic-cdk` to v0.14. 41 | 42 | ## [0.10.0] - 2024-03-01 43 | 44 | ### Changed 45 | - Upgrade `ic-cdk` to v0.13. 46 | 47 | ## [0.9.0] - 2023-11-23 48 | 49 | ### Changed 50 | - Upgrade `ic-cdk` to v0.12 and `candid` to v0.10. 51 | 52 | ## [0.8.0] - 2023-09-18 53 | 54 | ### Changed 55 | - Upgrade `ic-cdk` to v0.11. 56 | 57 | ## [0.7.0] - 2023-07-13 58 | 59 | ### Added 60 | - from_hex/from_slice/to_hex methods to AccountIdentifier in ic-ledger-types 61 | 62 | ### Changed 63 | - Upgrade `ic-cdk` to v0.10 and `candid` to v0.9. 64 | 65 | ## [0.6.0] - 2023-06-20 66 | ### Changed 67 | - Upgrade `ic-cdk` to v0.9. 68 | 69 | ## [0.5.0] - 2023-05-26 70 | ### Changed 71 | - Upgrade `ic-cdk` to v0.8. 72 | 73 | ## [0.4.2] - 2023-03-01 74 | ### Fixed 75 | - Fill missing docs. 76 | 77 | ## [0.4.1] - 2023-02-22 78 | ### Fixed 79 | - Use automatic link in document. 80 | 81 | ## [0.4.0] - 2023-02-13 82 | ### Changed 83 | - Extend the Operation type to support approve/transfer_from transactions. 84 | 85 | ## [0.3.0] - 2023-02-03 86 | ### Changed 87 | - Upgrade `ic-cdk` to v0.7. 88 | 89 | ## [0.2.1] - 2023-01-20 90 | 91 | ### Added 92 | 93 | - Implemented `From` for `Subaccount` (#361) 94 | 95 | ## [0.2.0] - 2022-11-04 96 | ### Changed 97 | - Upgrade `ic-cdk` to v0.6 and `candid` to v0.8. 98 | 99 | ## [0.1.2] - 2022-05-31 100 | ### Added 101 | - Integrate with the ledger's `token_symbol` method 102 | - Methods to query ledger blocks. 103 | 104 | ### Changed 105 | - Support conversion from `[u8; 32]` to `AccountIdentifier` via `TryFrom` with CRC-32 check. 106 | - Upgrade `ic-cdk` to 0.5.0 107 | 108 | ## [0.1.1] - 2022-02-04 109 | ### Changed 110 | - Upgrade `ic-cdk` to v0.4.0. 111 | 112 | ## [0.1.0] - 2021-11-11 113 | ### Added 114 | - Initial release of the library. 115 | -------------------------------------------------------------------------------- /library/ic-ledger-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-ledger-types" 3 | version = "0.15.0" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | rust-version.workspace = true 8 | repository.workspace = true 9 | description = "Types for interacting with the ICP ledger canister." 10 | homepage = "https://docs.rs/ic-ledger-types" 11 | documentation = "https://docs.rs/ic-ledger-types" 12 | readme = "README.md" 13 | keywords = ["internet-computer", "ledger"] 14 | categories = ["cryptography::cryptocurrencies", "data-structures"] 15 | include = ["src", "Cargo.toml", "CHANGELOG.md", "LICENSE", "README.md"] 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | ic-cdk.workspace = true 21 | candid.workspace = true 22 | crc32fast = "1.2.0" 23 | hex.workspace = true 24 | serde.workspace = true 25 | serde_bytes.workspace = true 26 | sha2.workspace = true 27 | -------------------------------------------------------------------------------- /library/ic-ledger-types/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /library/ic-ledger-types/README.md: -------------------------------------------------------------------------------- 1 | # ICP Ledger types 2 | 3 | A library of types to communicate with the ICP ledger canister. 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # This is not the MSRV, we just want to use the latest stable Rust for development 3 | # MSRV is defined in the `Cargo.toml` file 4 | channel = "stable" 5 | targets = ["wasm32-unknown-unknown"] 6 | components = ["rustfmt", "clippy"] 7 | -------------------------------------------------------------------------------- /scripts/download_pocket_ic_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | uname_sys=$(uname -s | tr '[:upper:]' '[:lower:]') 6 | echo "uname_sys: $uname_sys" 7 | 8 | SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | cd "$SCRIPTS_DIR/../e2e-tests" 10 | # extract the tag from e2e-tests/Cargo.toml 11 | tag=$(grep -E 'pocket-ic.*tag' Cargo.toml | sed -n "s/.*tag *= *\"\([^\"]*\)\".*/\1/p") 12 | 13 | ARTIFACTS_DIR="$SCRIPTS_DIR/../target/e2e-tests-artifacts" 14 | mkdir -p "$ARTIFACTS_DIR" 15 | cd "$ARTIFACTS_DIR" 16 | echo -n "$tag" > pocket-ic-tag 17 | curl -sL "https://github.com/dfinity/ic/releases/download/$tag/pocket-ic-x86_64-$uname_sys.gz" --output pocket-ic.gz 18 | gzip -df pocket-ic.gz 19 | chmod a+x pocket-ic 20 | ./pocket-ic --version 21 | 22 | if [[ "$uname_sys" == "darwin" ]]; then 23 | xattr -dr com.apple.quarantine pocket-ic 24 | fi 25 | --------------------------------------------------------------------------------