Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ rust-version = "1.88.0"
license = "Apache-2.0"

[workspace.dependencies]
ic-cdk = { path = "ic-cdk", version = "0.20.0" }
# Crates of this workspace
ic-cdk = { path = "ic-cdk", version = "0.20.1" }
ic-cdk-bindgen = { path = "ic-cdk-bindgen", version = "0.2.0" }
ic-cdk-bitcoin-canister = { path = "ic-cdk-bitcoin-canister", version = "0.2.0" }
ic-cdk-executor = { path = "ic-cdk-executor", version = "2.0.0" }
ic-cdk-macros = { path = "ic-cdk-macros", version = "=0.20.0" }
ic-cdk-macros = { path = "ic-cdk-macros", version = "=0.20.1" }
ic-cdk-management-canister = { path = "ic-cdk-management-canister", version = "0.1.1" }
ic-cdk-timers = { path = "ic-cdk-timers", version = "1.0.0" }
ic-management-canister-types = "0.7.1"
# Crates of this workspace
ic0 = { path = "ic0", version = "1.0.1" }
ic0 = { path = "ic0", version = "1.1.0" }

# Regular dependencies
## sync candid version with the doc comment in ic-cdk/README.md
Expand Down Expand Up @@ -66,7 +66,7 @@ escargot = "0.5.15"
futures = "0.3"
ic-vetkd-utils = { git = "https://github.com/dfinity/ic", rev = "95231520" }
lazy_static = "1.5.0"
pocket-ic = { git = "https://github.com/dfinity/ic", tag = "release-2026-03-02_11-09-base" }
pocket-ic = { git = "https://github.com/dfinity/ic", tag = "release-2026-04-16_04-20-base" }
prost = "0.14.3"
prost-build = "0.14.3"
reqwest = "0.13.2"
Expand Down
17 changes: 17 additions & 0 deletions e2e-tests/src/bin/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ fn call_msg_reply() {
msg_reply(vec![42]);
}

/// Returns the caller info data bytes provided in the `sender_info`.
/// Returns empty bytes if no `sender_info` was provided.
#[unsafe(export_name = "canister_update call_msg_caller_info_data")]
fn call_msg_caller_info_data() {
msg_reply(msg_caller_info_data());
}

/// Returns the caller info signer as raw principal bytes.
/// Returns empty bytes if no `sender_info` was provided.
#[unsafe(export_name = "canister_update call_msg_caller_info_signer")]
fn call_msg_caller_info_signer() {
let signer_bytes = msg_caller_info_signer()
.map(|p| p.as_slice().to_vec())
.unwrap_or_default();
msg_reply(signer_bytes);
}

#[unsafe(export_name = "canister_update call_msg_reject")]
fn call_msg_reject() {
msg_reject("e2e test reject");
Expand Down
43 changes: 40 additions & 3 deletions e2e-tests/tests/api.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use candid::Principal;
use ic_cdk_management_canister::{CanisterSettings, EnvironmentVariable, UpdateSettingsArgs};
use pocket_ic::ErrorCode;
use pocket_ic::{ErrorCode, common::rest::RawSenderInfo};

mod test_utilities;
use test_utilities::{cargo_build_canister, pic_base, update};

#[test]
fn call_api() {
let wasm = cargo_build_canister("api");
// with_ii_subnet is required for testing the ic0.cost_sign_with_* API with pre-defined key name.
let pic = pic_base().with_ii_subnet().build();
// with_test_threshold_keys_subnet is required for testing the ic0.cost_sign_with_* API with pre-defined key name.
let pic = pic_base().with_test_threshold_keys_subnet().build();
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 100_000_000_000_000);
pic.install_canister(canister_id, wasm, vec![], None);
Expand All @@ -25,6 +25,43 @@ fn call_api() {
// Unlike the other entry points, `call_msg_dealine_caller` was implemented with the `#[update]` macro.
// So we use the update method which assumes candid
let _: () = update(&pic, canister_id, "call_msg_deadline_caller", ()).unwrap();
let info_data = b"test_caller_info_data";
let sender_info = RawSenderInfo {
info: info_data.to_vec(),
signer: canister_id.as_slice().to_vec(),
};
// With sender_info: data should equal the info bytes that were sent.
let res = pic
.update_call_with_sender_info(
canister_id,
sender,
"call_msg_caller_info_data",
vec![],
sender_info.clone(),
)
.unwrap();
assert_eq!(res, info_data);
// Without sender_info: data should be empty.
let res = pic
.update_call(canister_id, sender, "call_msg_caller_info_data", vec![])
.unwrap();
assert!(res.is_empty());
// With sender_info: signer should be the canister_id principal bytes.
let res = pic
.update_call_with_sender_info(
canister_id,
sender,
"call_msg_caller_info_signer",
Comment thread
aterga marked this conversation as resolved.
vec![],
sender_info,
)
.unwrap();
assert_eq!(res, canister_id.as_slice());
// Without sender_info: signer should be None, returning empty bytes.
let res = pic
.update_call(canister_id, sender, "call_msg_caller_info_signer", vec![])
.unwrap();
assert!(res.is_empty());
// `msg_reject_code` and `msg_reject_msg` can't be tested here.
// They are invoked in the reply/reject callback of inter-canister calls.
// So the `call.rs` test covers them.
Expand Down
6 changes: 4 additions & 2 deletions e2e-tests/tests/management_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use test_utilities::{cargo_build_canister, pic_base, update};
#[test]
fn test_management_canister() {
let wasm = cargo_build_canister("management_canister");
// Setup pocket-ic with an II subnet which is required by the "provisional" test.
let pic = pic_base().with_ii_subnet().build();
let pic = pic_base()
.with_ii_subnet() // II subnet is required by the "provisional" test.
.with_test_threshold_keys_subnet() // threshold keys subnet is required for testing ecdsa/schnorr signing APIs with pre-defined key name.
.build();

let canister_id = pic.create_canister();
let subnet_id = pic.get_subnet(canister_id).unwrap();
Expand Down
22 changes: 14 additions & 8 deletions e2e-tests/tests/test_utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,30 +114,36 @@ fn check_pocket_ic_server() -> PathBuf {
.iter()
.find(|m| m.name.as_ref() == "ic-cdk-e2e-tests")
.expect("ic-cdk-e2e-tests not found in Cargo.toml");
let pocket_ic_tag = e2e_tests_package
let source_repr = &e2e_tests_package
.dependencies
.iter()
.find(|d| d.name == "pocket-ic")
.expect("pocket-ic not found in Cargo.toml")
.source
.as_ref()
.expect("pocket-ic source not found in Cargo.toml")
.repr
.split_once("tag=")
.expect("`tag=` not found in pocket-ic source")
.1;
.repr;
// Source URL is e.g. `git+https://...?rev=<hash>#<hash>` or `git+https://...?tag=<tag>#<hash>`.
// Extract the value after `rev=` or `tag=`, stopping at `#` or `&`.
let pocket_ic_ref = if let Some((_, rest)) = source_repr.split_once("rev=") {
rest.split_once(['#', '&']).map_or(rest, |(v, _)| v)
} else if let Some((_, rest)) = source_repr.split_once("tag=") {
rest.split_once(['#', '&']).map_or(rest, |(v, _)| v)
} else {
panic!("neither `rev=` nor `tag=` found in pocket-ic source: {source_repr}")
};
let target_dir = metadata.target_directory;
let artifact_dir = target_dir.join("e2e-tests-artifacts");
let tag_path = artifact_dir.join("pocket-ic-tag");
let server_binary_path = artifact_dir.join("pocket-ic");
if let Ok(tag) = std::fs::read_to_string(&tag_path)
&& tag == pocket_ic_tag
if let Ok(cached) = std::fs::read_to_string(&tag_path)
&& cached == pocket_ic_ref
&& server_binary_path.exists()
{
return server_binary_path.into();
}
panic!(
"pocket-ic server not found or tag mismatch, please run `scripts/download_pocket_ic_server.sh` in the project root"
"pocket-ic server not found or version mismatch, please run `scripts/download_pocket_ic_server.sh` in the project root"
);
}

Expand Down
2 changes: 1 addition & 1 deletion ic-cdk-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic-cdk-macros"
version = "0.20.0" # sync with ic-cdk
version = "0.20.1" # sync with ic-cdk
authors.workspace = true
edition.workspace = true
license.workspace = true
Expand Down
35 changes: 16 additions & 19 deletions ic-cdk-timers/src/global_timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,28 @@ fn do_timer(
interval,
concurrent_calls,
..
} => {
if *concurrent_calls >= 5 {
ic0::debug_print(
format!(
"[ic-cdk-timers] canister_global_timer: \
too many concurrent calls for single timer ({}), \
rescheduling for next possible execution time",
concurrent_calls
)
.as_bytes(),
);
// Reschedule based on `now`, not `timer_scheduled_time`,
// intentionally skipping intermediate executions.
reschedule_timer(timers, task_id, now, *interval);
return true; // skip
}
} if *concurrent_calls >= 5 => {
ic0::debug_print(
format!(
"[ic-cdk-timers] canister_global_timer: \
too many concurrent calls for single timer ({}), \
rescheduling for next possible execution time",
concurrent_calls
)
.as_bytes(),
);
// Reschedule based on `now`, not `timer_scheduled_time`,
// intentionally skipping intermediate executions.
reschedule_timer(timers, task_id, now, *interval);
true // skip
}
// 3. Check serial timer is available
Task::RepeatedSerialBusy { interval } => {
reschedule_timer(timers, task_id, timer_scheduled_time, *interval);
return true; // skip
true // skip
}
_ => (),
_ => false, // do not skip
}
false // do not skip
});
if skip {
return ControlFlow::Continue(());
Expand Down
6 changes: 6 additions & 0 deletions ic-cdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.20.1] - 2026-04-20

### Added

- Added `msg_caller_info_data` and `msg_caller_info_signer` to `ic_cdk::api`, exposing the caller's identity attribute data and the signing canister ID respectively.

## [0.20.0] - 2026-03-05

### Changed
Expand Down
2 changes: 1 addition & 1 deletion ic-cdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic-cdk"
version = "0.20.0" # sync with ic-cdk-macros
version = "0.20.1" # sync with ic-cdk-macros
authors.workspace = true
edition.workspace = true
license.workspace = true
Expand Down
48 changes: 48 additions & 0 deletions ic-cdk/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,54 @@ pub fn msg_caller() -> Principal {
Principal::try_from(&buf).unwrap()
}

/// Gets auxiliary data about the caller as provided by the canister with which the caller's identity is associated.
///
/// This only returns non-empty data if the caller is a self-authenticating principal authenticated
/// by canister signatures (e.g. Internet Identity). Returns empty bytes when the caller is another canister.
///
/// The data is guaranteed to be signed by the canister returned from [`msg_caller_info_signer`],
/// so the signer should be checked before trusting the payload.
///
/// ```rust,no_run
/// use ic_cdk::api::{msg_caller_info_data, msg_caller_info_signer};
///
/// if msg_caller_info_signer().is_some() {
/// let data = msg_caller_info_data();
/// // Decode per the signer's documented format (e.g. identity attributes).
/// }
/// ```
pub fn msg_caller_info_data() -> Vec<u8> {
let len = ic0::msg_caller_info_data_size();
let mut buf = vec![0u8; len];
ic0::msg_caller_info_data_copy(&mut buf, 0);
buf
}

/// Gets the canister ID of the canister that provided the caller's canister signature.
///
/// Returns `None` if the caller is not a self-authenticating principal authenticated by canister
/// signatures (e.g. when the caller is another canister or no sender info was provided).
///
/// ```rust,no_run
/// use ic_cdk::api::msg_caller_info_signer;
/// use candid::Principal;
///
/// let trusted_issuer = Principal::from_text("rdmx6-jaaaa-aaaaa-aaadq-cai").unwrap();
/// if msg_caller_info_signer() == Some(trusted_issuer) {
/// // Caller's identity was attested by the trusted issuer (e.g. Internet Identity).
/// }
/// ```
pub fn msg_caller_info_signer() -> Option<Principal> {
let len = ic0::msg_caller_info_signer_size();
if len == 0 {
return None;
}
let mut buf = vec![0u8; len];
ic0::msg_caller_info_signer_copy(&mut buf, 0);
// Trust that the system always returns a valid principal when non-empty.
Some(Principal::try_from(&buf).expect("msg_caller_info_signer must be a valid principal"))
}

/// Returns the reject code, if the current function is invoked as a reject callback.
pub fn msg_reject_code() -> u32 {
ic0::msg_reject_code()
Expand Down
6 changes: 6 additions & 0 deletions ic0/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [1.1.0] - 2026-04-20

### Added

- Added `msg_caller_info_data_size`, `msg_caller_info_data_copy`, `msg_caller_info_signer_size`, and `msg_caller_info_signer_copy` API bindings.

## [1.0.1] - 2025-09-11

### Added
Expand Down
2 changes: 1 addition & 1 deletion ic0/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic0"
version = "1.0.1"
version = "1.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions ic0/ic0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
ic0.msg_arg_data_copy : (dst : I, offset : I, size : I) -> (); // I U RQ NRQ CQ Ry CRy F
ic0.msg_caller_size : () -> I; // *
ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // *
ic0.msg_caller_info_data_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_signer_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt
ic0.msg_reject_msg_size : () -> I ; // Rt CRt
ic0.msg_reject_msg_copy : (dst : I, offset : I, size : I) -> (); // Rt CRt
Expand Down
8 changes: 8 additions & 0 deletions ic0/manual_safety_comments.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ ic0.msg_caller_size : () -> I;
Always safe to call
ic0.msg_caller_copy : (dst : I, offset : I, size : I) -> (); // *
`dst` must be a pointer to a writable sequence of bytes with size `size`. The `offset` parameter does not affect safety.
ic0.msg_caller_info_data_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
Always safe to call
ic0.msg_caller_info_data_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
`dst` must be a pointer to a writable sequence of bytes with size `size`. The `offset` parameter does not affect safety.
ic0.msg_caller_info_signer_size : () -> I; // U RQ NRQ CQ Ry Rt CRy CRt C CC F
Always safe to call
ic0.msg_caller_info_signer_copy : (dst : I, offset : I, size : I) -> (); // U RQ NRQ CQ Ry Rt CRy CRt C CC F
`dst` must be a pointer to a writable sequence of bytes with size `size`. The `offset` parameter does not affect safety.
ic0.msg_reject_code : () -> i32; // Ry Rt CRy CRt
Always safe to call
ic0.msg_reject_msg_size : () -> I ; // Rt CRt
Expand Down
Loading
Loading