Skip to main content

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/// An error encountered when attempting to construct a [`LogMemoryLimit`].
258#[derive(Error, Debug)]
259pub enum LogMemoryLimitError {
260    /// The provided value was not in the range [0, 2^48] (i.e. 256 TiB).
261    #[error("Log memory limit must be between 0 and 2^48 (i.e 256TiB), inclusively. Got {0}.")]
262    InvalidMemoryLimit(i64),
263}
264
265/// A limit on the memory allocated to storing canister logs.
266/// Must be a number between 0 and 2^48 (i.e 256TiB), inclusively.
267#[derive(Copy, Clone, Debug)]
268pub struct LogMemoryLimit(u64);
269
270impl std::convert::From<LogMemoryLimit> for u64 {
271    fn from(log_memory_limit: LogMemoryLimit) -> Self {
272        log_memory_limit.0
273    }
274}
275
276macro_rules! try_from_log_memory_limit_decl {
277    ( $t: ty ) => {
278        impl std::convert::TryFrom<$t> for LogMemoryLimit {
279            type Error = LogMemoryLimitError;
280
281            fn try_from(value: $t) -> Result<Self, Self::Error> {
282                if (value as i64) < 0 || (value as i64) > (1i64 << 48) {
283                    Err(Self::Error::InvalidMemoryLimit(value as i64))
284                } else {
285                    Ok(Self(value as u64))
286                }
287            }
288        }
289    };
290}
291
292try_from_log_memory_limit_decl!(u8);
293try_from_log_memory_limit_decl!(u16);
294try_from_log_memory_limit_decl!(u32);
295try_from_log_memory_limit_decl!(u64);
296try_from_log_memory_limit_decl!(i8);
297try_from_log_memory_limit_decl!(i16);
298try_from_log_memory_limit_decl!(i32);
299try_from_log_memory_limit_decl!(i64);
300
301#[test]
302#[allow(clippy::useless_conversion)]
303fn can_convert_compute_allocation() {
304    use std::convert::{TryFrom, TryInto};
305
306    // This is more of a compiler test than an actual test.
307    let _ca_u8: ComputeAllocation = 1u8.try_into().unwrap();
308    let _ca_u16: ComputeAllocation = 1u16.try_into().unwrap();
309    let _ca_u32: ComputeAllocation = 1u32.try_into().unwrap();
310    let _ca_u64: ComputeAllocation = 1u64.try_into().unwrap();
311    let _ca_i8: ComputeAllocation = 1i8.try_into().unwrap();
312    let _ca_i16: ComputeAllocation = 1i16.try_into().unwrap();
313    let _ca_i32: ComputeAllocation = 1i32.try_into().unwrap();
314    let _ca_i64: ComputeAllocation = 1i64.try_into().unwrap();
315
316    let ca = ComputeAllocation(100);
317    let _ca_ca: ComputeAllocation = ComputeAllocation::try_from(ca).unwrap();
318}
319
320#[test]
321#[allow(clippy::useless_conversion)]
322fn can_convert_memory_allocation() {
323    use std::convert::{TryFrom, TryInto};
324
325    // This is more of a compiler test than an actual test.
326    let _ma_u8: MemoryAllocation = 1u8.try_into().unwrap();
327    let _ma_u16: MemoryAllocation = 1u16.try_into().unwrap();
328    let _ma_u32: MemoryAllocation = 1u32.try_into().unwrap();
329    let _ma_u64: MemoryAllocation = 1u64.try_into().unwrap();
330    let _ma_i8: MemoryAllocation = 1i8.try_into().unwrap();
331    let _ma_i16: MemoryAllocation = 1i16.try_into().unwrap();
332    let _ma_i32: MemoryAllocation = 1i32.try_into().unwrap();
333    let _ma_i64: MemoryAllocation = 1i64.try_into().unwrap();
334
335    let ma = MemoryAllocation(100);
336    let _ma_ma: MemoryAllocation = MemoryAllocation::try_from(ma).unwrap();
337}
338
339#[test]
340#[allow(clippy::useless_conversion)]
341fn can_convert_freezing_threshold() {
342    use std::convert::{TryFrom, TryInto};
343
344    // This is more of a compiler test than an actual test.
345    let _ft_u8: FreezingThreshold = 1u8.try_into().unwrap();
346    let _ft_u16: FreezingThreshold = 1u16.try_into().unwrap();
347    let _ft_u32: FreezingThreshold = 1u32.try_into().unwrap();
348    let _ft_u64: FreezingThreshold = 1u64.try_into().unwrap();
349    let _ft_i8: FreezingThreshold = 1i8.try_into().unwrap();
350    let _ft_i16: FreezingThreshold = 1i16.try_into().unwrap();
351    let _ft_i32: FreezingThreshold = 1i32.try_into().unwrap();
352    let _ft_i64: FreezingThreshold = 1i64.try_into().unwrap();
353    let _ft_u128: FreezingThreshold = 1i128.try_into().unwrap();
354    let _ft_i128: FreezingThreshold = 1u128.try_into().unwrap();
355
356    let ft = FreezingThreshold(100);
357    let _ft_ft: FreezingThreshold = FreezingThreshold::try_from(ft).unwrap();
358}
359
360#[test]
361#[allow(clippy::useless_conversion)]
362fn can_convert_reserved_cycles_limit() {
363    use std::convert::{TryFrom, TryInto};
364
365    // This is more of a compiler test than an actual test.
366    let _ft_u8: ReservedCyclesLimit = 1u8.try_into().unwrap();
367    let _ft_u16: ReservedCyclesLimit = 1u16.try_into().unwrap();
368    let _ft_u32: ReservedCyclesLimit = 1u32.try_into().unwrap();
369    let _ft_u64: ReservedCyclesLimit = 1u64.try_into().unwrap();
370    let _ft_i8: ReservedCyclesLimit = 1i8.try_into().unwrap();
371    let _ft_i16: ReservedCyclesLimit = 1i16.try_into().unwrap();
372    let _ft_i32: ReservedCyclesLimit = 1i32.try_into().unwrap();
373    let _ft_i64: ReservedCyclesLimit = 1i64.try_into().unwrap();
374    let _ft_u128: ReservedCyclesLimit = 1i128.try_into().unwrap();
375    let _ft_i128: ReservedCyclesLimit = 1u128.try_into().unwrap();
376
377    assert!(matches!(
378        ReservedCyclesLimit::try_from(-4).unwrap_err(),
379        ReservedCyclesLimitError::InvalidReservedCyclesLimit(-4)
380    ));
381
382    assert_eq!(
383        ReservedCyclesLimit::try_from(2u128.pow(127) + 6).unwrap().0,
384        170_141_183_460_469_231_731_687_303_715_884_105_734_u128
385    );
386
387    let ft = ReservedCyclesLimit(100);
388    let _ft_ft: ReservedCyclesLimit = ReservedCyclesLimit::try_from(ft).unwrap();
389}
390
391#[test]
392#[allow(clippy::useless_conversion)]
393fn can_convert_wasm_memory_limit() {
394    use std::convert::{TryFrom, TryInto};
395
396    // This is more of a compiler test than an actual test.
397    let _ma_u8: WasmMemoryLimit = 1u8.try_into().unwrap();
398    let _ma_u16: WasmMemoryLimit = 1u16.try_into().unwrap();
399    let _ma_u32: WasmMemoryLimit = 1u32.try_into().unwrap();
400    let _ma_u64: WasmMemoryLimit = 1u64.try_into().unwrap();
401    let _ma_i8: WasmMemoryLimit = 1i8.try_into().unwrap();
402    let _ma_i16: WasmMemoryLimit = 1i16.try_into().unwrap();
403    let _ma_i32: WasmMemoryLimit = 1i32.try_into().unwrap();
404    let _ma_i64: WasmMemoryLimit = 1i64.try_into().unwrap();
405
406    let ma = WasmMemoryLimit(100);
407    let _ma_ma: WasmMemoryLimit = WasmMemoryLimit::try_from(ma).unwrap();
408
409    assert!(matches!(
410        WasmMemoryLimit::try_from(-4).unwrap_err(),
411        WasmMemoryLimitError::InvalidMemoryLimit(-4)
412    ));
413
414    assert!(matches!(
415        WasmMemoryLimit::try_from(562_949_953_421_312_u64).unwrap_err(),
416        WasmMemoryLimitError::InvalidMemoryLimit(562_949_953_421_312)
417    ));
418}