1use cosmwasm_schema::cw_serde;
2use cosmwasm_std::{BlockInfo, StdError, StdResult, Timestamp};
3use std::cmp::Ordering;
4use std::fmt;
5use std::ops::{Add, Mul};
6
7#[cw_serde]
11#[derive(Copy)]
12pub enum Expiration {
13 AtHeight(u64),
15 AtTime(Timestamp),
17 Never {},
19}
20
21impl fmt::Display for Expiration {
22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 match self {
24 Expiration::AtHeight(height) => write!(f, "expiration height: {}", height),
25 Expiration::AtTime(time) => write!(f, "expiration time: {}", time),
26 Expiration::Never {} => write!(f, "expiration: never"),
27 }
28 }
29}
30
31impl Default for Expiration {
33 fn default() -> Self {
34 Expiration::Never {}
35 }
36}
37
38impl Expiration {
39 pub fn is_expired(&self, block: &BlockInfo) -> bool {
40 match self {
41 Expiration::AtHeight(height) => block.height >= *height,
42 Expiration::AtTime(time) => block.time >= *time,
43 Expiration::Never {} => false,
44 }
45 }
46}
47
48impl Add<Duration> for Expiration {
49 type Output = StdResult<Expiration>;
50
51 fn add(self, duration: Duration) -> StdResult<Expiration> {
52 match (self, duration) {
53 (Expiration::AtTime(t), Duration::Time(delta)) => {
54 Ok(Expiration::AtTime(t.plus_seconds(delta)))
55 }
56 (Expiration::AtHeight(h), Duration::Height(delta)) => {
57 Ok(Expiration::AtHeight(h + delta))
58 }
59 (Expiration::Never {}, _) => Ok(Expiration::Never {}),
60 _ => Err(StdError::msg("Cannot add height and time")),
61 }
62 }
63}
64
65impl PartialOrd for Expiration {
67 fn partial_cmp(&self, other: &Expiration) -> Option<Ordering> {
68 match (self, other) {
69 (Expiration::AtHeight(h1), Expiration::AtHeight(h2)) => Some(h1.cmp(h2)),
71 (Expiration::AtTime(t1), Expiration::AtTime(t2)) => Some(t1.cmp(t2)),
72 (Expiration::Never {}, Expiration::Never {}) => Some(Ordering::Equal),
74 (Expiration::Never {}, _) => Some(Ordering::Greater),
75 (_, Expiration::Never {}) => Some(Ordering::Less),
76 _ => None,
78 }
79 }
80}
81
82pub const HOUR: Duration = Duration::Time(60 * 60);
83pub const DAY: Duration = Duration::Time(24 * 60 * 60);
84pub const WEEK: Duration = Duration::Time(7 * 24 * 60 * 60);
85
86#[cw_serde]
90#[derive(Copy)]
91pub enum Duration {
92 Height(u64),
93 Time(u64),
95}
96
97impl fmt::Display for Duration {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 match self {
100 Duration::Height(height) => write!(f, "height: {}", height),
101 Duration::Time(time) => write!(f, "time: {}", time),
102 }
103 }
104}
105
106impl Duration {
107 pub fn after(&self, block: &BlockInfo) -> Expiration {
109 match self {
110 Duration::Height(h) => Expiration::AtHeight(block.height + h),
111 Duration::Time(t) => Expiration::AtTime(block.time.plus_seconds(*t)),
112 }
113 }
114
115 pub fn plus_one(&self) -> Duration {
117 match self {
118 Duration::Height(h) => Duration::Height(h + 1),
119 Duration::Time(t) => Duration::Time(t + 1),
120 }
121 }
122}
123
124impl Add<Duration> for Duration {
125 type Output = StdResult<Duration>;
126
127 fn add(self, rhs: Duration) -> StdResult<Duration> {
128 match (self, rhs) {
129 (Duration::Time(t), Duration::Time(t2)) => Ok(Duration::Time(t + t2)),
130 (Duration::Height(h), Duration::Height(h2)) => Ok(Duration::Height(h + h2)),
131 _ => Err(StdError::msg("Cannot add height and time")),
132 }
133 }
134}
135
136impl Mul<u64> for Duration {
137 type Output = Duration;
138
139 fn mul(self, rhs: u64) -> Self::Output {
140 match self {
141 Duration::Time(t) => Duration::Time(t * rhs),
142 Duration::Height(h) => Duration::Height(h * rhs),
143 }
144 }
145}
146
147#[cfg(test)]
148mod test {
149 use super::*;
150
151 #[test]
152 fn compare_expiration() {
153 assert!(Expiration::AtHeight(5) < Expiration::AtHeight(10));
155 assert!(Expiration::AtHeight(8) > Expiration::AtHeight(7));
156 assert!(
157 Expiration::AtTime(Timestamp::from_seconds(555))
158 < Expiration::AtTime(Timestamp::from_seconds(777))
159 );
160 assert!(
161 Expiration::AtTime(Timestamp::from_seconds(86))
162 < Expiration::AtTime(Timestamp::from_seconds(100))
163 );
164
165 assert!(Expiration::AtHeight(500000) < Expiration::Never {});
167 assert!(Expiration::Never {} > Expiration::AtTime(Timestamp::from_seconds(500000)));
168
169 assert_eq!(
171 None,
172 Expiration::AtTime(Timestamp::from_seconds(1000))
173 .partial_cmp(&Expiration::AtHeight(230))
174 );
175 assert_eq!(
176 Expiration::AtTime(Timestamp::from_seconds(1000))
177 .partial_cmp(&Expiration::AtHeight(230)),
178 None
179 );
180 assert_eq!(
181 Expiration::AtTime(Timestamp::from_seconds(1000))
182 .partial_cmp(&Expiration::AtHeight(230)),
183 None
184 );
185 assert!(!(Expiration::AtTime(Timestamp::from_seconds(1000)) == Expiration::AtHeight(230)));
186 }
187
188 #[test]
189 fn expiration_addition() {
190 let end = Expiration::AtHeight(12345) + Duration::Height(400);
192 assert_eq!(end.unwrap(), Expiration::AtHeight(12745));
193
194 let end = Expiration::AtTime(Timestamp::from_seconds(55544433)) + Duration::Time(40300);
196 assert_eq!(
197 end.unwrap(),
198 Expiration::AtTime(Timestamp::from_seconds(55584733))
199 );
200
201 let end = Expiration::Never {} + Duration::Time(40300);
203 assert_eq!(end.unwrap(), Expiration::Never {});
204
205 let end = Expiration::AtHeight(12345) + Duration::Time(1500);
207 end.unwrap_err();
208
209 }
213
214 #[test]
215 fn block_plus_duration() {
216 let block = BlockInfo {
217 height: 1000,
218 time: Timestamp::from_seconds(7777),
219 chain_id: "foo".to_string(),
220 };
221
222 let end = Duration::Height(456).after(&block);
223 assert_eq!(Expiration::AtHeight(1456), end);
224
225 let end = Duration::Time(1212).after(&block);
226 assert_eq!(Expiration::AtTime(Timestamp::from_seconds(8989)), end);
227 }
228
229 #[test]
230 fn duration_math() {
231 let long = (Duration::Height(444) + Duration::Height(555)).unwrap();
232 assert_eq!(Duration::Height(999), long);
233
234 let days = DAY * 3;
235 assert_eq!(Duration::Time(3 * 24 * 60 * 60), days);
236 }
237}