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    Duplicate,
49    FabricationUnavailable,
50    InvalidLedgerBlockIndex,
51    InvalidTransaction,
52    LedgerTransferFailed,
53    NotifyFailed,
54    NotifyMaxAttempts,
55    Processing,
56    RateGateDenied,
57    Refunded,
58    RequestDenied,
59    TransactionTooOld,
60    TransferWindowStale,
61}
62
63/// Return whether an ICP refill outcome must retain its operation identity for retry.
64#[must_use]
65pub const fn icp_refill_outcome_is_resumable(
66    status: IcpRefillStatus,
67    error_code: Option<IcpRefillErrorCode>,
68    ledger_block_recorded: bool,
69) -> bool {
70    matches!(
71        status,
72        IcpRefillStatus::Requested
73            | IcpRefillStatus::Transferred
74            | IcpRefillStatus::NotifyProcessing
75    ) || matches!(
76        (status, error_code, ledger_block_recorded),
77        (
78            IcpRefillStatus::Failed,
79            Some(IcpRefillErrorCode::NotifyFailed),
80            true
81        ) | (
82            IcpRefillStatus::Failed,
83            Some(IcpRefillErrorCode::BadFee),
84            false
85        )
86    )
87}
88
89// -----------------------------------------------------------------------------
90// Tests
91// -----------------------------------------------------------------------------
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn resumable_outcome_requires_active_or_retryable_state() {
99        assert!(icp_refill_outcome_is_resumable(
100            IcpRefillStatus::Requested,
101            None,
102            false
103        ));
104        assert!(icp_refill_outcome_is_resumable(
105            IcpRefillStatus::Transferred,
106            None,
107            true
108        ));
109        assert!(icp_refill_outcome_is_resumable(
110            IcpRefillStatus::NotifyProcessing,
111            Some(IcpRefillErrorCode::Processing),
112            true
113        ));
114        assert!(icp_refill_outcome_is_resumable(
115            IcpRefillStatus::Failed,
116            Some(IcpRefillErrorCode::BadFee),
117            false
118        ));
119        assert!(icp_refill_outcome_is_resumable(
120            IcpRefillStatus::Failed,
121            Some(IcpRefillErrorCode::NotifyFailed),
122            true
123        ));
124
125        assert!(!icp_refill_outcome_is_resumable(
126            IcpRefillStatus::Completed,
127            None,
128            true
129        ));
130        assert!(!icp_refill_outcome_is_resumable(
131            IcpRefillStatus::Failed,
132            Some(IcpRefillErrorCode::NotifyMaxAttempts),
133            true
134        ));
135        assert!(!icp_refill_outcome_is_resumable(
136            IcpRefillStatus::Failed,
137            Some(IcpRefillErrorCode::NotifyFailed),
138            false
139        ));
140        assert!(!icp_refill_outcome_is_resumable(
141            IcpRefillStatus::Failed,
142            Some(IcpRefillErrorCode::BadFee),
143            true
144        ));
145    }
146}