1use std::cmp::{max, min};
2
3use cosmwasm_schema::cw_serde;
4use cosmwasm_std::{Decimal, Decimal256, Fraction, Timestamp, Uint128, Uint256};
5
6#[cw_serde]
7pub struct Schedule {
8 pub start: Timestamp,
9 pub end: Timestamp,
10 pub amount: Uint128,
11 pub release: Release,
12}
13
14#[cw_serde]
15pub enum Release {
16 Fixed,
17 Decay,
18}
19
20impl Schedule {
21 pub fn released(&self, start: &Timestamp, end: &Timestamp) -> Uint128 {
22 if self.start.seconds() > end.seconds() {
23 return Uint128::zero();
24 }
25 match self.release {
26 Release::Fixed => {
27 let total_duration = self.end.seconds() - self.start.seconds();
28 let start = max(self.start.seconds(), start.seconds());
29 let end = min(self.end.seconds(), end.seconds());
30 if end <= start {
31 return Uint128::zero();
32 }
33 let duration = end - start;
34
35 self.amount
36 .mul_floor(Decimal::from_ratio(duration, total_duration))
37 }
38 Release::Decay => {
39 let total_duration = self.end.seconds() - self.start.seconds();
40 let start = max(self.start.seconds(), start.seconds());
41 let end = min(self.end.seconds(), end.seconds());
42 if end <= start {
43 return Uint128::zero();
44 }
45 let c = Decimal256::from_ratio(
46 Uint256::from(self.amount) * Uint256::from(2u128),
47 total_duration,
48 );
49 let div = Decimal256::from_ratio(total_duration * total_duration, self.amount);
50
51 let b = Uint256::from(end - self.start.seconds());
52 let a = Uint256::from(start - self.start.seconds());
53 let b = b.mul_floor(c)
54 - Uint256::one().mul_floor(Decimal256::from_ratio(
55 b * b * div.denominator(),
56 div.numerator(),
57 ));
58
59 let a = a.mul_floor(c)
60 - Uint256::one().mul_floor(Decimal256::from_ratio(
61 a * a * div.denominator(),
62 div.numerator(),
63 ));
64
65 let diff = b.checked_sub(a).unwrap_or_default();
66 diff.try_into().unwrap()
67 }
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74
75 use super::*;
76
77 #[test]
78 fn decay_schedule() {
79 let s = Schedule {
80 start: Timestamp::from_seconds(1670940900),
81 end: Timestamp::from_seconds(1671545400),
82 amount: Uint128::from(3000000000u128),
83 release: Release::Decay,
84 };
85
86 let extra = s.released(
87 &Timestamp::from_seconds(1671545401),
88 &Timestamp::from_seconds(1671963618),
89 );
90
91 println!("extra {extra}");
92
93 let s = Schedule {
94 start: Timestamp::from_seconds(0),
95 end: Timestamp::from_seconds(1000),
96 amount: Uint128::from(5000u128),
97 release: Release::Decay,
98 };
99
100 assert_eq!(
103 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(0)),
104 Uint128::zero()
105 );
106 assert_eq!(
107 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(1000)),
108 Uint128::from(5000u128)
109 );
110 assert_eq!(
111 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(100)),
112 Uint128::from(950u128)
113 );
114 assert_eq!(
115 s.released(&Timestamp::from_seconds(400), &Timestamp::from_seconds(600)),
116 Uint128::from(1000u128)
117 );
118
119 assert_eq!(
121 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(600)),
122 Uint128::from(4200u128)
123 );
124 assert_eq!(
125 s.released(
126 &Timestamp::from_seconds(600),
127 &Timestamp::from_seconds(1200)
128 ),
129 Uint128::from(800u128)
130 );
131
132 let s = Schedule {
141 start: Timestamp::from_seconds(0),
142 end: Timestamp::from_seconds(45000),
143 amount: Uint128::from(154600u128),
144 release: Release::Decay,
145 };
146
147 assert_eq!(
150 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(6750)),
151 Uint128::from(42901u128)
152 );
153
154 let s = Schedule {
155 start: Timestamp::from_seconds(20000),
156 end: Timestamp::from_seconds(45000),
157 amount: Uint128::from(154600u128),
158 release: Release::Decay,
159 };
160
161 assert_eq!(
164 s.released(&Timestamp::from_seconds(0), &Timestamp::from_seconds(6750)),
165 Uint128::zero()
166 );
167
168 let s = Schedule {
169 start: Timestamp::from_seconds(1670940900),
170 end: Timestamp::from_seconds(1671545400),
171 amount: Uint128::from(3000000000u128),
172 release: Release::Decay,
173 };
174
175 let extra = s.released(
183 &Timestamp::from_seconds(1671545401),
184 &Timestamp::from_seconds(1671963618),
185 );
186
187 println!("extra {extra}");
188
189 assert_eq!(
190 s.released(
191 &Timestamp::from_seconds(1671780641),
192 &Timestamp::from_seconds(1671785290)
193 ),
194 Uint128::zero()
195 );
196 }
197
198 #[test]
199 fn decimals() {
200 let s = Schedule {
201 start: Timestamp::from_seconds(1703083387),
202 end: Timestamp::from_seconds(1710974700),
203 amount: Uint128::from(65_000_000_000_000_000_000_000u128),
204 release: Release::Decay,
205 };
206
207 s.released(
208 &Timestamp::from_seconds(1703083387),
209 &Timestamp::from_seconds(1710974700),
210 );
211 }
212}