ic_utils/interfaces/management_canister/
attributes.rs

1//! Checked wrappers around certain numeric values used in management calls.
2
3use thiserror::Error;
4
5/// An error encountered when attempting to construct a [`ComputeAllocation`].
6#[derive(Error, Debug)]
7pub enum ComputeAllocationError {
8    /// The provided value was not a percentage in the range [0, 100].
9    #[error("Must be a percent between 0 and 100.")]
10    MustBeAPercentage(),
11}
12
13/// A compute allocation for a canister, represented as a percentage between 0 and 100 inclusive.
14///
15/// This represents the percentage of a canister's maximum compute capacity that the IC should commit to guaranteeing for the canister.
16/// If 0, computation is provided on a best-effort basis.
17#[derive(Copy, Clone, Debug)]
18pub struct ComputeAllocation(u8);
19
20impl std::convert::From<ComputeAllocation> for u8 {
21    fn from(compute_allocation: ComputeAllocation) -> Self {
22        compute_allocation.0
23    }
24}
25
26macro_rules! try_from_compute_alloc_decl {
27    ( $t: ty ) => {
28        impl std::convert::TryFrom<$t> for ComputeAllocation {
29            type Error = ComputeAllocationError;
30
31            fn try_from(value: $t) -> Result<Self, Self::Error> {
32                if (value as i64) < 0 || (value as i64) > 100 {
33                    Err(ComputeAllocationError::MustBeAPercentage())
34                } else {
35                    Ok(Self(value as u8))
36                }
37            }
38        }
39    };
40}
41
42try_from_compute_alloc_decl!(u8);
43try_from_compute_alloc_decl!(u16);
44try_from_compute_alloc_decl!(u32);
45try_from_compute_alloc_decl!(u64);
46try_from_compute_alloc_decl!(i8);
47try_from_compute_alloc_decl!(i16);
48try_from_compute_alloc_decl!(i32);
49try_from_compute_alloc_decl!(i64);
50
51/// An error encountered when attempting to construct a [`MemoryAllocation`].
52#[derive(Error, Debug)]
53pub enum MemoryAllocationError {
54    /// The provided value was not in the range [0, 2^48] (i.e. 256 TiB).
55    #[error("Memory allocation must be between 0 and 2^48 (i.e 256TiB), inclusively. Got {0}.")]
56    InvalidMemorySize(u64),
57}
58
59/// A memory allocation for a canister. Can be anywhere from 0 to 2^48 (i.e. 256 TiB) inclusive.
60///
61/// This represents the size, in bytes, that the IC guarantees to the canister and limits the canister to.
62/// If a canister attempts to exceed this value (and the value is nonzero), the attempt will fail. If 0,
63/// memory allocation is provided on a best-effort basis.
64#[derive(Copy, Clone, Debug)]
65pub struct MemoryAllocation(u64);
66
67impl std::convert::From<MemoryAllocation> for u64 {
68    fn from(memory_allocation: MemoryAllocation) -> Self {
69        memory_allocation.0
70    }
71}
72
73macro_rules! try_from_memory_alloc_decl {
74    ( $t: ty ) => {
75        impl std::convert::TryFrom<$t> for MemoryAllocation {
76            type Error = MemoryAllocationError;
77
78            fn try_from(value: $t) -> Result<Self, Self::Error> {
79                if (value as i64) < 0 || (value as i64) > (1i64 << 48) {
80                    Err(MemoryAllocationError::InvalidMemorySize(value as u64))
81                } else {
82                    Ok(Self(value as u64))
83                }
84            }
85        }
86    };
87}
88
89try_from_memory_alloc_decl!(u8);
90try_from_memory_alloc_decl!(u16);
91try_from_memory_alloc_decl!(u32);
92try_from_memory_alloc_decl!(u64);
93try_from_memory_alloc_decl!(i8);
94try_from_memory_alloc_decl!(i16);
95try_from_memory_alloc_decl!(i32);
96try_from_memory_alloc_decl!(i64);
97
98/// An error encountered when attempting to construct a [`FreezingThreshold`].
99#[derive(Error, Debug)]
100pub enum FreezingThresholdError {
101    /// The provided value was not in the range [0, 2^64-1].
102    #[error("Freezing threshold must be between 0 and 2^64-1, inclusively. Got {0}.")]
103    InvalidFreezingThreshold(u64),
104}
105
106/// A freezing threshold for a canister. Can be anywhere from 0 to 2^64-1 inclusive.
107///
108/// This represents the time, in seconds, of 'runway' the IC tries to guarantee the canister.
109/// If the canister's persistent costs, like storage, will likely lead it to run out of cycles within this amount of time,
110/// then the IC will 'freeze' the canister. Attempts to call its methods will be rejected unconditionally.
111/// The canister also cannot make any calls that push its cycle count into freezing threshold range.
112#[derive(Copy, Clone, Debug)]
113pub struct FreezingThreshold(u64);
114
115impl std::convert::From<FreezingThreshold> for u64 {
116    fn from(freezing_threshold: FreezingThreshold) -> Self {
117        freezing_threshold.0
118    }
119}
120
121macro_rules! try_from_freezing_threshold_decl {
122    ( $t: ty ) => {
123        impl std::convert::TryFrom<$t> for FreezingThreshold {
124            type Error = FreezingThresholdError;
125
126            fn try_from(value: $t) -> Result<Self, Self::Error> {
127                if (value as i128) < 0 || (value as i128) > (2_i128.pow(64) - 1i128) {
128                    Err(FreezingThresholdError::InvalidFreezingThreshold(
129                        value as u64,
130                    ))
131                } else {
132                    Ok(Self(value as u64))
133                }
134            }
135        }
136    };
137}
138
139try_from_freezing_threshold_decl!(u8);
140try_from_freezing_threshold_decl!(u16);
141try_from_freezing_threshold_decl!(u32);
142try_from_freezing_threshold_decl!(u64);
143try_from_freezing_threshold_decl!(i8);
144try_from_freezing_threshold_decl!(i16);
145try_from_freezing_threshold_decl!(i32);
146try_from_freezing_threshold_decl!(i64);
147try_from_freezing_threshold_decl!(i128);
148try_from_freezing_threshold_decl!(u128);
149
150/// An error encountered when attempting to construct a [`ReservedCyclesLimit`].
151#[derive(Error, Debug)]
152pub enum ReservedCyclesLimitError {
153    /// The provided value was not in the range [0, 2^128-1].
154    #[error("ReservedCyclesLimit must be between 0 and 2^128-1, inclusively. Got {0}.")]
155    InvalidReservedCyclesLimit(i128),
156}
157
158/// A reserved cycles limit for a canister. Can be anywhere from 0 to 2^128-1 inclusive.
159///
160/// This represents the upper limit of `reserved_cycles` for the canister.
161///
162/// Reserved cycles are cycles that the system sets aside for future use by the canister.
163/// If a subnet's storage exceeds 450 GiB, then every time a canister allocates new storage bytes,
164/// the system sets aside some amount of cycles from the main balance of the canister.
165/// These reserved cycles will be used to cover future payments for the newly allocated bytes.
166/// The reserved cycles are not transferable and the amount of reserved cycles depends on how full the subnet is.
167///
168/// A reserved cycles limit of 0 disables the reservation mechanism for the canister.
169/// If so disabled, the canister will trap when it tries to allocate storage, if the subnet's usage exceeds 450 GiB.
170#[derive(Copy, Clone, Debug)]
171pub struct ReservedCyclesLimit(u128);
172
173impl std::convert::From<ReservedCyclesLimit> for u128 {
174    fn from(reserved_cycles_limit: ReservedCyclesLimit) -> Self {
175        reserved_cycles_limit.0
176    }
177}
178
179#[allow(unused_comparisons)]
180macro_rules! try_from_reserved_cycles_limit_decl {
181    ( $t: ty ) => {
182        impl std::convert::TryFrom<$t> for ReservedCyclesLimit {
183            type Error = ReservedCyclesLimitError;
184
185            fn try_from(value: $t) -> Result<Self, Self::Error> {
186                #[allow(unused_comparisons)]
187                if value < 0 {
188                    Err(ReservedCyclesLimitError::InvalidReservedCyclesLimit(
189                        value as i128,
190                    ))
191                } else {
192                    Ok(Self(value as u128))
193                }
194            }
195        }
196    };
197}
198
199try_from_reserved_cycles_limit_decl!(u8);
200try_from_reserved_cycles_limit_decl!(u16);
201try_from_reserved_cycles_limit_decl!(u32);
202try_from_reserved_cycles_limit_decl!(u64);
203try_from_reserved_cycles_limit_decl!(i8);
204try_from_reserved_cycles_limit_decl!(i16);
205try_from_reserved_cycles_limit_decl!(i32);
206try_from_reserved_cycles_limit_decl!(i64);
207try_from_reserved_cycles_limit_decl!(i128);
208try_from_reserved_cycles_limit_decl!(u128);
209
210/// An error encountered when attempting to construct a [`WasmMemoryLimit`].
211#[derive(Error, Debug)]
212pub enum WasmMemoryLimitError {
213    /// The provided value was not in the range [0, 2^48] (i.e. 256 TiB).
214    #[error("Wasm memory limit must be between 0 and 2^48 (i.e 256TiB), inclusively. Got {0}.")]
215    InvalidMemoryLimit(i64),
216}
217
218/// A soft limit on the Wasm memory usage of the canister. Update calls,
219/// timers, heartbeats, install, and post-upgrade fail if the Wasm memory
220/// usage exceeds this limit. The main purpose of this field is to protect
221/// against the case when the canister reaches the hard 4GiB limit.
222/// Must be a number between 0 and 2^48^ (i.e 256TB), inclusively.
223#[derive(Copy, Clone, Debug)]
224pub struct WasmMemoryLimit(u64);
225
226impl std::convert::From<WasmMemoryLimit> for u64 {
227    fn from(wasm_memory_limit: WasmMemoryLimit) -> Self {
228        wasm_memory_limit.0
229    }
230}
231
232macro_rules! try_from_wasm_memory_limit_decl {
233    ( $t: ty ) => {
234        impl std::convert::TryFrom<$t> for WasmMemoryLimit {
235            type Error = WasmMemoryLimitError;
236
237            fn try_from(value: $t) -> Result<Self, Self::Error> {
238                if (value as i64) < 0 || (value as i64) > (1i64 << 48) {
239                    Err(Self::Error::InvalidMemoryLimit(value as i64))
240                } else {
241                    Ok(Self(value as u64))
242                }
243            }
244        }
245    };
246}
247
248try_from_wasm_memory_limit_decl!(u8);
249try_from_wasm_memory_limit_decl!(u16);
250try_from_wasm_memory_limit_decl!(u32);
251try_from_wasm_memory_limit_decl!(u64);
252try_from_wasm_memory_limit_decl!(i8);
253try_from_wasm_memory_limit_decl!(i16);
254try_from_wasm_memory_limit_decl!(i32);
255try_from_wasm_memory_limit_decl!(i64);
256
257#[test]
258#[allow(clippy::useless_conversion)]
259fn can_convert_compute_allocation() {
260    use std::convert::{TryFrom, TryInto};
261
262    // This is more of a compiler test than an actual test.
263    let _ca_u8: ComputeAllocation = 1u8.try_into().unwrap();
264    let _ca_u16: ComputeAllocation = 1u16.try_into().unwrap();
265    let _ca_u32: ComputeAllocation = 1u32.try_into().unwrap();
266    let _ca_u64: ComputeAllocation = 1u64.try_into().unwrap();
267    let _ca_i8: ComputeAllocation = 1i8.try_into().unwrap();
268    let _ca_i16: ComputeAllocation = 1i16.try_into().unwrap();
269    let _ca_i32: ComputeAllocation = 1i32.try_into().unwrap();
270    let _ca_i64: ComputeAllocation = 1i64.try_into().unwrap();
271
272    let ca = ComputeAllocation(100);
273    let _ca_ca: ComputeAllocation = ComputeAllocation::try_from(ca).unwrap();
274}
275
276#[test]
277#[allow(clippy::useless_conversion)]
278fn can_convert_memory_allocation() {
279    use std::convert::{TryFrom, TryInto};
280
281    // This is more of a compiler test than an actual test.
282    let _ma_u8: MemoryAllocation = 1u8.try_into().unwrap();
283    let _ma_u16: MemoryAllocation = 1u16.try_into().unwrap();
284    let _ma_u32: MemoryAllocation = 1u32.try_into().unwrap();
285    let _ma_u64: MemoryAllocation = 1u64.try_into().unwrap();
286    let _ma_i8: MemoryAllocation = 1i8.try_into().unwrap();
287    let _ma_i16: MemoryAllocation = 1i16.try_into().unwrap();
288    let _ma_i32: MemoryAllocation = 1i32.try_into().unwrap();
289    let _ma_i64: MemoryAllocation = 1i64.try_into().unwrap();
290
291    let ma = MemoryAllocation(100);
292    let _ma_ma: MemoryAllocation = MemoryAllocation::try_from(ma).unwrap();
293}
294
295#[test]
296#[allow(clippy::useless_conversion)]
297fn can_convert_freezing_threshold() {
298    use std::convert::{TryFrom, TryInto};
299
300    // This is more of a compiler test than an actual test.
301    let _ft_u8: FreezingThreshold = 1u8.try_into().unwrap();
302    let _ft_u16: FreezingThreshold = 1u16.try_into().unwrap();
303    let _ft_u32: FreezingThreshold = 1u32.try_into().unwrap();
304    let _ft_u64: FreezingThreshold = 1u64.try_into().unwrap();
305    let _ft_i8: FreezingThreshold = 1i8.try_into().unwrap();
306    let _ft_i16: FreezingThreshold = 1i16.try_into().unwrap();
307    let _ft_i32: FreezingThreshold = 1i32.try_into().unwrap();
308    let _ft_i64: FreezingThreshold = 1i64.try_into().unwrap();
309    let _ft_u128: FreezingThreshold = 1i128.try_into().unwrap();
310    let _ft_i128: FreezingThreshold = 1u128.try_into().unwrap();
311
312    let ft = FreezingThreshold(100);
313    let _ft_ft: FreezingThreshold = FreezingThreshold::try_from(ft).unwrap();
314}
315
316#[test]
317#[allow(clippy::useless_conversion)]
318fn can_convert_reserved_cycles_limit() {
319    use std::convert::{TryFrom, TryInto};
320
321    // This is more of a compiler test than an actual test.
322    let _ft_u8: ReservedCyclesLimit = 1u8.try_into().unwrap();
323    let _ft_u16: ReservedCyclesLimit = 1u16.try_into().unwrap();
324    let _ft_u32: ReservedCyclesLimit = 1u32.try_into().unwrap();
325    let _ft_u64: ReservedCyclesLimit = 1u64.try_into().unwrap();
326    let _ft_i8: ReservedCyclesLimit = 1i8.try_into().unwrap();
327    let _ft_i16: ReservedCyclesLimit = 1i16.try_into().unwrap();
328    let _ft_i32: ReservedCyclesLimit = 1i32.try_into().unwrap();
329    let _ft_i64: ReservedCyclesLimit = 1i64.try_into().unwrap();
330    let _ft_u128: ReservedCyclesLimit = 1i128.try_into().unwrap();
331    let _ft_i128: ReservedCyclesLimit = 1u128.try_into().unwrap();
332
333    assert!(matches!(
334        ReservedCyclesLimit::try_from(-4).unwrap_err(),
335        ReservedCyclesLimitError::InvalidReservedCyclesLimit(-4)
336    ));
337
338    assert_eq!(
339        ReservedCyclesLimit::try_from(2u128.pow(127) + 6).unwrap().0,
340        170_141_183_460_469_231_731_687_303_715_884_105_734_u128
341    );
342
343    let ft = ReservedCyclesLimit(100);
344    let _ft_ft: ReservedCyclesLimit = ReservedCyclesLimit::try_from(ft).unwrap();
345}
346
347#[test]
348#[allow(clippy::useless_conversion)]
349fn can_convert_wasm_memory_limit() {
350    use std::convert::{TryFrom, TryInto};
351
352    // This is more of a compiler test than an actual test.
353    let _ma_u8: WasmMemoryLimit = 1u8.try_into().unwrap();
354    let _ma_u16: WasmMemoryLimit = 1u16.try_into().unwrap();
355    let _ma_u32: WasmMemoryLimit = 1u32.try_into().unwrap();
356    let _ma_u64: WasmMemoryLimit = 1u64.try_into().unwrap();
357    let _ma_i8: WasmMemoryLimit = 1i8.try_into().unwrap();
358    let _ma_i16: WasmMemoryLimit = 1i16.try_into().unwrap();
359    let _ma_i32: WasmMemoryLimit = 1i32.try_into().unwrap();
360    let _ma_i64: WasmMemoryLimit = 1i64.try_into().unwrap();
361
362    let ma = WasmMemoryLimit(100);
363    let _ma_ma: WasmMemoryLimit = WasmMemoryLimit::try_from(ma).unwrap();
364
365    assert!(matches!(
366        WasmMemoryLimit::try_from(-4).unwrap_err(),
367        WasmMemoryLimitError::InvalidMemoryLimit(-4)
368    ));
369
370    assert!(matches!(
371        WasmMemoryLimit::try_from(562_949_953_421_312_u64).unwrap_err(),
372        WasmMemoryLimitError::InvalidMemoryLimit(562_949_953_421_312)
373    ));
374}