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///
14/// IcpRefillMode
15///
16
17#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
18#[remain::sorted]
19pub enum IcpRefillMode {
20    Canister,
21    Fabricate,
22}
23
24///
25/// IcpRefillStatus
26///
27
28#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
29#[remain::sorted]
30pub enum IcpRefillStatus {
31    Completed,
32    Failed,
33    InvalidTransaction,
34    NotifyProcessing,
35    Refunded,
36    Requested,
37    TransactionTooOld,
38    Transferred,
39}
40
41///
42/// IcpRefillErrorCode
43///
44#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
45#[remain::sorted]
46pub enum IcpRefillErrorCode {
47    BadFee,
48    CyclesSentOverflow,
49    Duplicate,
50    FabricationUnavailable,
51    InvalidLedgerBlockIndex,
52    InvalidTransaction,
53    LedgerTransferFailed,
54    NotifyFailed,
55    NotifyMaxAttempts,
56    Processing,
57    RateGateDenied,
58    Refunded,
59    RequestDenied,
60    TransactionTooOld,
61    TransferWindowStale,
62}
63
64/// Return whether an ICP refill outcome must retain its operation identity for retry.
65#[must_use]
66pub const fn icp_refill_outcome_is_resumable(
67    status: IcpRefillStatus,
68    error_code: Option<IcpRefillErrorCode>,
69    ledger_block_recorded: bool,
70) -> bool {
71    matches!(
72        status,
73        IcpRefillStatus::Requested
74            | IcpRefillStatus::Transferred
75            | IcpRefillStatus::NotifyProcessing
76    ) || matches!(
77        (status, error_code, ledger_block_recorded),
78        (
79            IcpRefillStatus::Failed,
80            Some(IcpRefillErrorCode::NotifyFailed),
81            true
82        ) | (
83            IcpRefillStatus::Failed,
84            Some(IcpRefillErrorCode::BadFee),
85            false
86        )
87    )
88}
89
90// -----------------------------------------------------------------------------
91// Tests
92// -----------------------------------------------------------------------------
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn resumable_outcome_requires_active_or_retryable_state() {
100        assert!(icp_refill_outcome_is_resumable(
101            IcpRefillStatus::Requested,
102            None,
103            false
104        ));
105        assert!(icp_refill_outcome_is_resumable(
106            IcpRefillStatus::Transferred,
107            None,
108            true
109        ));
110        assert!(icp_refill_outcome_is_resumable(
111            IcpRefillStatus::NotifyProcessing,
112            Some(IcpRefillErrorCode::Processing),
113            true
114        ));
115        assert!(icp_refill_outcome_is_resumable(
116            IcpRefillStatus::Failed,
117            Some(IcpRefillErrorCode::BadFee),
118            false
119        ));
120        assert!(icp_refill_outcome_is_resumable(
121            IcpRefillStatus::Failed,
122            Some(IcpRefillErrorCode::NotifyFailed),
123            true
124        ));
125
126        assert!(!icp_refill_outcome_is_resumable(
127            IcpRefillStatus::Completed,
128            None,
129            true
130        ));
131        assert!(!icp_refill_outcome_is_resumable(
132            IcpRefillStatus::Failed,
133            Some(IcpRefillErrorCode::NotifyMaxAttempts),
134            true
135        ));
136        assert!(!icp_refill_outcome_is_resumable(
137            IcpRefillStatus::Failed,
138            Some(IcpRefillErrorCode::NotifyFailed),
139            false
140        ));
141        assert!(!icp_refill_outcome_is_resumable(
142            IcpRefillStatus::Failed,
143            Some(IcpRefillErrorCode::BadFee),
144            true
145        ));
146    }
147}