Skip to main content

canic_core/domain/
icp_refill.rs

1//! Module: domain::icp_refill
2//!
3//! Responsibility: define pure ICP refill operation value enums shared across
4//! storage projections, workflow decisions, and endpoint DTOs.
5//! Does not own: endpoint request/response structs, stable records, or ledger
6//! execution.
7//! Boundary: DTOs re-export these values to preserve the public API path while
8//! internal code imports them from the domain owner.
9
10use candid::CandidType;
11use serde::{Deserialize, Serialize};
12
13/// Durable owner that initiated one Root ICP-refill operation.
14#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
15pub enum IcpRefillTrigger {
16    Automatic { sequence: u64 },
17    Manual,
18}
19
20///
21/// IcpRefillStatus
22///
23
24#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
25#[remain::sorted]
26pub enum IcpRefillStatus {
27    Completed,
28    Failed,
29    InvalidTransaction,
30    NotifyProcessing,
31    Refunded,
32    Requested,
33    TransactionTooOld,
34    Transferred,
35}
36
37///
38/// IcpRefillErrorCode
39///
40#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
41#[remain::sorted]
42pub enum IcpRefillErrorCode {
43    BadFee,
44    CyclesSentOverflow,
45    Duplicate,
46    InvalidLedgerBlockIndex,
47    InvalidTransaction,
48    LedgerTransferFailed,
49    NotifyFailed,
50    NotifyMaxAttempts,
51    Processing,
52    Refunded,
53    TransactionTooOld,
54    TransferWindowStale,
55}
56
57/// Return whether an ICP refill outcome must retain its operation identity for retry.
58#[must_use]
59pub const fn icp_refill_outcome_is_resumable(
60    status: IcpRefillStatus,
61    error_code: Option<IcpRefillErrorCode>,
62    ledger_block_recorded: bool,
63) -> bool {
64    matches!(
65        status,
66        IcpRefillStatus::Requested
67            | IcpRefillStatus::Transferred
68            | IcpRefillStatus::NotifyProcessing
69    ) || matches!(
70        (status, error_code, ledger_block_recorded),
71        (
72            IcpRefillStatus::Failed,
73            Some(IcpRefillErrorCode::NotifyFailed),
74            true
75        ) | (
76            IcpRefillStatus::Failed,
77            Some(IcpRefillErrorCode::BadFee),
78            false
79        )
80    )
81}
82
83// -----------------------------------------------------------------------------
84// Tests
85// -----------------------------------------------------------------------------
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn resumable_outcome_requires_active_or_retryable_state() {
93        assert!(icp_refill_outcome_is_resumable(
94            IcpRefillStatus::Requested,
95            None,
96            false
97        ));
98        assert!(icp_refill_outcome_is_resumable(
99            IcpRefillStatus::Transferred,
100            None,
101            true
102        ));
103        assert!(icp_refill_outcome_is_resumable(
104            IcpRefillStatus::NotifyProcessing,
105            Some(IcpRefillErrorCode::Processing),
106            true
107        ));
108        assert!(icp_refill_outcome_is_resumable(
109            IcpRefillStatus::Failed,
110            Some(IcpRefillErrorCode::BadFee),
111            false
112        ));
113        assert!(icp_refill_outcome_is_resumable(
114            IcpRefillStatus::Failed,
115            Some(IcpRefillErrorCode::NotifyFailed),
116            true
117        ));
118
119        assert!(!icp_refill_outcome_is_resumable(
120            IcpRefillStatus::Completed,
121            None,
122            true
123        ));
124        assert!(!icp_refill_outcome_is_resumable(
125            IcpRefillStatus::Failed,
126            Some(IcpRefillErrorCode::NotifyMaxAttempts),
127            true
128        ));
129        assert!(!icp_refill_outcome_is_resumable(
130            IcpRefillStatus::Failed,
131            Some(IcpRefillErrorCode::NotifyFailed),
132            false
133        ));
134        assert!(!icp_refill_outcome_is_resumable(
135            IcpRefillStatus::Failed,
136            Some(IcpRefillErrorCode::BadFee),
137            true
138        ));
139    }
140}