templar_common/
time_chunk.rs1use near_sdk::{env, json_types::U64, near};
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
4#[near(serializers = [json, borsh])]
5pub enum V0 {
6 BlockHeight { divisor: U64 },
7 EpochHeight { divisor: U64 },
8 BlockTimestampMs { divisor: U64 },
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
12#[near(serializers = [json, borsh])]
13pub struct V1 {
14 pub duration_ms: U64,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19#[near(serializers = [json, borsh])]
20#[cfg_attr(not(target_arch = "wasm32"), schemars(untagged))]
21pub enum TimeChunkConfiguration {
22 #[serde(untagged)]
23 V0(V0),
24 #[serde(untagged)]
25 V1(V1),
26}
27
28impl TimeChunkConfiguration {
29 pub fn new(duration_ms: u64) -> Self {
30 Self::V1(V1 {
31 duration_ms: U64(duration_ms),
32 })
33 }
34
35 pub fn duration_ms(&self) -> u64 {
36 match self {
37 TimeChunkConfiguration::V0(V0::BlockTimestampMs { divisor }) => divisor.0,
38 TimeChunkConfiguration::V0(_) => {
39 crate::panic_with_message("Unsupported time chunk configuration")
40 }
41 TimeChunkConfiguration::V1(v1) => v1.duration_ms.0,
42 }
43 }
44
45 pub fn now(&self) -> TimeChunk {
46 let block_timestamp_ms = env::block_timestamp_ms();
47 TimeChunk(U64(block_timestamp_ms
48 .checked_div(self.duration_ms())
49 .unwrap_or(block_timestamp_ms)))
50 }
51
52 pub fn previous(&self) -> TimeChunk {
53 let TimeChunk(U64(time)) = self.now();
54 #[allow(clippy::unwrap_used, reason = "Assume now > 0")]
55 TimeChunk(U64(time.checked_sub(1).unwrap()))
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
60#[near(serializers = [borsh, json])]
61pub struct TimeChunk(pub U64);
62
63#[cfg(test)]
64mod tests {
65 use near_sdk::serde_json;
66 use near_sdk::test_utils;
67
68 use super::*;
69
70 #[test]
71 fn schema_matches_serde_wire_forms() {
72 let schema = near_sdk::serde_json::to_value(schemars::schema_for!(TimeChunkConfiguration))
73 .expect("time chunk schema serializes");
74 let validator = jsonschema::draft7::new(&schema).expect("time chunk schema is Draft 7");
75 let configurations = [
76 (
77 TimeChunkConfiguration::V0(V0::BlockTimestampMs {
78 divisor: U64(600_000),
79 }),
80 serde_json::json!({"BlockTimestampMs":{"divisor":"600000"}}),
81 ),
82 (
83 TimeChunkConfiguration::V1(V1 {
84 duration_ms: U64(600_000),
85 }),
86 serde_json::json!({"duration_ms":"600000"}),
87 ),
88 ];
89
90 for (configuration, expected_json) in configurations {
91 let serialized =
92 serde_json::to_value(&configuration).expect("time chunk serialization succeeds");
93 assert_eq!(serialized, expected_json);
94 validator
95 .validate(&serialized)
96 .expect("serialized time chunk is valid under its schema");
97 assert_eq!(
98 serde_json::from_value::<TimeChunkConfiguration>(serialized)
99 .expect("time chunk deserialization succeeds"),
100 configuration
101 );
102 }
103 }
104
105 #[test]
106 fn now() {
107 let context = test_utils::VMContextBuilder::new()
108 .block_timestamp((600_000 * 45 + 12345) * 1_000_000 )
109 .build();
110 near_sdk::testing_env!(context.clone());
111
112 let t = TimeChunkConfiguration::new(600_000);
113 assert_eq!(t.duration_ms(), 600_000);
114 let now = t.now();
115 assert_eq!(now, TimeChunk(U64(45)));
116 let prev = t.previous();
117 assert_eq!(prev, TimeChunk(U64(44)));
118 }
119
120 #[test]
121 fn v0_deserialization() {
122 let s = r#"{
123 "BlockTimestampMs": {
124 "divisor": "600000"
125 }
126 }"#;
127
128 let d: TimeChunkConfiguration = serde_json::from_str(s).unwrap();
129
130 assert_eq!(
131 d,
132 TimeChunkConfiguration::V0(V0::BlockTimestampMs {
133 divisor: U64(600_000),
134 }),
135 );
136 assert_eq!(d.duration_ms(), 600_000);
137 }
138
139 #[test]
140 fn v0_serialization() {
141 let v0 = TimeChunkConfiguration::V0(V0::BlockTimestampMs {
142 divisor: U64(600_000),
143 });
144
145 let s = serde_json::to_string(&v0).unwrap();
146
147 assert_eq!(s, r#"{"BlockTimestampMs":{"divisor":"600000"}}"#);
148 }
149
150 #[test]
151 fn v1_deserialization_allows_version_metadata() {
152 let configuration: TimeChunkConfiguration =
153 serde_json::from_str(r#"{"version":"V1","duration_ms":"600000"}"#)
154 .expect("V1 accepts ignored version metadata");
155
156 assert_eq!(
157 configuration,
158 TimeChunkConfiguration::V1(V1 {
159 duration_ms: U64(600_000),
160 })
161 );
162 }
163
164 #[test]
165 fn v1_deserialization() {
166 let s = r#"{
167 "duration_ms": "600000"
168 }"#;
169
170 let d: TimeChunkConfiguration = serde_json::from_str(s).unwrap();
171
172 assert_eq!(
173 d,
174 TimeChunkConfiguration::V1(V1 {
175 duration_ms: U64(600_000),
176 }),
177 );
178 assert_eq!(d.duration_ms(), 600_000);
179 }
180
181 #[test]
182 fn v1_serialization() {
183 let v0 = TimeChunkConfiguration::V1(V1 {
184 duration_ms: U64(600_000),
185 });
186
187 let s = serde_json::to_string(&v0).unwrap();
188
189 assert_eq!(s, r#"{"duration_ms":"600000"}"#);
190 }
191}