kujira_std/
schedule.rs

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        //
101
102        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        // last 400 seconds out of 1000
120        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        // assert_eq!(
133        //     s.released(
134        //         &Timestamp::from_seconds(1200),
135        //         &Timestamp::from_seconds(1400)
136        //     ),
137        //     Uint128::zero()
138        // );
139
140        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        //
148
149        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        //
162
163        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        // total 3k distribution
176        // start 1670940900
177        // last distributed 1671780641
178        // end 1671545400
179
180        // This was distributed since it ended. make sure it's not offering extra rewards
181
182        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}