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
49 changes: 49 additions & 0 deletions docs/references/ic-interface-spec/abstract-behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ S = {
balances: CanisterId ↦ Nat;
reserved_balances: CanisterId ↦ Nat;
reserved_balance_limits: CanisterId ↦ Nat;
minimum_incoming_canister_call_cycles: CanisterId ↦ Nat;
wasm_memory_limit: CanisterId ↦ Nat;
wasm_memory_threshold: CanisterId ↦ Nat;
environment_variables: CanisterId ↦ (Text ↦ Text)
Expand Down Expand Up @@ -618,6 +619,7 @@ The initial state of the IC is
balances = ();
reserved_balances = ();
reserved_balance_limits = ();
minimum_incoming_canister_call_cycles = ();
wasm_memory_limit = ();
wasm_memory_threshold = ();
environment_variables = ();
Expand Down Expand Up @@ -1014,6 +1016,34 @@ messages = Older_messages · Younger_messages ·

```

#### Calls with insufficient cycles are rejected

An inter-canister call from a different canister with fewer cycles attached than the callee's minimum is automatically rejected.

Conditions

```html

S.messages = Older_messages · CallMessage CM · Younger_messages
(CM.queue = Unordered) or (∀ CallMessage M' | FuncMessage M' ∈ Older_messages. M'.queue ≠ CM.queue)
CM.origin = FromCanister _
CM.caller ≠ CM.callee
CM.transferred_cycles < S.minimum_incoming_canister_call_cycles[CM.callee]
```

State after:

```html

messages = Older_messages · Younger_messages ·
ResponseMessage {
origin = CM.origin;
response = Reject (CANISTER_ERROR, <implementation-specific>);
refunded_cycles = CM.transferred_cycles;
}

```

#### Call context creation {#call-context-creation}

Before invoking a heartbeat, a global timer, or a message to a public entry point, a call context is created for bookkeeping purposes. For these invocations the canister must be running (so not stopped or stopping). Additionally, these invocations only happen for "real" canisters, not the IC management canister.
Expand Down Expand Up @@ -1666,6 +1696,10 @@ if A.settings.reserved_cycles_limit is not null:
New_reserved_balance_limit = A.settings.reserved_cycles_limit
else:
New_reserved_balance_limit = 5_000_000_000_000
if A.settings.minimum_incoming_canister_call_cycles is not null:
New_minimum_incoming_canister_call_cycles = A.settings.minimum_incoming_canister_call_cycles
else:
New_minimum_incoming_canister_call_cycles = 0
if A.settings.wasm_memory_limit is not null:
New_wasm_memory_limit = A.settings.wasm_memory_limit
else:
Expand Down Expand Up @@ -1730,6 +1764,7 @@ S' = S with
balances[Canister_id] = New_balance
reserved_balances[Canister_id] = New_reserved_balance
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
minimum_incoming_canister_call_cycles[Canister_id] = New_minimum_incoming_canister_call_cycles
wasm_memory_limit[Canister_id] = New_wasm_memory_limit
wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold
environment_variables[Canister_id] = New_environment_variables
Expand Down Expand Up @@ -1812,6 +1847,10 @@ if A.settings.reserved_cycles_limit is not null:
New_reserved_balance_limit = A.settings.reserved_cycles_limit
else:
New_reserved_balance_limit = S.reserved_balance_limits[A.canister_id]
if A.settings.minimum_incoming_canister_call_cycles is not null:
New_minimum_incoming_canister_call_cycles = A.settings.minimum_incoming_canister_call_cycles
else:
New_minimum_incoming_canister_call_cycles = S.minimum_incoming_canister_call_cycles[A.canister_id]
if A.settings.wasm_memory_limit is not null:
New_wasm_memory_limit = A.settings.wasm_memory_limit
else:
Expand Down Expand Up @@ -1868,6 +1907,7 @@ S' = S with
balances[A.canister_id] = New_balance
reserved_balances[A.canister_id] = New_reserved_balance
reserved_balance_limits[A.canister_id] = New_reserved_balance_limit
minimum_incoming_canister_call_cycles[A.canister_id] = New_minimum_incoming_canister_call_cycles
wasm_memory_limit[A.canister_id] = New_wasm_memory_limit
wasm_memory_threshold[A.canister_id] = New_wasm_memory_threshold
environment_variables[A.canister_id] = New_environment_variables
Expand Down Expand Up @@ -1902,6 +1942,7 @@ canister_status(S, Canister_id) =
memory_allocation = S.memory_allocation[Canister_id];
freezing_threshold = S.freezing_threshold[Canister_id];
reserved_cycles_limit = S.reserved_balance_limit[Canister_id];
minimum_incoming_canister_call_cycles = S.minimum_incoming_canister_call_cycles[Canister_id];
wasm_memory_limit = S.wasm_memory_limit[Canister_id];
wasm_memory_threshold = S.wasm_memory_threshold[Canister_id];
environment_variables = S.environment_variables[Canister_id];
Expand Down Expand Up @@ -2893,6 +2934,7 @@ S with
balances[A.canister_id] = (deleted)
reserved_balances[A.canister_id] = (deleted)
reserved_balance_limits[A.canister_id] = (deleted)
minimum_incoming_canister_call_cycles[A.canister_id] = (deleted)
wasm_memory_limit[A.canister_id] = (deleted)
wasm_memory_threshold[A.canister_id] = (deleted)
on_low_wasm_memory_hook_status[A.canister_id] = (deleted)
Expand Down Expand Up @@ -3087,6 +3129,10 @@ if A.settings.reserved_cycles_limit is not null:
New_reserved_balance_limit = A.settings.reserved_cycles_limit
else:
New_reserved_balance_limit = 5_000_000_000_000
if A.settings.minimum_incoming_canister_call_cycles is not null:
New_minimum_incoming_canister_call_cycles = A.settings.minimum_incoming_canister_call_cycles
else:
New_minimum_incoming_canister_call_cycles = 0
if A.settings.wasm_memory_limit is not null:
New_wasm_memory_limit = A.settings.wasm_memory_limit
else:
Expand Down Expand Up @@ -3154,6 +3200,7 @@ S' = S with
balances[Canister_id] = New_balance
reserved_balances[Canister_id] = New_reserved_balance
reserved_balance_limits[Canister_id] = New_reserved_balance_limit
minimum_incoming_canister_call_cycles[Canister_id] = New_minimum_incoming_canister_call_cycles
wasm_memory_limit[Canister_id] = New_wasm_memory_limit
wasm_memory_threshold[Canister_id] = New_wasm_memory_threshold
environment_variables[Canister_id] = New_environment_variables
Expand Down Expand Up @@ -4126,6 +4173,8 @@ S with
reserved_balances[Canister_id] = (deleted)
reserved_balance_limits[New_canister_id] = S.reserved_balance_limits[Canister_id]
reserved_balance_limits[Canister_id] = (deleted)
minimum_incoming_canister_call_cycles[New_canister_id] = S.minimum_incoming_canister_call_cycles[Canister_id]
minimum_incoming_canister_call_cycles[Canister_id] = (deleted)
wasm_memory_limit[New_canister_id] = S.wasm_memory_limit[Canister_id]
wasm_memory_limit[Canister_id] = (deleted)
wasm_memory_threshold[New_canister_id] = S.wasm_memory_threshold[Canister_id]
Expand Down
3 changes: 3 additions & 0 deletions docs/references/ic-interface-spec/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ sidebar:
omitting the field. Requests of any kind carrying a delegation with any other value of
the `permissions` field are not accepted.
* `wasm_memory_threshold` in canister settings is now bounded by 2<sup>48</sup>, analogously to `wasm_memory_limit`.
* New canister setting `minimum_incoming_canister_call_cycles`: if set, inter-canister calls
from a different canister that attach fewer cycles than this threshold are rejected with `CANISTER_ERROR`
and all attached cycles are refunded. Ingress messages and self-calls are not affected.

### 0.63.0 (2026-06-29) {$0_63_0}
* Support for the HTTP method `PATCH` in canister `http_request` in non-replicated mode.
Expand Down
8 changes: 8 additions & 0 deletions docs/references/ic-interface-spec/management-canister.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ The optional `settings` parameter can be used to set the following settings:

Default value: 5_000_000_000_000 (5 trillion cycles).

- `minimum_incoming_canister_call_cycles` (`nat`)

Must be a number between 0 and 2<sup>128</sup>-1, inclusively, and indicates the minimum number of cycles that must be attached to an incoming inter-canister call. If a different canister makes a call with fewer attached cycles than this threshold, the call is rejected with `CANISTER_ERROR` and all attached cycles are refunded to the caller. Ingress messages and self-calls (where the caller and the callee are the same canister) are not subject to this limit.

Default value: 0 (no minimum enforced).

- `wasm_memory_limit` (`nat`)

Must be a number between 0 and 2<sup>48</sup>, inclusively, and indicates the upper limit on the WASM heap memory consumption of the canister in bytes.
Expand Down Expand Up @@ -252,6 +258,8 @@ Indicates various information about the canister. It contains:

- The reserved cycles limit of the canister, i.e., the maximum number of cycles that can be in the canister's reserved balance after increasing the canister's memory allocation and/or actual memory usage.

- The minimum number of cycles required for incoming inter-canister calls from a different canister (self-calls and ingress messages are not subject to this limit).

- The visibility of the canister's logs.

- The visibility of the canister's snapshots.
Expand Down
2 changes: 2 additions & 0 deletions public/references/ic.did
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type canister_settings = record {
memory_allocation : opt nat;
freezing_threshold : opt nat;
reserved_cycles_limit : opt nat;
minimum_incoming_canister_call_cycles : opt nat;
log_visibility : opt log_visibility;
snapshot_visibility : opt snapshot_visibility;
wasm_memory_limit : opt nat;
Expand All @@ -38,6 +39,7 @@ type definite_canister_settings = record {
memory_allocation : nat;
freezing_threshold : nat;
reserved_cycles_limit : nat;
minimum_incoming_canister_call_cycles : nat;
log_visibility : log_visibility;
snapshot_visibility : snapshot_visibility;
wasm_memory_limit : nat;
Expand Down
Loading