├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── conventional-commits.yml ├── .gitignore ├── .gitmodules ├── .mergify.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── TROUBLESHOOTING.md ├── e2e-tests ├── Cargo.toml ├── build.rs ├── src │ └── bin │ │ ├── api.rs │ │ ├── async.rs │ │ ├── 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 │ ├── 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 │ ├── code_generator.rs │ └── lib.rs ├── ic-cdk-executor ├── Cargo.toml ├── build.rs └── src │ └── lib.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 │ ├── lib.rs │ ├── macros.rs │ ├── management_canister.rs │ ├── printer.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 ├── Cargo.toml ├── LICENSE ├── README.md ├── ic0.txt ├── src │ ├── ic0.rs │ └── lib.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/09883dd052911d2d0e243afdb7b5c5e45a3a55d5/.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 = "2" 14 | exclude = ["candid-extractor"] 15 | 16 | [workspace.package] 17 | authors = ["DFINITY Stiftung "] 18 | edition = "2021" 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.75.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 = "0.25.1" } 35 | ic-cdk = { path = "ic-cdk", version = "0.18.3" } 36 | ic-cdk-timers = { path = "ic-cdk-timers", version = "0.12.0" } 37 | ic-cdk-executor = { path = "ic-cdk-executor", version = "1.0.0" } 38 | ic-management-canister-types = { path = "ic-management-canister-types", version = "0.3.1" } 39 | 40 | candid = "0.10.13" # sync with the doc comment in ic-cdk/README.md 41 | candid_parser = "0.1.4" 42 | futures = "0.3" 43 | hex = "0.4" 44 | quote = "1" 45 | serde = "1" 46 | serde_bytes = "0.11" 47 | sha2 = "0.10" 48 | slotmap = "1" 49 | syn = "2" 50 | -------------------------------------------------------------------------------- /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 versions 0.18 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 | -------------------------------------------------------------------------------- /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_bytes.workspace = true 21 | sha2.workspace = true 22 | prost = "0.13.5" 23 | 24 | [dev-dependencies] 25 | candid_parser.workspace = true 26 | cargo_metadata = "0.19" 27 | futures = "0.3" 28 | hex.workspace = true 29 | ic-vetkd-utils = { git = "https://github.com/dfinity/ic", rev = "95231520" } 30 | pocket-ic = { git = "https://github.com/dfinity/ic", tag = "release-2025-05-30_03-21-base" } 31 | reqwest = "0.12" 32 | 33 | [build-dependencies] 34 | prost-build = "0.13.5" 35 | -------------------------------------------------------------------------------- /e2e-tests/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | fn main() -> Result<()> { 3 | prost_build::compile_protos(&["src/bin/macros/canister.proto"], &["src/"])?; 4 | Ok(()) 5 | } 6 | -------------------------------------------------------------------------------- /e2e-tests/src/bin/async.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use ic_cdk::call::Call; 3 | use ic_cdk::{query, update}; 4 | use lazy_static::lazy_static; 5 | use std::sync::RwLock; 6 | use std::time::Duration; 7 | 8 | lazy_static! { 9 | static ref RESOURCE: RwLock = RwLock::new(0); 10 | static ref NOTIFICATIONS_RECEIVED: RwLock = RwLock::new(0); 11 | } 12 | 13 | #[query] 14 | fn inc(n: u64) -> u64 { 15 | n + 1 16 | } 17 | 18 | #[query] 19 | fn invocation_count() -> u64 { 20 | let lock = RESOURCE 21 | .read() 22 | .unwrap_or_else(|_| ic_cdk::api::trap("failed to obtain a read lock")); 23 | *lock 24 | } 25 | 26 | #[update] 27 | #[allow(clippy::await_holding_lock)] 28 | async fn panic_after_async() { 29 | let mut lock = RESOURCE 30 | .write() 31 | .unwrap_or_else(|_| ic_cdk::api::trap("failed to obtain a write lock")); 32 | *lock += 1; 33 | let value = *lock; 34 | // Do not drop the lock before the await point. 35 | 36 | Call::bounded_wait(ic_cdk::api::canister_self(), "inc") 37 | .with_arg(value) 38 | .await 39 | .unwrap(); 40 | ic_cdk::api::trap("Goodbye, cruel world.") 41 | } 42 | 43 | #[update] 44 | #[allow(clippy::await_holding_lock)] 45 | async fn panic_twice() { 46 | let _lock = RESOURCE.write().unwrap(); 47 | let fut1 = async_then_panic(); 48 | let fut2 = async_then_panic(); 49 | futures::future::join_all([fut1, fut2]).await; 50 | } 51 | 52 | async fn async_then_panic() { 53 | Call::bounded_wait(ic_cdk::api::canister_self(), "on_notify") 54 | .await 55 | .unwrap(); 56 | panic!(); 57 | } 58 | 59 | #[query] 60 | fn notifications_received() -> u64 { 61 | *NOTIFICATIONS_RECEIVED.read().unwrap() 62 | } 63 | 64 | #[update] 65 | fn on_notify() { 66 | *NOTIFICATIONS_RECEIVED.write().unwrap() += 1; 67 | } 68 | 69 | #[update] 70 | fn notify(whom: Principal, method: String) { 71 | Call::bounded_wait(whom, method.as_str()) 72 | .oneway() 73 | .unwrap_or_else(|reject| { 74 | ic_cdk::api::trap(format!( 75 | "failed to notify (callee={}, method={}): {:?}", 76 | whom, method, reject 77 | )) 78 | }); 79 | } 80 | 81 | #[query] 82 | fn greet(name: String) -> String { 83 | format!("Hello, {}", name) 84 | } 85 | 86 | #[query(composite = true)] 87 | async fn greet_self(greeter: Principal) -> String { 88 | Call::bounded_wait(greeter, "greet") 89 | .with_arg("myself") 90 | .await 91 | .unwrap() 92 | .candid() 93 | .unwrap() 94 | } 95 | 96 | #[update] 97 | async fn invalid_reply_payload_does_not_trap() -> String { 98 | // We're decoding an integer instead of a string, decoding must fail. 99 | let result = Call::bounded_wait(ic_cdk::api::canister_self(), "greet") 100 | .with_arg("World") 101 | .await 102 | .unwrap() 103 | .candid::(); 104 | 105 | match result { 106 | Ok(_) => ic_cdk::api::trap("expected the decoding to fail"), 107 | Err(e) => format!("handled decoding error gracefully: {e}"), 108 | } 109 | } 110 | 111 | #[update] 112 | async fn await_channel_completion() -> String { 113 | let (tx, rx) = async_channel::bounded(1); 114 | ic_cdk::futures::spawn(async move { 115 | let greeting: String = Call::bounded_wait(ic_cdk::api::canister_self(), "greet") 116 | .with_arg("myself") 117 | .await 118 | .unwrap() 119 | .candid() 120 | .unwrap(); 121 | tx.send(greeting).await.unwrap(); 122 | }); 123 | let greeting = rx.recv().await; 124 | greeting.unwrap() 125 | } 126 | 127 | #[update] 128 | async fn schedule_on_panic() { 129 | struct Guard; 130 | impl Drop for Guard { 131 | fn drop(&mut self) { 132 | for _ in 0..3 { 133 | ic_cdk::futures::spawn(async { 134 | on_notify(); 135 | }) 136 | } 137 | } 138 | } 139 | let _guard = Guard; 140 | Call::bounded_wait(ic_cdk::api::canister_self(), "on_notify") 141 | .await 142 | .unwrap(); 143 | ic_cdk::trap("testing"); 144 | } 145 | 146 | #[update] 147 | async fn timer_on_panic() { 148 | struct Guard; 149 | impl Drop for Guard { 150 | fn drop(&mut self) { 151 | for _ in 0..3 { 152 | ic_cdk_timers::set_timer(Duration::ZERO, || { 153 | ic_cdk::futures::spawn(async { 154 | on_notify(); 155 | }) 156 | }); 157 | } 158 | } 159 | } 160 | let _guard = Guard; 161 | Call::bounded_wait(ic_cdk::api::canister_self(), "on_notify") 162 | .await 163 | .unwrap(); 164 | ic_cdk::trap("testing"); 165 | } 166 | 167 | fn main() {} 168 | -------------------------------------------------------------------------------- /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 | }, 75 | canister_id, 76 | }) 77 | .await 78 | .unwrap(); 79 | canister_id 80 | } 81 | 82 | fn main() {} 83 | -------------------------------------------------------------------------------- /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.to_vec(), 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 | }; 21 | 22 | let res = http_request(&args).await.unwrap(); 23 | assert_eq!(res.status, 200u32); 24 | assert_eq!( 25 | res.headers, 26 | vec![HttpHeader { 27 | name: "response_header_name".to_string(), 28 | value: "response_header_value".to_string(), 29 | }] 30 | ); 31 | assert_eq!(res.body, vec![42]); 32 | } 33 | 34 | /// Method is POST. 35 | #[update] 36 | async fn post() { 37 | let args = HttpRequestArgs { 38 | url: "https://example.com".to_string(), 39 | method: HttpMethod::POST, 40 | ..Default::default() 41 | }; 42 | 43 | http_request(&args).await.unwrap(); 44 | } 45 | 46 | /// Method is HEAD. 47 | #[update] 48 | async fn head() { 49 | let args = HttpRequestArgs { 50 | url: "https://example.com".to_string(), 51 | method: HttpMethod::HEAD, 52 | ..Default::default() 53 | }; 54 | 55 | http_request(&args).await.unwrap(); 56 | } 57 | 58 | /// The standard way to define a transform function. 59 | /// 60 | /// It is a query method that takes a TransformArgs and returns an HttpRequestResult. 61 | #[query] 62 | fn transform(args: TransformArgs) -> HttpRequestResult { 63 | let mut body = args.response.body; 64 | body.push(args.context[0]); 65 | HttpRequestResult { 66 | status: args.response.status, 67 | headers: args.response.headers, 68 | body, 69 | } 70 | } 71 | 72 | /// Set the transform field with the name of the transform query method. 73 | #[update] 74 | async fn get_with_transform() { 75 | let args = HttpRequestArgs { 76 | url: "https://example.com".to_string(), 77 | method: HttpMethod::GET, 78 | transform: Some(transform_context_from_query( 79 | "transform".to_string(), 80 | vec![42], 81 | )), 82 | ..Default::default() 83 | }; 84 | 85 | let res = http_request(&args).await.unwrap(); 86 | assert_eq!(res.status, 200u32); 87 | assert_eq!( 88 | res.headers, 89 | vec![HttpHeader { 90 | name: "response_header_name".to_string(), 91 | value: "response_header_value".to_string(), 92 | }] 93 | ); 94 | // The first 42 is from the response body, the second 42 is from the transform context. 95 | assert_eq!(res.body, vec![42, 42]); 96 | } 97 | 98 | /// Set the transform field with a closure. 99 | #[update] 100 | async fn get_with_transform_closure() { 101 | let transform = |args: HttpRequestResult| { 102 | let mut body = args.body; 103 | body.push(42); 104 | HttpRequestResult { 105 | status: args.status, 106 | headers: args.headers, 107 | body, 108 | } 109 | }; 110 | let args = HttpRequestArgs { 111 | url: "https://example.com".to_string(), 112 | method: HttpMethod::GET, 113 | transform: None, 114 | ..Default::default() 115 | }; 116 | let res = http_request_with_closure(&args, transform).await.unwrap(); 117 | assert_eq!(res.status, 200u32); 118 | assert_eq!( 119 | res.headers, 120 | vec![HttpHeader { 121 | name: "response_header_name".to_string(), 122 | value: "response_header_value".to_string(), 123 | }] 124 | ); 125 | // The first 42 is from the response body, the second 42 is from the transform closure. 126 | assert_eq!(res.body, vec![42, 42]); 127 | } 128 | 129 | fn main() {} 130 | -------------------------------------------------------------------------------- /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] % 3 != 0 { 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] % 5 != 0 { 136 | Err("guard2 failed".to_string()) 137 | } else { 138 | Ok(()) 139 | } 140 | } 141 | 142 | export_candid! {} 143 | 144 | fn main() { 145 | println!("{}", __export_service()); 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use candid_parser::utils::{service_equal, CandidSource}; 151 | 152 | #[test] 153 | fn candid_equality_test() { 154 | // If `decode_with` is specified, the argument type would be `blob` in Candid. 155 | // If `encode_with` is specified, the return type would be `blob` in Candid. 156 | let expected = "service : { 157 | arg0 : (blob) -> (); 158 | arg1 : (blob) -> (); 159 | arg2 : (blob) -> (); 160 | ret0 : () -> (blob); 161 | ret1 : () -> (blob); 162 | ret2 : () -> (blob); 163 | method_one : (blob) -> (blob); 164 | method_two : (blob) -> (blob); 165 | generic : (blob) -> (blob); 166 | manual_reply : () -> (nat32); 167 | with_guards : () -> (); 168 | }"; 169 | let expected_candid = CandidSource::Text(expected); 170 | 171 | let actual = super::__export_service(); 172 | let actual_candid = CandidSource::Text(&actual); 173 | 174 | let result = service_equal(expected_candid, actual_candid); 175 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /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 ic_cdk::{query, update}; 2 | use ic_cdk_timers::{clear_timer, set_timer, set_timer_interval, TimerId}; 3 | use std::{ 4 | cell::{Cell, RefCell}, 5 | sync::atomic::{AtomicU32, Ordering}, 6 | time::Duration, 7 | }; 8 | 9 | thread_local! { 10 | static EVENTS: RefCell> = RefCell::default(); 11 | static LONG: Cell = Cell::default(); 12 | static REPEATING: Cell = Cell::default(); 13 | } 14 | 15 | static EXECUTED_TIMERS: AtomicU32 = AtomicU32::new(0); 16 | 17 | #[query] 18 | fn get_events() -> Vec<&'static str> { 19 | EVENTS.with(|events| events.borrow().clone()) 20 | } 21 | 22 | #[update] 23 | fn clear_events() { 24 | EVENTS.with(|events| events.borrow_mut().clear()); 25 | } 26 | 27 | #[update] 28 | fn schedule() { 29 | set_timer(Duration::from_secs(2), || add_event("2")); 30 | set_timer(Duration::from_secs(1), || { 31 | add_event("1"); 32 | set_timer(Duration::from_secs(2), || add_event("3")); 33 | }); 34 | set_timer(Duration::from_secs(4), || add_event("4")); 35 | } 36 | 37 | #[update] 38 | fn schedule_n_timers(n: u32) { 39 | for i in 0..n { 40 | ic_cdk_timers::set_timer(Duration::from_nanos(i.into()), move || { 41 | EXECUTED_TIMERS.fetch_add(1, Ordering::Relaxed); 42 | }); 43 | } 44 | } 45 | 46 | #[query] 47 | fn executed_timers() -> u32 { 48 | EXECUTED_TIMERS.load(Ordering::Relaxed) 49 | } 50 | 51 | #[update] 52 | fn schedule_long() { 53 | let id = set_timer(Duration::from_secs(9), || add_event("long")); 54 | LONG.with(|long| long.set(id)); 55 | } 56 | 57 | #[update] 58 | fn set_self_cancelling_timer() { 59 | let id = set_timer(Duration::from_secs(0), || { 60 | cancel_long(); 61 | add_event("timer cancelled self"); 62 | }); 63 | LONG.with(|long| long.set(id)); 64 | } 65 | 66 | #[update] 67 | fn cancel_long() { 68 | LONG.with(|long| clear_timer(long.get())); 69 | } 70 | 71 | #[update] 72 | fn start_repeating() { 73 | let id = set_timer_interval(Duration::from_secs(1), || add_event("repeat")); 74 | REPEATING.with(|repeating| repeating.set(id)); 75 | } 76 | 77 | #[update] 78 | fn set_self_cancelling_periodic_timer() { 79 | let id = set_timer_interval(Duration::from_secs(0), || { 80 | stop_repeating(); 81 | add_event("periodic timer cancelled self") 82 | }); 83 | REPEATING.with(|repeating| repeating.set(id)); 84 | } 85 | 86 | #[update] 87 | fn stop_repeating() { 88 | REPEATING.with(|repeating| clear_timer(repeating.get())); 89 | } 90 | 91 | fn add_event(event: &'static str) { 92 | EVENTS.with(|events| events.borrow_mut().push(event)); 93 | } 94 | 95 | #[update] 96 | fn global_timer_set(timestamp: u64) -> u64 { 97 | ic_cdk::api::global_timer_set(timestamp) 98 | } 99 | 100 | fn main() {} 101 | -------------------------------------------------------------------------------- /e2e-tests/tests/api.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use pocket_ic::ErrorCode; 3 | 4 | mod test_utilities; 5 | use test_utilities::{cargo_build_canister, pic_base, update}; 6 | 7 | #[test] 8 | fn call_api() { 9 | // with_ii_subnet is required for testing the ic0.cost_sign_with_* API with pre-defined key name. 10 | let pic = pic_base().with_ii_subnet().build(); 11 | let wasm = cargo_build_canister("api"); 12 | let canister_id = pic.create_canister(); 13 | pic.add_cycles(canister_id, 100_000_000_000_000); 14 | pic.install_canister(canister_id, wasm, vec![], None); 15 | let sender = Principal::anonymous(); 16 | let res = pic 17 | .update_call(canister_id, sender, "call_msg_arg_data", vec![42]) 18 | .unwrap(); 19 | assert!(res.is_empty()); 20 | let res = pic 21 | .update_call(canister_id, sender, "call_msg_caller", vec![]) 22 | .unwrap(); 23 | assert!(res.is_empty()); 24 | // Unlike the other entry points, `call_msg_dealine_caller` was implemented with the `#[update]` macro. 25 | // So we use the update method which assumes candid 26 | let _: () = update(&pic, canister_id, "call_msg_deadline_caller", ()).unwrap(); 27 | // `msg_reject_code` and `msg_reject_msg` can't be tested here. 28 | // They are invoked in the reply/reject callback of inter-canister calls. 29 | // So the `call.rs` test covers them. 30 | let res = pic 31 | .update_call(canister_id, sender, "call_msg_reply", vec![]) 32 | .unwrap(); 33 | assert_eq!(res, vec![42]); 34 | let res = pic 35 | .update_call(canister_id, sender, "call_msg_reject", vec![]) 36 | .unwrap_err(); 37 | assert_eq!(res.reject_message, "e2e test reject"); 38 | let res = pic 39 | .update_call(canister_id, sender, "call_msg_cycles_available", vec![]) 40 | .unwrap(); 41 | assert!(res.is_empty()); 42 | // `msg_cycles_refunded` can't be tested here. 43 | // It can only be called in the reply/reject callback of inter-canister calls. 44 | // TODO: Find a way to test it. 45 | let res = pic 46 | .update_call(canister_id, sender, "call_msg_cycles_accept", vec![]) 47 | .unwrap(); 48 | assert!(res.is_empty()); 49 | let res = pic 50 | .update_call(canister_id, sender, "call_cycles_burn", vec![]) 51 | .unwrap(); 52 | assert!(res.is_empty()); 53 | let res = pic 54 | .update_call( 55 | canister_id, 56 | sender, 57 | "call_canister_self", 58 | canister_id.as_slice().to_vec(), 59 | ) 60 | .unwrap(); 61 | assert!(res.is_empty()); 62 | let res = pic 63 | .update_call(canister_id, sender, "call_canister_cycle_balance", vec![]) 64 | .unwrap(); 65 | assert!(res.is_empty()); 66 | let res = pic 67 | .update_call( 68 | canister_id, 69 | sender, 70 | "call_canister_liquid_cycle_balance", 71 | vec![], 72 | ) 73 | .unwrap(); 74 | assert!(res.is_empty()); 75 | let res = pic 76 | .update_call(canister_id, sender, "call_canister_status", vec![]) 77 | .unwrap(); 78 | assert!(res.is_empty()); 79 | let res = pic 80 | .update_call(canister_id, sender, "call_canister_version", vec![]) 81 | .unwrap(); 82 | assert!(res.is_empty()); 83 | let res = pic 84 | .update_call(canister_id, sender, "call_subnet_self", vec![]) 85 | .unwrap(); 86 | assert!(res.is_empty()); 87 | // `msg_method_name` and `accept_message` are invoked in the inspect_message entry point. 88 | // Every calls above/below execute the inspect_message entry point. 89 | // So these two API bindings are tested implicitly. 90 | let res = pic 91 | .update_call(canister_id, sender, "call_stable", vec![]) 92 | .unwrap(); 93 | assert!(res.is_empty()); 94 | let res = pic 95 | .update_call(canister_id, sender, "call_root_key", vec![]) 96 | .unwrap(); 97 | assert!(res.is_empty()); 98 | let res = pic 99 | .update_call(canister_id, sender, "call_certified_data_set", vec![]) 100 | .unwrap(); 101 | assert!(res.is_empty()); 102 | let res = pic 103 | .query_call(canister_id, sender, "call_data_certificate", vec![]) 104 | .unwrap(); 105 | assert!(res.is_empty()); 106 | let res = pic 107 | .update_call(canister_id, sender, "call_time", vec![]) 108 | .unwrap(); 109 | assert!(res.is_empty()); 110 | // `global_timer_set` is tested in `timers.rs`. 111 | let res = pic 112 | .update_call(canister_id, sender, "call_performance_counter", vec![]) 113 | .unwrap(); 114 | assert!(res.is_empty()); 115 | let res = pic 116 | .update_call(canister_id, sender, "call_is_controller", vec![]) 117 | .unwrap(); 118 | assert!(res.is_empty()); 119 | let res = pic 120 | .update_call(canister_id, sender, "call_in_replicated_execution", vec![]) 121 | .unwrap(); 122 | assert_eq!(res, vec![1]); 123 | let res = pic 124 | .query_call(canister_id, sender, "call_in_replicated_execution", vec![]) 125 | .unwrap(); 126 | assert_eq!(res, vec![0]); 127 | let res = pic 128 | .update_call(canister_id, sender, "call_cost_call", vec![]) 129 | .unwrap(); 130 | assert!(res.is_empty()); 131 | let res = pic 132 | .update_call(canister_id, sender, "call_cost_create_canister", vec![]) 133 | .unwrap(); 134 | assert!(res.is_empty()); 135 | let res = pic 136 | .update_call(canister_id, sender, "call_cost_http_request", vec![]) 137 | .unwrap(); 138 | assert!(res.is_empty()); 139 | let res = pic 140 | .update_call(canister_id, sender, "call_cost_sign_with_ecdsa", vec![]) 141 | .unwrap(); 142 | assert!(res.is_empty()); 143 | let res = pic 144 | .update_call(canister_id, sender, "call_cost_sign_with_schnorr", vec![]) 145 | .unwrap(); 146 | assert!(res.is_empty()); 147 | let res = pic 148 | .update_call(canister_id, sender, "call_debug_print", vec![]) 149 | .unwrap(); 150 | assert!(res.is_empty()); 151 | let rej = pic 152 | .update_call(canister_id, sender, "call_trap", vec![]) 153 | .unwrap_err(); 154 | assert_eq!(rej.error_code, ErrorCode::CanisterCalledTrap); 155 | assert!(rej.reject_message.contains("It's a trap!")); 156 | } 157 | -------------------------------------------------------------------------------- /e2e-tests/tests/async.rs: -------------------------------------------------------------------------------- 1 | use pocket_ic::{query_candid, ErrorCode}; 2 | 3 | mod test_utilities; 4 | use test_utilities::{cargo_build_canister, pic_base, update}; 5 | 6 | #[test] 7 | fn panic_after_async_frees_resources() { 8 | let pic = pic_base().build(); 9 | let wasm = cargo_build_canister("async"); 10 | let canister_id = pic.create_canister(); 11 | pic.add_cycles(canister_id, 2_000_000_000_000); 12 | pic.install_canister(canister_id, wasm, vec![], None); 13 | 14 | for i in 1..3 { 15 | match update(&pic, canister_id, "panic_after_async", ()) { 16 | Ok(()) => (), 17 | Err(rej) => { 18 | println!("Got a user error as expected: {}", rej); 19 | 20 | assert_eq!(rej.error_code, ErrorCode::CanisterCalledTrap); 21 | let expected_message = "Goodbye, cruel world."; 22 | assert!( 23 | rej.reject_message.contains(expected_message), 24 | "Expected the user error to contain '{}', got: {}", 25 | expected_message, 26 | rej.reject_message 27 | ); 28 | } 29 | } 30 | 31 | let (n,): (u64,) = update(&pic, canister_id, "invocation_count", ()).unwrap(); 32 | 33 | assert_eq!(i, n, "expected the invocation count to be {}, got {}", i, n); 34 | } 35 | 36 | let (message,): (String,) = 37 | update(&pic, canister_id, "invalid_reply_payload_does_not_trap", ()).unwrap(); 38 | assert!(message.contains("handled decoding error gracefully")); 39 | 40 | let rej = update::<_, ()>(&pic, canister_id, "panic_twice", ()).expect_err("failed to panic"); 41 | assert!(rej.reject_message.contains("Call already trapped")); 42 | let _: (u64,) = update(&pic, canister_id, "notifications_received", ()).unwrap(); 43 | let _: (u64,) = update(&pic, canister_id, "invocation_count", ()).unwrap(); 44 | } 45 | 46 | #[test] 47 | fn panic_after_async_destructors_cannot_schedule_tasks() { 48 | let pic = pic_base().build(); 49 | let wasm = cargo_build_canister("async"); 50 | let canister_id = pic.create_canister(); 51 | pic.add_cycles(canister_id, 2_000_000_000_000); 52 | pic.install_canister(canister_id, wasm, vec![], None); 53 | let err = update::<_, ()>(&pic, canister_id, "schedule_on_panic", ()).unwrap_err(); 54 | assert!(err.reject_message.contains("recovery")); 55 | let (pre_bg_notifs,): (u64,) = 56 | query_candid(&pic, canister_id, "notifications_received", ()).unwrap(); 57 | assert_eq!(pre_bg_notifs, 1); 58 | update::<_, ()>(&pic, canister_id, "on_notify", ()).unwrap(); 59 | let (post_bg_notifs,): (u64,) = 60 | query_candid(&pic, canister_id, "notifications_received", ()).unwrap(); 61 | assert_eq!(post_bg_notifs, 2); 62 | } 63 | 64 | #[test] 65 | fn panic_after_async_destructors_can_schedule_timers() { 66 | let pic = pic_base().build(); 67 | let wasm = cargo_build_canister("async"); 68 | let canister_id = pic.create_canister(); 69 | pic.add_cycles(canister_id, 2_000_000_000_000); 70 | pic.install_canister(canister_id, wasm, vec![], None); 71 | let err = update::<_, ()>(&pic, canister_id, "timer_on_panic", ()).unwrap_err(); 72 | assert!(err.reject_message.contains("testing")); 73 | assert!(!err.reject_message.contains("recovery")); 74 | let (pre_bg_notifs,): (u64,) = 75 | query_candid(&pic, canister_id, "notifications_received", ()).unwrap(); 76 | assert_eq!(pre_bg_notifs, 1); 77 | update::<_, ()>(&pic, canister_id, "on_notify", ()).unwrap(); 78 | let (post_bg_notifs,): (u64,) = 79 | query_candid(&pic, canister_id, "notifications_received", ()).unwrap(); 80 | assert_eq!(post_bg_notifs, 5); 81 | } 82 | 83 | #[test] 84 | fn notify_calls() { 85 | let pic = pic_base().build(); 86 | let wasm = cargo_build_canister("async"); 87 | let sender_id = pic.create_canister(); 88 | pic.add_cycles(sender_id, 2_000_000_000_000); 89 | pic.install_canister(sender_id, wasm.clone(), vec![], None); 90 | let receiver_id = pic.create_canister(); 91 | pic.add_cycles(receiver_id, 2_000_000_000_000); 92 | pic.install_canister(receiver_id, wasm, vec![], None); 93 | 94 | let (n,): (u64,) = query_candid(&pic, receiver_id, "notifications_received", ()).unwrap(); 95 | assert_eq!(n, 0); 96 | 97 | let () = update(&pic, sender_id, "notify", (receiver_id, "on_notify")).unwrap(); 98 | 99 | let (n,): (u64,) = query_candid(&pic, receiver_id, "notifications_received", ()).unwrap(); 100 | assert_eq!(n, 1); 101 | } 102 | 103 | #[test] 104 | fn test_composite_query() { 105 | let pic = pic_base().build(); 106 | let wasm = cargo_build_canister("async"); 107 | let sender_id = pic.create_canister(); 108 | pic.add_cycles(sender_id, 2_000_000_000_000); 109 | pic.install_canister(sender_id, wasm.clone(), vec![], None); 110 | let receiver_id = pic.create_canister(); 111 | pic.add_cycles(receiver_id, 2_000_000_000_000); 112 | pic.install_canister(receiver_id, wasm, vec![], None); 113 | 114 | let (greeting,): (String,) = 115 | query_candid(&pic, sender_id, "greet_self", (receiver_id,)).unwrap(); 116 | assert_eq!(greeting, "Hello, myself"); 117 | } 118 | 119 | #[test] 120 | fn channels() { 121 | let pic = pic_base().build(); 122 | let wasm = cargo_build_canister("async"); 123 | let canister_id = pic.create_canister(); 124 | pic.add_cycles(canister_id, 2_000_000_000_000); 125 | pic.install_canister(canister_id, wasm, vec![], None); 126 | 127 | let (greeting,): (String,) = update(&pic, canister_id, "await_channel_completion", ()).unwrap(); 128 | assert_eq!(greeting, "Hello, myself"); 129 | } 130 | -------------------------------------------------------------------------------- /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 pic = pic_base().build(); 7 | let wasm = cargo_build_canister("call"); 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 pic = pic_base().build(); 10 | let wasm = cargo_build_canister("chunk"); 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 pic = pic_base().build(); 14 | 15 | let wasm = cargo_build_canister("http_request"); 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 | } 26 | 27 | fn test_one_http_request(pic: &PocketIc, canister_id: Principal, method: &str) { 28 | let call_id = pic 29 | .submit_call( 30 | canister_id, 31 | Principal::anonymous(), 32 | method, 33 | Encode!(&()).unwrap(), 34 | ) 35 | .unwrap(); 36 | let canister_http_requests = tick_until_next_request(pic); 37 | assert_eq!(canister_http_requests.len(), 1); 38 | let request = &canister_http_requests[0]; 39 | pic.mock_canister_http_response(MockCanisterHttpResponse { 40 | subnet_id: request.subnet_id, 41 | request_id: request.request_id, 42 | response: CanisterHttpResponse::CanisterHttpReply(CanisterHttpReply { 43 | status: 200, 44 | headers: vec![CanisterHttpHeader { 45 | name: "response_header_name".to_string(), 46 | value: "response_header_value".to_string(), 47 | }], 48 | body: vec![42], 49 | }), 50 | additional_responses: vec![], 51 | }); 52 | pic.await_call(call_id).unwrap(); 53 | } 54 | 55 | fn tick_until_next_request(pic: &PocketIc) -> Vec { 56 | for _ in 0..10 { 57 | let requests = pic.get_canister_http(); 58 | if !requests.is_empty() { 59 | return requests; 60 | } 61 | pic.tick(); 62 | } 63 | vec![] 64 | } 65 | -------------------------------------------------------------------------------- /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 pic = pic_base().build(); 15 | let wasm = cargo_build_canister("macros"); 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 | -------------------------------------------------------------------------------- /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 pic = pic_base().with_ii_subnet().build(); 7 | 8 | let wasm = cargo_build_canister("management_canister"); 9 | let canister_id = pic.create_canister(); 10 | let subnet_id = pic.get_subnet(canister_id).unwrap(); 11 | pic.add_cycles(canister_id, 10_000_000_000_000u128); // 10 T 12 | pic.install_canister(canister_id, wasm, vec![], None); 13 | let () = update(&pic, canister_id, "basic", ()).unwrap(); 14 | let () = update(&pic, canister_id, "ecdsa", ()).unwrap(); 15 | let () = update(&pic, canister_id, "schnorr", ()).unwrap(); 16 | let () = update(&pic, canister_id, "metrics", (subnet_id,)).unwrap(); 17 | let () = update(&pic, canister_id, "subnet", (subnet_id,)).unwrap(); 18 | let () = update(&pic, canister_id, "provisional", ()).unwrap(); 19 | let () = update(&pic, canister_id, "snapshots", ()).unwrap(); 20 | } 21 | 22 | #[test] 23 | fn test_vetkd() { 24 | let pic = pic_base() 25 | .with_ii_subnet() 26 | // vetKD is not available on mainnet yet 27 | .with_nonmainnet_features(true) 28 | .build(); 29 | 30 | let wasm = cargo_build_canister("management_canister"); 31 | let canister_id = pic.create_canister(); 32 | pic.add_cycles(canister_id, 10_000_000_000_000u128); // 10 T 33 | pic.install_canister(canister_id, wasm, vec![], None); 34 | 35 | const VETKD_TRANSPORT_SECRET_KEY_SEED: [u8; 32] = [13; 32]; 36 | let transport_key = 37 | ic_vetkd_utils::TransportSecretKey::from_seed(VETKD_TRANSPORT_SECRET_KEY_SEED.to_vec()) 38 | .expect("Failed to generate transport secret key"); 39 | let transport_public_key = transport_key.public_key(); 40 | 41 | let () = update(&pic, canister_id, "vetkd", (transport_public_key,)).unwrap(); 42 | } 43 | -------------------------------------------------------------------------------- /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 pic = pic_base().build(); 13 | let wasm = cargo_build_canister("simple_kv_store"); 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; 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", 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.args([ 50 | "--bin", 51 | bin_name, 52 | "--profile", 53 | "canister-release", 54 | "--manifest-path", 55 | &cargo_toml_path.to_string_lossy(), 56 | "--target-dir", 57 | wasm_target_dir.as_ref(), 58 | ]); 59 | 60 | cmd.output().expect("failed to compile the wasm binary"); 61 | 62 | let wasm_path = wasm_target_dir 63 | .join(target) 64 | .join("canister-release") 65 | .join(bin_name) 66 | .with_extension("wasm"); 67 | 68 | std::fs::read(&wasm_path).unwrap_or_else(|e| { 69 | panic!( 70 | "failed to read compiled Wasm file from {:?}: {}", 71 | &wasm_path, e 72 | ) 73 | }) 74 | } 75 | 76 | // The linter complains "function `update` is never used" 77 | // because not EVERY test uses this function. 78 | pub fn update( 79 | env: &PocketIc, 80 | canister_id: Principal, 81 | method: &str, 82 | input: Input, 83 | ) -> Result 84 | where 85 | Input: ArgumentEncoder, 86 | Output: for<'a> ArgumentDecoder<'a>, 87 | { 88 | call_candid(env, canister_id, RawEffectivePrincipal::None, method, input) 89 | } 90 | 91 | /// Creates a PocketIcBuilder with the base configuration for e2e tests. 92 | /// 93 | /// The PocketIc server binary is cached for reuse. 94 | pub fn pic_base() -> PocketIcBuilder { 95 | let pocket_ic_server = check_pocket_ic_server(); 96 | PocketIcBuilder::new() 97 | .with_server_binary(pocket_ic_server) 98 | .with_application_subnet() 99 | } 100 | 101 | fn check_pocket_ic_server() -> PathBuf { 102 | let dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); 103 | let cargo_toml_path = dir.join("Cargo.toml"); 104 | let metadata = MetadataCommand::new() 105 | .manifest_path(&cargo_toml_path) 106 | .exec() 107 | .expect("failed to run cargo metadata"); 108 | let e2e_tests_package = metadata 109 | .packages 110 | .iter() 111 | .find(|m| m.name == "ic-cdk-e2e-tests") 112 | .expect("ic-cdk-e2e-tests not found in Cargo.toml"); 113 | let pocket_ic_tag = e2e_tests_package 114 | .dependencies 115 | .iter() 116 | .find(|d| d.name == "pocket-ic") 117 | .expect("pocket-ic not found in Cargo.toml") 118 | .source 119 | .as_ref() 120 | .expect("pocket-ic source not found in Cargo.toml") 121 | .split_once("tag=") 122 | .expect("`tag=` not found in pocket-ic source") 123 | .1; 124 | let target_dir = metadata.target_directory; 125 | let artifact_dir = target_dir.join("e2e-tests-artifacts"); 126 | let tag_path = artifact_dir.join("pocket-ic-tag"); 127 | let server_binary_path = artifact_dir.join("pocket-ic"); 128 | if let Ok(tag) = std::fs::read_to_string(&tag_path) { 129 | if tag == pocket_ic_tag && server_binary_path.exists() { 130 | return server_binary_path.into(); 131 | } 132 | } 133 | panic!("pocket-ic server not found or tag mismatch, please run `scripts/download_pocket_ic_server.sh` in the project root"); 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests { 138 | use super::*; 139 | 140 | #[test] 141 | fn test_pocket_ic() { 142 | let _pic = pic_base(); 143 | } 144 | 145 | #[test] 146 | fn test_update() { 147 | let pic = pic_base().build(); 148 | let canister_id = pic.create_canister(); 149 | pic.add_cycles(canister_id, 2_000_000_000_000); 150 | pic.install_canister( 151 | canister_id, 152 | b"\x00asm\x01\x00\x00\x00".to_vec(), 153 | vec![], 154 | None, 155 | ); 156 | assert!(update::<(), ()>(&pic, canister_id, "insert", ()).is_err()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /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 pic = pic_base().build(); 10 | let wasm = cargo_build_canister("timers"); 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 | 23 | update::<_, ()>(&pic, canister_id, "start_repeating", ()) 24 | .expect("Failed to call start_repeating"); 25 | advance_seconds(&pic, 3); 26 | update::<_, ()>(&pic, canister_id, "stop_repeating", ()) 27 | .expect("Failed to call stop_repeating"); 28 | advance_seconds(&pic, 2); 29 | 30 | let (events,): (Vec,) = 31 | query_candid(&pic, canister_id, "get_events", ()).expect("Failed to call get_events"); 32 | assert_eq!( 33 | events[..], 34 | ["1", "2", "3", "4", "repeat", "repeat", "repeat"] 35 | ); 36 | } 37 | 38 | #[test] 39 | fn test_timers_can_cancel_themselves() { 40 | let pic = pic_base().build(); 41 | let wasm = cargo_build_canister("timers"); 42 | let canister_id = pic.create_canister(); 43 | pic.add_cycles(canister_id, 2_000_000_000_000); 44 | pic.install_canister(canister_id, wasm, vec![], None); 45 | 46 | update::<_, ()>(&pic, canister_id, "set_self_cancelling_timer", ()) 47 | .expect("Failed to call set_self_cancelling_timer"); 48 | update::<_, ()>(&pic, canister_id, "set_self_cancelling_periodic_timer", ()) 49 | .expect("Failed to call set_self_cancelling_periodic_timer"); 50 | 51 | advance_seconds(&pic, 1); 52 | 53 | let (events,): (Vec,) = 54 | query_candid(&pic, canister_id, "get_events", ()).expect("Failed to call get_events"); 55 | assert_eq!( 56 | events, 57 | ["timer cancelled self", "periodic timer cancelled self"] 58 | ); 59 | } 60 | 61 | #[test] 62 | fn test_scheduling_many_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 wasm = cargo_build_canister("timers"); 67 | let canister_id = pic.create_canister(); 68 | pic.add_cycles(canister_id, 100_000_000_000_000u128); 69 | pic.install_canister(canister_id, wasm, vec![], None); 70 | 71 | let () = update( 72 | &pic, 73 | canister_id, 74 | "schedule_n_timers", 75 | (timers_to_schedule,), 76 | ) 77 | .expect("Error calling schedule_n_timers"); 78 | 79 | // Up to 20 timers will be executed per round 80 | // Be conservative that advance 2 times the minimum number of rounds 81 | const TIMERS_PER_ROUND: u32 = 20; 82 | advance_seconds(&pic, 2 * timers_to_schedule / TIMERS_PER_ROUND); 83 | 84 | let (executed_timers,): (u32,) = query_candid(&pic, canister_id, "executed_timers", ()) 85 | .expect("Error querying executed_timers"); 86 | 87 | assert_eq!(timers_to_schedule, executed_timers); 88 | } 89 | 90 | fn advance_seconds(pic: &PocketIc, seconds: u32) { 91 | for _ in 0..seconds { 92 | pic.advance_time(Duration::from_secs(1)); 93 | pic.tick(); 94 | } 95 | } 96 | 97 | #[test] 98 | fn test_set_global_timers() { 99 | let pic = pic_base().build(); 100 | 101 | let wasm = cargo_build_canister("timers"); 102 | let canister_id = pic.create_canister(); 103 | pic.add_cycles(canister_id, 2_000_000_000_000); 104 | pic.install_canister(canister_id, wasm, vec![], None); 105 | 106 | // Set a 9s timer at t0, it expires at t1 = t0 + 9s 107 | let t0 = pic.get_time().as_nanos_since_unix_epoch(); 108 | let t1 = t0 + 9_000_000_000; 109 | update::<_, ()>(&pic, canister_id, "schedule_long", ()).expect("Failed to call schedule_long"); 110 | 111 | // 5 seconds later, the 9s timer is still active 112 | advance_seconds(&pic, 5); 113 | 114 | // Set the expiration time of the timer to t2 = t1 + 5s 115 | let t2 = t1 + 5_000_000_000; 116 | let (previous,) = 117 | update::<(u64,), (u64,)>(&pic, canister_id, "global_timer_set", (t2,)).unwrap(); 118 | assert!(previous.abs_diff(t1) < 2); // time error no more than 1 nanosecond 119 | 120 | // Deactivate the timer 121 | let (previous,) = 122 | update::<(u64,), (u64,)>(&pic, canister_id, "global_timer_set", (0,)).unwrap(); 123 | assert!(previous.abs_diff(t2) < 2); // time error no more than 1 nanosecond 124 | } 125 | -------------------------------------------------------------------------------- /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 | - Support canister setting `log_visibility`. 10 | 11 | ### Changed 12 | 13 | - Refactor!: move Rust code generation logic from candid_parser. (#480) 14 | 15 | ### Fixed 16 | 17 | - Re-generate bindings if the canister ids changed (e.g. when switching networks) or when the path to the candid file of a dependency changed. (#479) 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.1.3" 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 | convert_case = "0.6" 19 | pretty = "0.12" 20 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /ic-cdk-bindgen/README.md: -------------------------------------------------------------------------------- 1 | # ic-cdk-bindgen 2 | 3 | Generate Rust bindings from Candid to make inter-canister calls. 4 | 5 | ## How to use 6 | 7 | 1. Canister project add `ic-cdk-bindgen` as a build dependency. 8 | 9 | ```toml 10 | [build-dependencies] 11 | ic-cdk-bindgen = "0.1" 12 | ``` 13 | 14 | 2. Add `build.rs` to generate Rust bindings in the source directory with config options. 15 | 16 | ```rs 17 | use ic_cdk_bindgen::{Builder, Config}; 18 | fn main() { 19 | let counter = Config::new("counter"); 20 | let mut builder = Builder::new(); 21 | builder.add(counter); 22 | builder.build(None); // default write to src/declarations 23 | } 24 | ``` 25 | 26 | 3. In Canister code, 27 | 28 | ```rs 29 | mod declarations; 30 | use declarations::counter::counter; 31 | 32 | counter.inc().await? 33 | ``` 34 | -------------------------------------------------------------------------------- /ic-cdk-bindgen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use candid::Principal; 2 | use candid_parser::pretty_check_file; 3 | use std::env; 4 | use std::fs; 5 | use std::io::Write; 6 | use std::path::PathBuf; 7 | 8 | mod code_generator; 9 | 10 | #[derive(Clone)] 11 | pub struct Config { 12 | pub canister_name: String, 13 | pub candid_path: PathBuf, 14 | pub skip_existing_files: bool, 15 | pub binding: code_generator::Config, 16 | } 17 | 18 | impl Config { 19 | pub fn new(canister_name: &str) -> Self { 20 | let (candid_path, canister_id) = resolve_candid_path_and_canister_id(canister_name); 21 | let mut binding = code_generator::Config::new(); 22 | binding 23 | // User will depend on candid crate directly 24 | .set_candid_crate("candid".to_string()) 25 | .set_canister_id(canister_id) 26 | .set_service_name(canister_name.to_string()) 27 | .set_target(code_generator::Target::CanisterCall); 28 | 29 | Config { 30 | canister_name: canister_name.to_string(), 31 | candid_path, 32 | skip_existing_files: false, 33 | binding, 34 | } 35 | } 36 | } 37 | 38 | /// Resolve the candid path and canister id from environment variables. 39 | /// 40 | /// The name and format of the environment variables are standardized: 41 | /// https://github.com/dfinity/sdk/blob/master/docs/cli-reference/dfx-envars.md#canister_id_canistername 42 | /// 43 | /// We previously used environment variables like`CANISTER_CANDID_PATH_` without to_uppercase. 44 | /// That is deprecated. To keep backward compatibility, we also check for the old format. 45 | /// Just in case the user run `ic-cdk-bindgen` outside `dfx`. 46 | /// If the old format is found, we print a warning to the user. 47 | /// dfx v0.13.0 only provides the old format, which can be used to check the warning logic. 48 | /// TODO: remove the support for the old format, in the next major release (v0.2) of `ic-cdk-bindgen`. 49 | fn resolve_candid_path_and_canister_id(canister_name: &str) -> (PathBuf, Principal) { 50 | fn warning_deprecated_env(deprecated_name: &str, new_name: &str) { 51 | println!("cargo:warning=The environment variable {} is deprecated. Please set {} instead. Upgrading dfx may fix this issue.", deprecated_name, new_name); 52 | } 53 | 54 | let canister_name = canister_name.replace('-', "_"); 55 | let canister_name_upper = canister_name.to_uppercase(); 56 | 57 | let candid_path_var_name = format!("CANISTER_CANDID_PATH_{}", canister_name_upper); 58 | let candid_path_var_name_legacy = format!("CANISTER_CANDID_PATH_{}", canister_name); 59 | println!("cargo:rerun-if-env-changed={candid_path_var_name}"); 60 | println!("cargo:rerun-if-env-changed={candid_path_var_name_legacy}"); 61 | 62 | let candid_path_str = if let Ok(candid_path_str) = env::var(&candid_path_var_name) { 63 | candid_path_str 64 | } else if let Ok(candid_path_str) = env::var(&candid_path_var_name_legacy) { 65 | warning_deprecated_env(&candid_path_var_name_legacy, &candid_path_var_name); 66 | candid_path_str 67 | } else { 68 | panic!( 69 | "Cannot find environment variable: {}", 70 | &candid_path_var_name 71 | ); 72 | }; 73 | let candid_path = PathBuf::from(candid_path_str); 74 | 75 | let canister_id_var_name = format!("CANISTER_ID_{}", canister_name_upper); 76 | let canister_id_var_name_legacy = format!("CANISTER_ID_{}", canister_name); 77 | println!("cargo:rerun-if-env-changed={canister_id_var_name}"); 78 | println!("cargo:rerun-if-env-changed={canister_id_var_name_legacy}"); 79 | let canister_id_str = if let Ok(canister_id_str) = env::var(&canister_id_var_name) { 80 | canister_id_str 81 | } else if let Ok(canister_id_str) = env::var(&canister_id_var_name_legacy) { 82 | warning_deprecated_env(&canister_id_var_name_legacy, &canister_id_var_name); 83 | canister_id_str 84 | } else { 85 | panic!( 86 | "Cannot find environment variable: {}", 87 | &canister_id_var_name 88 | ); 89 | }; 90 | let canister_id = Principal::from_text(&canister_id_str) 91 | .unwrap_or_else(|_| panic!("Invalid principal: {}", &canister_id_str)); 92 | 93 | (candid_path, canister_id) 94 | } 95 | 96 | #[derive(Default)] 97 | pub struct Builder { 98 | configs: Vec, 99 | } 100 | 101 | impl Builder { 102 | pub fn new() -> Self { 103 | Builder { 104 | configs: Vec::new(), 105 | } 106 | } 107 | pub fn add(&mut self, config: Config) -> &mut Self { 108 | self.configs.push(config); 109 | self 110 | } 111 | pub fn build(self, out_path: Option) { 112 | let out_path = out_path.unwrap_or_else(|| { 113 | let manifest_dir = 114 | PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("Cannot find manifest dir")); 115 | manifest_dir.join("src").join("declarations") 116 | }); 117 | fs::create_dir_all(&out_path).unwrap(); 118 | for conf in self.configs.iter() { 119 | let (env, actor) = 120 | pretty_check_file(&conf.candid_path).expect("Cannot parse candid file"); 121 | let content = code_generator::compile(&conf.binding, &env, &actor); 122 | let generated_path = out_path.join(format!("{}.rs", conf.canister_name)); 123 | if !(conf.skip_existing_files && generated_path.exists()) { 124 | fs::write(generated_path, content).expect("Cannot store generated binding"); 125 | } 126 | } 127 | let mut module = fs::File::create(out_path.join("mod.rs")).unwrap(); 128 | module.write_all(b"#![allow(unused_imports)]\n").unwrap(); 129 | module 130 | .write_all(b"#![allow(non_upper_case_globals)]\n") 131 | .unwrap(); 132 | module.write_all(b"#![allow(non_snake_case)]\n").unwrap(); 133 | for conf in self.configs.iter() { 134 | module.write_all(b"#[rustfmt::skip]\n").unwrap(); // so that we get a better diff 135 | let line = format!("pub mod {};\n", conf.canister_name); 136 | module.write_all(line.as_bytes()).unwrap(); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /ic-cdk-executor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-executor" 3 | version = "1.0.0" 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 | slotmap.workspace = true 18 | -------------------------------------------------------------------------------- /ic-cdk-executor/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // dummy build script, required by `package.links` 3 | } 4 | -------------------------------------------------------------------------------- /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.18.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 = [ 14 | "api-bindings", 15 | "data-structures", 16 | "no-std", 17 | "development-tools::ffi", 18 | ] 19 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 20 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 21 | 22 | [lib] 23 | proc-macro = true 24 | 25 | [dependencies] 26 | candid.workspace = true 27 | darling = "0.20.11" 28 | proc-macro2 = "1.0" 29 | quote.workspace = true 30 | syn = { workspace = true, features = ["fold", "full", "extra-traits"] } 31 | -------------------------------------------------------------------------------- /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!("{}\n attr: {}\n item: {}", name, attr, 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 | #[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 | ## [0.12.0] - 2025-04-22 10 | 11 | - Upgrade `ic-cdk` to v0.18. 12 | 13 | ## [0.11.0] - 2024-11-04 14 | 15 | ### Changed 16 | 17 | - Upgrade `ic-cdk` to v0.17. 18 | 19 | ## [0.10.0] - 2024-08-27 20 | 21 | ### Changed 22 | 23 | - Upgrade `ic-cdk` to v0.16. 24 | 25 | ## [0.9.0] - 2024-07-01 26 | 27 | ### Changed 28 | 29 | - Upgrade `ic-cdk` to v0.15. 30 | 31 | ## [0.8.0] - 2024-05-17 32 | 33 | ### Changed 34 | 35 | - Upgrade `ic-cdk` to v0.14. 36 | 37 | ## [0.7.0] - 2024-03-01 38 | 39 | ### Changed 40 | 41 | - Upgrade `ic-cdk` to v0.13. 42 | 43 | ## [0.6.0] - 2023-11-23 44 | 45 | ### Changed 46 | 47 | - Upgrade `ic-cdk` to v0.12. 48 | 49 | ## [0.5.0] - 2023-09-18 50 | 51 | ### Changed 52 | 53 | - Upgrade `ic-cdk` to v0.11. 54 | 55 | ## [0.4.0] - 2023-07-13 56 | 57 | ### Changed 58 | 59 | - Upgrade `ic-cdk` to v0.10. 60 | 61 | ## [0.3.0] - 2023-06-20 62 | 63 | ### Changed 64 | 65 | - Upgrade `ic-cdk` to v0.9. 66 | 67 | ## [0.2.0] - 2023-05-26 68 | 69 | ### Changed 70 | 71 | - Upgrade `ic-cdk` to v0.8. 72 | 73 | ## [0.1.2] - 2023-03-01 74 | 75 | ## [0.1.1] - 2023-02-22 76 | 77 | ### Fixed 78 | 79 | - Broken references to `ic_cdk::api::time`. 80 | 81 | ## [0.1.0] - 2023-02-03 82 | 83 | ### Added 84 | 85 | - Initial release of the `ic-cdk-timers` library. 86 | -------------------------------------------------------------------------------- /ic-cdk-timers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-cdk-timers" 3 | version = "0.12.0" 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 = [ 14 | "api-bindings", 15 | "data-structures", 16 | "no-std", 17 | "development-tools::ffi", 18 | ] 19 | keywords = ["internet-computer", "dfinity", "canister", "cdk"] 20 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 21 | 22 | [dependencies] 23 | candid.workspace = true 24 | ic0.workspace = true 25 | ic-cdk.workspace = true 26 | serde.workspace = true 27 | serde_bytes.workspace = true 28 | slotmap.workspace = true 29 | futures.workspace = true 30 | 31 | [package.metadata.docs.rs] 32 | default-target = "wasm32-unknown-unknown" 33 | rustc-args = ["--cfg=docsrs"] 34 | -------------------------------------------------------------------------------- /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.18.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 = [ 14 | "api-bindings", 15 | "data-structures", 16 | "no-std", 17 | "development-tools::ffi", 18 | ] 19 | keywords = ["internet-computer", "types", "dfinity", "canister", "cdk"] 20 | include = ["src", "Cargo.toml", "LICENSE", "README.md"] 21 | 22 | [dependencies] 23 | candid.workspace = true 24 | ic0.workspace = true 25 | ic-cdk-executor.workspace = true 26 | # Pin ic-cdk-macros to a specific version. 27 | # This actually create a 1-to-1 mapping between ic-cdk and ic-cdk-macros. 28 | # Dependents won't accidentaly upgrading ic-cdk-macros only but not ic-cdk. 29 | # ic-cdk-macros is a hidden dependency, re-exported by ic-cdk. 30 | # It should not be included by users direcly. 31 | ic-cdk-macros = { path = "../ic-cdk-macros", version = "=0.18.3" } 32 | ic-error-types = "0.2.0" 33 | ic-management-canister-types.workspace = true 34 | serde.workspace = true 35 | serde_bytes.workspace = true 36 | slotmap.workspace = true 37 | thiserror = "2.0" 38 | 39 | [dev-dependencies] 40 | anyhow = "1" 41 | candid_parser.workspace = true 42 | rstest = "0.12.0" 43 | trybuild = "1.0" 44 | 45 | [features] 46 | transform-closure = [] 47 | 48 | [package.metadata.docs.rs] 49 | features = ["transform-closure"] 50 | default-target = "wasm32-unknown-unknown" 51 | rustdoc-args = ["--cfg=docsrs"] 52 | -------------------------------------------------------------------------------- /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::{call_with_payment128, CallResult}; 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::{call, call_with_payment128, CallResult}; 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 KeyId. 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::{call_with_payment128, CallResult}; 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 | #[export_name = "canister_query http_transform_legacy"] 40 | extern "C" fn http_transform() { 41 | use crate::api::{ 42 | call::{arg_data, reply, ArgDecoderConfig}, 43 | caller, 44 | }; 45 | if caller() != Principal::management_canister() { 46 | crate::trap("This function is internal to ic-cdk and should not be called externally."); 47 | } 48 | crate::setup(); 49 | let (args,): (TransformArgs,) = arg_data(ArgDecoderConfig::default()); 50 | let int = u64::from_be_bytes(args.context[..].try_into().unwrap()); 51 | let key = DefaultKey::from(KeyData::from_ffi(int)); 52 | let func = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(key)); 53 | let Some(func) = func else { 54 | crate::trap(format!("Missing transform function for request {int}")); 55 | }; 56 | let transformed = func(args.response); 57 | reply((transformed,)) 58 | } 59 | 60 | /// Make an HTTP request to a given URL and return the HTTP response, after a transformation. 61 | /// 62 | /// Do not set the `transform` field of `arg`. To use a Candid function, call [`http_request`] instead. 63 | /// 64 | /// See [IC method `http_request`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-http_request). 65 | /// 66 | /// This call requires cycles payment. The required cycles is a function of the request size and max_response_bytes. 67 | /// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details. 68 | #[cfg(feature = "transform-closure")] 69 | #[cfg_attr(docsrs, doc(cfg(feature = "transform-closure")))] 70 | pub async fn http_request_with_closure( 71 | arg: CanisterHttpRequestArgument, 72 | cycles: u128, 73 | transform_func: impl FnOnce(HttpResponse) -> HttpResponse + 'static, 74 | ) -> CallResult<(HttpResponse,)> { 75 | assert!( 76 | arg.transform.is_none(), 77 | "`CanisterHttpRequestArgument`'s `transform` field must be `None` when using a closure" 78 | ); 79 | let transform_func = Box::new(transform_func) as _; 80 | let key = TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().insert(transform_func)); 81 | struct DropGuard(DefaultKey); 82 | impl Drop for DropGuard { 83 | fn drop(&mut self) { 84 | TRANSFORMS_LEGACY.with(|transforms| transforms.borrow_mut().remove(self.0)); 85 | } 86 | } 87 | let key = DropGuard(key); 88 | let context = key.0.data().as_ffi().to_be_bytes().to_vec(); 89 | let arg = CanisterHttpRequestArgument { 90 | transform: Some(TransformContext { 91 | function: TransformFunc(candid::Func { 92 | method: " http_transform_legacy".into(), 93 | principal: crate::id(), 94 | }), 95 | context, 96 | }), 97 | ..arg 98 | }; 99 | http_request(arg, cycles).await 100 | } 101 | -------------------------------------------------------------------------------- /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 [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::{call, CallResult}; 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::{call, call_with_payment128, CallResult}; 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 KeyId. 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 | // SAFETY: ic0.stable64_size is always safe to call. 12 | unsafe { ic0::stable64_size() } 13 | } 14 | 15 | fn stable_grow(&self, new_pages: u64) -> Result { 16 | // SAFETY: ic0.stable64_grow is always safe to call. 17 | unsafe { 18 | match ic0::stable64_grow(new_pages) { 19 | u64::MAX => Err(StableMemoryError::OutOfMemory), 20 | x => Ok(x), 21 | } 22 | } 23 | } 24 | 25 | fn stable_write(&self, offset: u64, buf: &[u8]) { 26 | // SAFETY: `buf`, being &[u8], is a readable sequence of bytes, and therefore valid to pass to ic0.stable64_write. 27 | unsafe { 28 | ic0::stable64_write(offset, buf.as_ptr() as u64, buf.len() as u64); 29 | } 30 | } 31 | 32 | fn stable_read(&self, offset: u64, buf: &mut [u8]) { 33 | // SAFETY: `buf`, being &mut [u8], is a writable sequence of bytes, and therefore valid to pass to ic0.stable64_read. 34 | unsafe { 35 | ic0::stable64_read(buf.as_ptr() as u64, offset, buf.len() as u64); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ic-cdk/src/futures.rs: -------------------------------------------------------------------------------- 1 | //! Functions relating to the async executor. 2 | //! 3 | //! ## Running async tasks 4 | //! 5 | //! Most async tasks can be run just by changing your canister entry point to `async`: 6 | //! 7 | //! ``` 8 | //! # use ic_cdk::update; 9 | //! # async fn some_other_async_fn() {} 10 | //! #[update] 11 | //! async fn foo() { 12 | //! some_other_async_fn().await; 13 | //! } 14 | //! ``` 15 | //! 16 | //! To run async tasks in the *background*, however, use [`spawn`]: 17 | //! 18 | //! ``` 19 | //! # use ic_cdk::{update, futures::spawn}; 20 | //! # async fn some_other_async_fn() {} 21 | //! #[update] 22 | //! async fn foo() { 23 | //! spawn(async { some_other_async_fn().await; }); 24 | //! // do other stuff 25 | //! } 26 | //! ``` 27 | //! 28 | //! The spawned future will not be run at the same time as the remaining code, nor will it run immediately. It will start 29 | //! running while `foo` awaits (or after it ends if it does not await). Unlike some other libraries, `spawn` does not 30 | //! return a join-handle; if you want to await multiple results concurrently, use `futures`' [`join_all`] function. 31 | //! 32 | //! "Background" is a tricky subject on the IC. Background tasks can only run in the context of a canister message. 33 | //! If you await a future whose completion you manually trigger in code, such as sending to an async channel, 34 | //! then the code after the await will be in the call context of whatever you completed it in. This means that global state 35 | //! like [`caller`], [`in_replicated_execution`], and even [`canister_self`] may have changed. (The canister method 36 | //! itself cannot await anything triggered by another canister method, or you will get an error that it 'failed to reply'.) 37 | //! It will also take from that call's instruction limit, which can introduce hidden sources of instruction limit based traps. 38 | //! 39 | //! Most importantly, a background task that runs in other call contexts must never trap. When it traps, it will cancel 40 | //! (see below) the execution of the call whose context it's in, even though that call didn't do anything wrong, and it 41 | //! may not undo whatever caused it to trap, meaning the canister could end up bricked. Tasks that you expect to complete 42 | //! before the canister method ends are safe, but traps/panics in tasks that are expected to continue running into other 43 | //! calls/timers may produce surprising results and behavioral errors. 44 | //! 45 | //! ## Automatic cancellation 46 | //! 47 | //! Asynchronous tasks can be *canceled*, meaning that a partially completed function will halt at an 48 | //! `await` point, never complete, and drop its local variables as though it had returned. Cancellation 49 | //! is caused by panics and traps: if an async function panics, time will be rewound to the 50 | //! previous await as though the code since then never ran, and then the task will be canceled. 51 | //! 52 | //! Use panics sparingly in async functions after the first await, and beware system functions that trap 53 | //! (which is most of them in the right context). Make atomic transactions between awaits wherever 54 | //! possible, and use [`scopeguard`] or a [`Drop`] impl for any cleanup functions that must run no matter what. 55 | //! If an await cannot be removed from the middle of a transaction, and it must be rolled back if it fails, 56 | //! [`is_recovering_from_trap`] can be used to detect when the task is being automatically canceled. 57 | //! 58 | //! [`scopeguard`]: https://docs.rs/scopeguard 59 | //! [`join_all`]: https://docs.rs/futures/latest/futures/future/fn.join_all.html 60 | //! [`caller`]: crate::api::caller 61 | //! [`in_replicated_execution`]: crate::api::in_replicated_execution 62 | //! [`canister_self`]: crate::api::canister_self 63 | 64 | use std::future::Future; 65 | 66 | /// Spawn an asynchronous task to run in the background. For information about semantics, see 67 | /// [the module docs](self). 68 | pub fn spawn>(future: F) { 69 | ic_cdk_executor::spawn(future); 70 | } 71 | 72 | /// Execute an update function in a context that allows calling [`spawn`]. 73 | /// 74 | /// You do not need to worry about this function unless you are avoiding the attribute macros. 75 | /// 76 | /// Background tasks will be polled in the process (and will not be run otherwise). 77 | /// Panics if called inside an existing executor context. 78 | pub fn in_executor_context(f: impl FnOnce() -> R) -> R { 79 | crate::setup(); 80 | ic_cdk_executor::in_executor_context(f) 81 | } 82 | 83 | /// Execute a composite query function in a context that allows calling [`spawn`]. 84 | /// 85 | /// You do not need to worry about this function unless you are avoiding the attribute macros. 86 | /// 87 | /// Background composite query tasks will be polled in the process (and will not be run otherwise). 88 | /// Panics if called inside an existing executor context. 89 | pub fn in_query_executor_context(f: impl FnOnce() -> R) -> R { 90 | crate::setup(); 91 | ic_cdk_executor::in_query_executor_context(f) 92 | } 93 | 94 | /// Tells you whether the current async fn is being canceled due to a trap/panic. 95 | /// 96 | /// In a destructor, `is_recovering_from_trap` serves the same purpose as 97 | /// [std::thread::panicking] - it tells you whether the destructor is executing *because* of a trap, 98 | /// as opposed to just because the scope was exited, so you could e.g. implement mutex poisoning. 99 | /// 100 | /// For information about when and how this occurs, see [the module docs](self). 101 | pub fn is_recovering_from_trap() -> bool { 102 | ic_cdk_executor::is_recovering_from_trap() 103 | } 104 | -------------------------------------------------------------------------------- /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 | mod printer; 22 | pub mod stable; 23 | pub mod storage; 24 | 25 | use std::{ 26 | future::Future, 27 | sync::{Arc, Once}, 28 | task::{Context, Poll, Wake, Waker}, 29 | }; 30 | 31 | #[doc(inline)] 32 | pub use api::trap; 33 | 34 | #[doc(inline)] 35 | #[allow(deprecated)] 36 | pub use api::{ 37 | call::{call, notify}, 38 | caller, id, print, 39 | }; 40 | 41 | #[doc(inline)] 42 | pub use macros::*; 43 | 44 | static SETUP: Once = Once::new(); 45 | 46 | /// Setup the stdlib hooks. 47 | fn setup() { 48 | SETUP.call_once(printer::hook); 49 | } 50 | 51 | /// Format and then print the formatted message 52 | #[cfg(target_family = "wasm")] 53 | #[macro_export] 54 | macro_rules! println { 55 | ($fmt:expr) => ($crate::api::debug_print(format!($fmt))); 56 | ($fmt:expr, $($arg:tt)*) => ($crate::api::debug_print(format!($fmt, $($arg)*))); 57 | } 58 | 59 | /// Format and then print the formatted message 60 | #[cfg(not(target_family = "wasm"))] 61 | #[macro_export] 62 | macro_rules! println { 63 | ($fmt:expr) => (std::println!($fmt)); 64 | ($fmt:expr, $($arg:tt)*) => (std::println!($fmt, $($arg)*)); 65 | } 66 | 67 | /// Format and then print the formatted message 68 | #[cfg(target_family = "wasm")] 69 | #[macro_export] 70 | macro_rules! eprintln { 71 | ($fmt:expr) => ($crate::api::debug_print(format!($fmt))); 72 | ($fmt:expr, $($arg:tt)*) => ($crate::api::debug_print(format!($fmt, $($arg)*))); 73 | } 74 | 75 | /// Format and then print the formatted message 76 | #[cfg(not(target_family = "wasm"))] 77 | #[macro_export] 78 | macro_rules! eprintln { 79 | ($fmt:expr) => (std::eprintln!($fmt)); 80 | ($fmt:expr, $($arg:tt)*) => (std::eprintln!($fmt, $($arg)*)); 81 | } 82 | 83 | #[doc(hidden)] 84 | #[deprecated( 85 | since = "0.18.0", 86 | note = "Use ic_cdk::futures::spawn. Compatibility notice: Code execution order will change, \ 87 | see https://github.com/dfinity/cdk-rs/blob/0.18.3/ic-cdk/V18_GUIDE.md#futures-ordering-changes" 88 | )] 89 | pub fn spawn>(fut: F) { 90 | struct DummyWaker; 91 | impl Wake for DummyWaker { 92 | fn wake(self: Arc) { 93 | panic!("Your code is incompatible with the ic_cdk::spawn compatibility adapter. Migrate to ic_cdk::futures::spawn. \ 94 | Notice: Code execution order will change, see https://github.com/dfinity/cdk-rs/blob/0.18.3/ic-cdk/V18_GUIDE.md#futures-ordering-changes") 95 | } 96 | } 97 | // Emulated behavior: A spawned future is polled once immediately, then backgrounded and run at a normal pace. 98 | // We poll it once with an unimplemented waker, then spawn it, which will poll it again with the real waker. 99 | // In a correctly implemented future, this second poll should overwrite the fake waker with the real one. 100 | // The only way to hit the fake waker's wake function is if the first poll calls wake. 101 | // A more complex compat adapter will be needed to handle this case. 102 | let mut pin = Box::pin(fut); 103 | let poll = pin 104 | .as_mut() 105 | .poll(&mut Context::from_waker(&Waker::from(Arc::new(DummyWaker)))); 106 | match poll { 107 | Poll::Ready(()) => {} 108 | Poll::Pending => crate::futures::spawn(pin), 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ic-cdk/src/printer.rs: -------------------------------------------------------------------------------- 1 | use crate::api; 2 | use std::panic; 3 | 4 | /// Sets a custom panic hook, uses debug.trace 5 | pub fn set_panic_hook() { 6 | panic::set_hook(Box::new(|info| { 7 | let file = info.location().unwrap().file(); 8 | let line = info.location().unwrap().line(); 9 | let col = info.location().unwrap().column(); 10 | 11 | let msg = match info.payload().downcast_ref::<&'static str>() { 12 | Some(s) => *s, 13 | None => match info.payload().downcast_ref::() { 14 | Some(s) => &s[..], 15 | None => "Box", 16 | }, 17 | }; 18 | 19 | let err_info = format!("Panicked at '{}', {}:{}:{}", msg, file, line, col); 20 | api::debug_print(&err_info); 21 | api::trap(&err_info); 22 | })); 23 | } 24 | 25 | /// Sets stdout, stderr, and a custom panic hook 26 | pub fn hook() { 27 | set_panic_hook(); 28 | } 29 | -------------------------------------------------------------------------------- /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 = 24 | candid::de::IDLDeserialize::new(bytes.as_slice()).map_err(|e| format!("{:?}", e))?; 25 | let res = candid::utils::ArgumentDecoder::decode(&mut de).map_err(|e| format!("{:?}", e))?; 26 | Ok(res) 27 | } 28 | -------------------------------------------------------------------------------- /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 | unreachable!() 17 | } 18 | 19 | #[candid_method(update)] 20 | fn bitcoin_get_utxos(_: GetUtxosRequest) -> GetUtxosResponse { 21 | unreachable!() 22 | } 23 | 24 | #[candid_method(update)] 25 | fn bitcoin_get_current_fee_percentiles( 26 | _: GetCurrentFeePercentilesRequest, 27 | ) -> Vec { 28 | unreachable!() 29 | } 30 | 31 | #[candid_method(update)] 32 | fn bitcoin_get_block_headers(_: GetBlockHeadersRequest) -> GetBlockHeadersResponse { 33 | unreachable!() 34 | } 35 | 36 | #[candid_method(update)] 37 | fn bitcoin_send_transaction(_: SendTransactionRequest) { 38 | unreachable!() 39 | } 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use candid_parser::utils::{service_equal, CandidSource}; 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 | ## [0.3.1] - 2025-05-09 11 | 12 | ### Added 13 | 14 | - Types for `vetkd_public_key` and `vetkd_derive_key`. 15 | 16 | ## [0.3.0] - 2025-03-17 17 | 18 | ### Changed 19 | 20 | - Added `wasm_memory_threshold` field to `CanisterSettings` and `DefiniteCanisterSettings`. 21 | - Added the `memory_metrics` field to `CanisterStatusResult`. 22 | - Added the type `MemoryMetrics`. 23 | 24 | ### Added 25 | 26 | - Implemented trait that convert from `EcdsaCurve` and `SchnorrAlgorithm` into `u32`. 27 | 28 | ## [0.2.1] - 2025-02-28 29 | 30 | ### Added 31 | 32 | - Types for `fetch_canister_logs`. 33 | - `CanisterIdRecord`, an alias for various argument and result types to enhance inter-operability. 34 | 35 | ### Fixed 36 | 37 | - Doc: `HttpRequestArgs::max_response_bytes` is capped at 2MB, not 2MiB. 38 | 39 | ## [0.2.0] - 2025-02-18 40 | 41 | ### Changed 42 | 43 | - Added `aux` field in `SignWithSchnorrArgs`, introducing `SchnorrAux` and `Bip341` types. 44 | - Fixed `NodeMetrics` which should have a field `num_block_failures_total`, not `num_blocks_failures_total`. 45 | 46 | ## [0.1.0] - 2023-01-22 47 | 48 | ### Added 49 | 50 | - Initial release of the `ic-management-canister-types` library. 51 | -------------------------------------------------------------------------------- /ic-management-canister-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-management-canister-types" 3 | version = "0.3.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 | unreachable!() 8 | } 9 | 10 | #[candid_method(update)] 11 | fn update_settings(_: UpdateSettingsArgs) { 12 | unreachable!() 13 | } 14 | 15 | #[candid_method(update)] 16 | fn upload_chunk(_: UploadChunkArgs) -> UploadChunkResult { 17 | unreachable!() 18 | } 19 | 20 | #[candid_method(update)] 21 | fn clear_chunk_store(_: ClearChunkStoreArgs) { 22 | unreachable!() 23 | } 24 | 25 | #[candid_method(update)] 26 | fn stored_chunks(_: StoredChunksArgs) -> StoredChunksResult { 27 | unreachable!() 28 | } 29 | 30 | #[candid_method(update)] 31 | fn install_code(_: InstallCodeArgs) { 32 | unreachable!() 33 | } 34 | 35 | #[candid_method(update)] 36 | fn install_chunked_code(_: InstallChunkedCodeArgs) { 37 | unreachable!() 38 | } 39 | 40 | #[candid_method(update)] 41 | fn uninstall_code(_: UninstallCodeArgs) { 42 | unreachable!() 43 | } 44 | 45 | #[candid_method(update)] 46 | fn start_canister(_: StartCanisterArgs) { 47 | unreachable!() 48 | } 49 | 50 | #[candid_method(update)] 51 | fn stop_canister(_: StopCanisterArgs) { 52 | unreachable!() 53 | } 54 | 55 | #[candid_method(update)] 56 | fn canister_status(_: CanisterStatusArgs) -> CanisterStatusResult { 57 | unreachable!() 58 | } 59 | 60 | #[candid_method(update)] 61 | fn canister_info(_: CanisterInfoArgs) -> CanisterInfoResult { 62 | unreachable!() 63 | } 64 | 65 | #[candid_method(update)] 66 | fn subnet_info(_: SubnetInfoArgs) -> SubnetInfoResult { 67 | unreachable!() 68 | } 69 | 70 | #[candid_method(update)] 71 | fn delete_canister(_: DeleteCanisterArgs) { 72 | unreachable!() 73 | } 74 | 75 | #[candid_method(update)] 76 | fn deposit_cycles(_: DepositCyclesArgs) { 77 | unreachable!() 78 | } 79 | 80 | #[candid_method(update)] 81 | fn raw_rand() -> RawRandResult { 82 | unreachable!() 83 | } 84 | 85 | #[candid_method(update)] 86 | fn http_request(_: HttpRequestArgs) -> HttpRequestResult { 87 | unreachable!() 88 | } 89 | 90 | #[candid_method(update)] 91 | fn ecdsa_public_key(_: EcdsaPublicKeyArgs) -> EcdsaPublicKeyResult { 92 | unreachable!() 93 | } 94 | 95 | #[candid_method(update)] 96 | fn sign_with_ecdsa(_: SignWithEcdsaArgs) -> SignWithEcdsaResult { 97 | unreachable!() 98 | } 99 | 100 | #[candid_method(update)] 101 | fn schnorr_public_key(_: SchnorrPublicKeyArgs) -> SchnorrPublicKeyResult { 102 | unreachable!() 103 | } 104 | 105 | #[candid_method(update)] 106 | fn sign_with_schnorr(_: SignWithSchnorrArgs) -> SignWithSchnorrResult { 107 | unreachable!() 108 | } 109 | 110 | #[candid_method(update)] 111 | fn node_metrics_history(_: NodeMetricsHistoryArgs) -> NodeMetricsHistoryResult { 112 | unreachable!() 113 | } 114 | 115 | #[candid_method(update)] 116 | fn provisional_create_canister_with_cycles( 117 | _: ProvisionalCreateCanisterWithCyclesArgs, 118 | ) -> ProvisionalCreateCanisterWithCyclesResult { 119 | unreachable!() 120 | } 121 | 122 | #[candid_method(update)] 123 | fn provisional_top_up_canister(_: ProvisionalTopUpCanisterArgs) { 124 | unreachable!() 125 | } 126 | 127 | #[candid_method(update)] 128 | fn take_canister_snapshot(_: TakeCanisterSnapshotArgs) -> TakeCanisterSnapshotResult { 129 | unreachable!() 130 | } 131 | 132 | #[candid_method(update)] 133 | fn load_canister_snapshot(_: LoadCanisterSnapshotArgs) { 134 | unreachable!() 135 | } 136 | 137 | #[candid_method(update)] 138 | fn list_canister_snapshots(_: ListCanisterSnapshotsArgs) -> ListCanisterSnapshotsResult { 139 | unreachable!() 140 | } 141 | 142 | #[candid_method(update)] 143 | fn delete_canister_snapshot(_: DeleteCanisterSnapshotArgs) { 144 | unreachable!() 145 | } 146 | 147 | #[candid_method(query)] 148 | fn fetch_canister_logs(_: FetchCanisterLogsArgs) -> FetchCanisterLogsResult { 149 | unreachable!() 150 | } 151 | 152 | #[cfg(test)] 153 | mod test { 154 | use candid_parser::utils::{service_equal, CandidSource}; 155 | use ic_management_canister_types::*; 156 | 157 | #[test] 158 | fn candid_equality_test() { 159 | let declared_interface_str = 160 | std::fs::read_to_string("tests/ic.did").expect("Failed to read ic.did file"); 161 | let declared_interface = CandidSource::Text(&declared_interface_str); 162 | 163 | candid::export_service!(); 164 | let implemented_interface_str = __export_service(); 165 | let implemented_interface = CandidSource::Text(&implemented_interface_str); 166 | 167 | let result = service_equal(declared_interface, implemented_interface); 168 | assert!(result.is_ok(), "{:?}", result.unwrap_err()); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /ic0/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic0" 3 | version = "0.25.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 an unsafe Rust translation of the System API as described in the [IC interface specification][1]. 8 | 9 | ## Update 10 | 11 | `ic0` keeps in step with the IC interface specification. Particularly, `ic0` is directly generated from [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. execute `cargo run --example=ic0build`; 17 | 18 | `src/ic0.rs` should be updated. 19 | 20 | [1]: https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-imports 21 | -------------------------------------------------------------------------------- /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.debug_print : (src : I, size : I) -> (); // * s 79 | ic0.trap : (src : I, size : I) -> (); // * s 80 | -------------------------------------------------------------------------------- /ic0/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Raw bindings to the [Internet Computer system API](https://internetcomputer.org/docs/current/references/ic-interface-spec#system-api-imports). 2 | 3 | #![warn( 4 | elided_lifetimes_in_paths, 5 | missing_debug_implementations, 6 | unsafe_op_in_unsafe_fn, 7 | clippy::undocumented_unsafe_blocks, 8 | clippy::missing_safety_doc 9 | )] 10 | 11 | mod ic0; 12 | pub use crate::ic0::*; 13 | -------------------------------------------------------------------------------- /ic0/util/ic0build.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::parse::{Parse, ParseStream, Result}; 3 | use syn::punctuated::Punctuated; 4 | use syn::spanned::Spanned; 5 | use syn::token::Comma; 6 | use syn::{parenthesized, Error}; 7 | use syn::{FnArg, Ident, Token, TypePath}; 8 | 9 | use std::fs; 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | use std::process::Command; 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct SystemAPI { 16 | pub name: Ident, 17 | pub args: Vec, 18 | pub output: Option, 19 | } 20 | 21 | impl Parse for SystemAPI { 22 | fn parse(input: ParseStream) -> Result { 23 | let ic0_token: Ident = input.parse()?; 24 | if ic0_token != "ic0" { 25 | return Err(Error::new(ic0_token.span(), "expected `ic0`")); 26 | } 27 | input.parse::()?; 28 | let name: Ident = input.parse()?; 29 | input.parse::()?; 30 | 31 | // args 32 | let content; 33 | parenthesized!(content in input); 34 | let args = Punctuated::::parse_terminated(&content)?; 35 | let args: Vec = args.iter().cloned().collect(); 36 | for arg in &args { 37 | match arg { 38 | FnArg::Receiver(r) => return Err(Error::new(r.span(), "arguments can't be self")), 39 | FnArg::Typed(pat_type) => match &*pat_type.ty { 40 | syn::Type::Path(ty) => { 41 | type_supported(ty)?; 42 | } 43 | _ => { 44 | return Err(Error::new( 45 | pat_type.span(), 46 | "argument types can only be i32, i64 or isize", 47 | )) 48 | } 49 | }, 50 | } 51 | } 52 | 53 | input.parse::]>()?; 54 | 55 | // output 56 | let output = if input.peek(syn::token::Paren) { 57 | let content; 58 | parenthesized!(content in input); 59 | if content.is_empty() { 60 | None 61 | } else { 62 | let _output_name: Ident = content.parse()?; 63 | content.parse::()?; 64 | let ty: TypePath = content.parse()?; 65 | if !content.is_empty() { 66 | return Err(Error::new(ty.span(), "expected only one return type")); 67 | } 68 | type_supported(&ty)?; 69 | Some(ty) 70 | } 71 | } else { 72 | let ty: TypePath = input.parse()?; 73 | type_supported(&ty)?; 74 | Some(ty) 75 | }; 76 | 77 | input.parse::()?; 78 | 79 | Ok(Self { name, args, output }) 80 | } 81 | } 82 | 83 | fn type_supported(ty: &TypePath) -> Result<()> { 84 | let ty = ty 85 | .path 86 | .get_ident() 87 | .ok_or(Error::new(ty.span(), "cannot get ident from: {ty:?}"))?; 88 | if ty == "u32" || ty == "u64" || ty == "usize" { 89 | Ok(()) 90 | } else { 91 | Err(Error::new( 92 | ty.span(), 93 | "ic0.txt should only contain i32, i64 or I", 94 | )) 95 | } 96 | } 97 | 98 | #[derive(Clone, Debug)] 99 | pub struct IC0 { 100 | pub apis: Vec, 101 | } 102 | 103 | impl Parse for IC0 { 104 | fn parse(input: ParseStream) -> Result { 105 | Ok(Self { 106 | apis: { 107 | let mut apis = vec![]; 108 | while !input.is_empty() { 109 | apis.push(input.parse()?); 110 | } 111 | apis 112 | }, 113 | }) 114 | } 115 | } 116 | 117 | fn main() { 118 | let s = include_str!("../ic0.txt"); 119 | let s = s.replace('I', "usize"); 120 | let s = s.replace("i32", "u32"); 121 | let s = s.replace("i64", "u64"); 122 | let ic0: IC0 = syn::parse_str(&s).unwrap(); 123 | 124 | let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 125 | d.push("src/ic0.rs"); 126 | 127 | let mut f = fs::File::create(d).unwrap(); 128 | 129 | writeln!( 130 | f, 131 | r#"// This file is generated from ic0.txt. 132 | // Don't manually modify it. 133 | #[cfg(target_family = "wasm")] 134 | #[link(wasm_import_module = "ic0")] 135 | extern "C" {{"#, 136 | ) 137 | .unwrap(); 138 | 139 | for api in &ic0.apis { 140 | let fn_name = &api.name; 141 | let args = &api.args; 142 | 143 | let mut r = quote! { 144 | pub fn #fn_name(#(#args),*) 145 | }; 146 | 147 | if let Some(output) = &api.output { 148 | r = quote! { 149 | #r -> #output 150 | } 151 | } 152 | 153 | r = quote! {#r;}; 154 | writeln!(f, "{}", r).unwrap(); 155 | } 156 | 157 | writeln!(f, "}}").unwrap(); 158 | 159 | writeln!( 160 | f, 161 | r#" 162 | #[cfg(not(target_family = "wasm"))] 163 | #[allow(unused_variables)] 164 | #[allow(clippy::missing_safety_doc)] 165 | #[allow(clippy::too_many_arguments)] 166 | mod non_wasm{{"#, 167 | ) 168 | .unwrap(); 169 | 170 | for api in &ic0.apis { 171 | let fn_name = &api.name; 172 | let args = &api.args; 173 | 174 | let mut r = quote! { 175 | pub unsafe fn #fn_name(#(#args),*) 176 | }; 177 | 178 | if let Some(output) = &api.output { 179 | r = quote! { 180 | #r -> #output 181 | } 182 | } 183 | 184 | let panic_str = format!("{} should only be called inside canisters.", fn_name); 185 | 186 | r = quote! { 187 | #r { 188 | panic!(#panic_str); 189 | }}; 190 | writeln!(f, "{}", r).unwrap(); 191 | } 192 | 193 | writeln!( 194 | f, 195 | r#"}} 196 | 197 | #[cfg(not(target_family = "wasm"))] 198 | pub use non_wasm::*; 199 | "# 200 | ) 201 | .unwrap(); 202 | 203 | Command::new("cargo") 204 | .args(["fmt"]) 205 | .output() 206 | .expect("`cargo fmt` failed"); 207 | } 208 | -------------------------------------------------------------------------------- /ic0/util/work.rs: -------------------------------------------------------------------------------- 1 | // This file should compile on windows 2 | fn main() { 3 | unsafe { 4 | ic0::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 | ### Added 10 | - Implement CandidType, Serialize, and Deserialize for the RbTree. 11 | 12 | ## [0.4.0] - 2023-07-13 13 | 14 | ### Changed 15 | - Upgrade `ic-cdk` to v0.10 and `candid` to v0.9. 16 | 17 | ## [0.3.4] - 2023-03-01 18 | ### Added 19 | - Derive common traits for structs. 20 | 21 | ## [0.3.3] - 2023-02-22 22 | ### Fixed 23 | - Update links in doc. 24 | 25 | ## [0.3.2] - 2022-11-10 26 | ### Changed 27 | - Make `RbTree::new` and `RbTree::is_empty` both `const`. 28 | 29 | ## [0.3.1] - 2022-09-16 30 | ### Changed 31 | - Updated `sha2` dependency. 32 | 33 | ## [0.3.0] - 2022-01-13 34 | ### Added 35 | - `RbTree::iter()` method. 36 | - impls of `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `FromIterator`, and `Debug` for `RbTree`. 37 | 38 | ## [0.2.0] - 2021-09-16 39 | ### Added 40 | - `RbTree::value_range()` method to get a witness for a range of keys with values. 41 | 42 | ### Changed 43 | - RbTree::key_range() method returns tighter key bounds which reduces the size of witnesses. 44 | - Updated the version of candid from `0.6.19` to `0.7.1` ([#72](https://github.com/dfinity/cdk-rs/pull/72)). 45 | - Hash tree leaves can now hold both references and values ([#121](https://github.com/dfinity/cdk-rs/issues/121)). 46 | This is a BREAKING CHANGE, some clients might need to slightly change and recompile their code. 47 | 48 | ## [0.1.0] - 2021-05-04 49 | ### Added 50 | * Initial release of the library. 51 | -------------------------------------------------------------------------------- /library/ic-certified-map/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ic-certified-map" 3 | version = "0.4.0" 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.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test; 3 | 4 | use serde::{ser::SerializeSeq, Serialize, Serializer}; 5 | use serde_bytes::Bytes; 6 | use sha2::{Digest, Sha256}; 7 | use std::borrow::Cow; 8 | 9 | /// SHA-256 hash bytes. 10 | pub type Hash = [u8; 32]; 11 | 12 | /// HashTree as defined in the [interfaces spec](https://internetcomputer.org/docs/current/references/ic-interface-spec#certificate). 13 | #[derive(Debug, Clone, Default)] 14 | pub enum HashTree<'a> { 15 | /// No child nodes; a proof of absence. 16 | #[default] 17 | Empty, 18 | /// Left and right child branches. 19 | Fork(Box<(HashTree<'a>, HashTree<'a>)>), 20 | /// A labeled child node. 21 | Labeled(&'a [u8], Box>), 22 | /// A leaf node containing a value or hash. 23 | Leaf(Cow<'a, [u8]>), 24 | /// A branch that has been removed from this view of the tree, but is not necessarily absent. 25 | Pruned(Hash), 26 | } 27 | 28 | /// Shorthand for [`HashTree::Fork`]. 29 | pub fn fork<'a>(l: HashTree<'a>, r: HashTree<'a>) -> HashTree<'a> { 30 | HashTree::Fork(Box::new((l, r))) 31 | } 32 | 33 | /// Shorthand for [`HashTree::Labeled`]. 34 | pub fn labeled<'a>(l: &'a [u8], t: HashTree<'a>) -> HashTree<'a> { 35 | HashTree::Labeled(l, Box::new(t)) 36 | } 37 | 38 | /// Identifiably hashes a fork in the branch. Used for hashing [`HashTree::Fork`]. 39 | pub fn fork_hash(l: &Hash, r: &Hash) -> Hash { 40 | let mut h = domain_sep("ic-hashtree-fork"); 41 | h.update(&l[..]); 42 | h.update(&r[..]); 43 | h.finalize().into() 44 | } 45 | 46 | /// Identifiably hashes a leaf node's data. Used for hashing [`HashTree::Leaf`]. 47 | pub fn leaf_hash(data: &[u8]) -> Hash { 48 | let mut h = domain_sep("ic-hashtree-leaf"); 49 | h.update(data); 50 | h.finalize().into() 51 | } 52 | 53 | /// Identifiably hashes a label for this branch. Used for hashing [`HashTree::Labeled`]. 54 | pub fn labeled_hash(label: &[u8], content_hash: &Hash) -> Hash { 55 | let mut h = domain_sep("ic-hashtree-labeled"); 56 | h.update(label); 57 | h.update(&content_hash[..]); 58 | h.finalize().into() 59 | } 60 | 61 | impl HashTree<'_> { 62 | /// Produces the root hash of the tree. 63 | pub fn reconstruct(&self) -> Hash { 64 | match self { 65 | Self::Empty => domain_sep("ic-hashtree-empty").finalize().into(), 66 | Self::Fork(f) => fork_hash(&f.0.reconstruct(), &f.1.reconstruct()), 67 | Self::Labeled(l, t) => { 68 | let thash = t.reconstruct(); 69 | labeled_hash(l, &thash) 70 | } 71 | Self::Leaf(data) => leaf_hash(data), 72 | Self::Pruned(h) => *h, 73 | } 74 | } 75 | } 76 | 77 | impl Serialize for HashTree<'_> { 78 | fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> 79 | where 80 | S: Serializer, 81 | { 82 | match self { 83 | HashTree::Empty => { 84 | let mut seq = serializer.serialize_seq(Some(1))?; 85 | seq.serialize_element(&0u8)?; 86 | seq.end() 87 | } 88 | HashTree::Fork(p) => { 89 | let mut seq = serializer.serialize_seq(Some(3))?; 90 | seq.serialize_element(&1u8)?; 91 | seq.serialize_element(&p.0)?; 92 | seq.serialize_element(&p.1)?; 93 | seq.end() 94 | } 95 | HashTree::Labeled(label, tree) => { 96 | let mut seq = serializer.serialize_seq(Some(3))?; 97 | seq.serialize_element(&2u8)?; 98 | seq.serialize_element(Bytes::new(label))?; 99 | seq.serialize_element(&tree)?; 100 | seq.end() 101 | } 102 | HashTree::Leaf(leaf_bytes) => { 103 | let mut seq = serializer.serialize_seq(Some(2))?; 104 | seq.serialize_element(&3u8)?; 105 | seq.serialize_element(Bytes::new(leaf_bytes.as_ref()))?; 106 | seq.end() 107 | } 108 | HashTree::Pruned(digest) => { 109 | let mut seq = serializer.serialize_seq(Some(2))?; 110 | seq.serialize_element(&4u8)?; 111 | seq.serialize_element(Bytes::new(&digest[..]))?; 112 | seq.end() 113 | } 114 | } 115 | } 116 | } 117 | 118 | fn domain_sep(s: &str) -> sha2::Sha256 { 119 | let buf: [u8; 1] = [s.len() as u8]; 120 | let mut h = Sha256::new(); 121 | h.update(&buf[..]); 122 | h.update(s.as_bytes()); 123 | h 124 | } 125 | -------------------------------------------------------------------------------- /library/ic-certified-map/src/hashtree/test.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | fork, labeled, 3 | HashTree::{Empty, Leaf}, 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 | assert_eq!( 33 | hex::encode(&t.reconstruct()[..]), 34 | "eb5c5b2195e62d996b84c9bcc8259d19a83786a2f59e0878cec84c811f669aa0".to_string() 35 | ); 36 | 37 | assert_eq!( 38 | hex::encode(serde_cbor::to_vec(&t).unwrap()), 39 | "8301830183024161830183018302417882034568656c6c6f810083024179820345776f726c6483024162820344676f6f648301830241638100830241648203476d6f726e696e67".to_string()); 40 | } 41 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------