defuse_nep245/
checked.rs

1use crate::MtEvent;
2use defuse_near_utils::REFUND_MEMO;
3use near_sdk::FunctionError;
4
5#[derive(Debug, Clone, PartialEq, Eq, FunctionError, thiserror::Error)]
6#[error("refund event log would be too long")]
7pub struct ErrorLogTooLong;
8
9const REFUND_STR_LEN: usize = REFUND_MEMO.len();
10pub const REFUND_EXTRA_BYTES: usize = r#","memo":""#.len() + REFUND_STR_LEN;
11
12#[derive(Default, Clone, Copy)]
13#[must_use]
14pub struct RefundLogDelta {
15    overhead: usize,
16    savings: usize,
17}
18
19impl RefundLogDelta {
20    pub const fn new(overhead: usize, savings: usize) -> Self {
21        Self {
22            overhead: overhead.saturating_sub(savings),
23            savings: savings.saturating_sub(overhead),
24        }
25    }
26
27    pub const fn overhead(&self) -> usize {
28        self.overhead
29    }
30
31    pub const fn savings(&self) -> usize {
32        self.savings
33    }
34
35    pub const fn saturating_add(self, other: Self) -> Self {
36        Self::new(
37            self.overhead.saturating_add(other.overhead),
38            self.savings.saturating_add(other.savings),
39        )
40    }
41}
42
43const fn refund_log_delta(memo: Option<&str>) -> RefundLogDelta {
44    let Some(m) = memo else {
45        return RefundLogDelta {
46            overhead: REFUND_EXTRA_BYTES,
47            savings: 0,
48        };
49    };
50    RefundLogDelta::new(REFUND_STR_LEN, m.len())
51}
52
53impl MtEvent<'_> {
54    pub(crate) fn compute_refund_delta(&self) -> RefundLogDelta {
55        match self {
56            MtEvent::MtMint(events) => events
57                .iter()
58                .map(|e| refund_log_delta(e.memo.as_deref()))
59                .fold(RefundLogDelta::default(), RefundLogDelta::saturating_add),
60            MtEvent::MtBurn(events) => events
61                .iter()
62                .map(|e| refund_log_delta(e.memo.as_deref()))
63                .fold(RefundLogDelta::default(), RefundLogDelta::saturating_add),
64            MtEvent::MtTransfer(events) => events
65                .iter()
66                .map(|e| refund_log_delta(e.memo.as_deref()))
67                .fold(RefundLogDelta::default(), RefundLogDelta::saturating_add),
68        }
69    }
70}
71
72/// A validated event log that has been checked for refund overhead.
73/// Use [`CheckedMtEvent::emit`] to emit the event.
74#[derive(Debug)]
75#[must_use = "call `.emit()` to emit the event"]
76pub struct CheckedMtEvent(pub(crate) String);
77
78impl CheckedMtEvent {
79    pub fn emit(self) {
80        near_sdk::env::log_str(&self.0);
81    }
82}
83
84#[cfg(test)]
85mod test {
86    use crate::checked::REFUND_EXTRA_BYTES;
87
88    use super::refund_log_delta;
89
90    #[test]
91    fn test_refund_log_delta_shorter_memo() {
92        let delta = refund_log_delta(Some("r"));
93        assert_eq!(delta.savings(), 0);
94        assert_eq!(delta.overhead(), 5);
95    }
96
97    #[test]
98    fn test_refund_log_delta_longer_memo() {
99        let delta = refund_log_delta(Some("refund123"));
100        assert_eq!(delta.savings(), 3);
101        assert_eq!(delta.overhead(), 0);
102    }
103
104    #[test]
105    fn test_refund_log_delta_equal_memo() {
106        let delta = refund_log_delta(Some("123456"));
107        assert_eq!(delta.savings(), 0);
108        assert_eq!(delta.overhead(), 0);
109    }
110
111    #[test]
112    fn test_refund_log_delta_empty_memo() {
113        let delta = refund_log_delta(None);
114        assert_eq!(delta.savings(), 0);
115        assert_eq!(delta.overhead(), REFUND_EXTRA_BYTES);
116    }
117}