1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use crate::vest_schedule::create_vesting_schedule;
use bincode::{self, deserialize, serialize_into};
use chrono::prelude::*;
use chrono::{
prelude::{DateTime, TimeZone, Utc},
serde::ts_seconds,
};
use serde_derive::{Deserialize, Serialize};
use solana_sdk::{account::Account, instruction::InstructionError, pubkey::Pubkey};
use std::cmp::min;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct VestState {
pub terminator_pubkey: Pubkey,
pub payee_pubkey: Pubkey,
#[serde(with = "ts_seconds")]
pub start_date_time: DateTime<Utc>,
pub date_pubkey: Pubkey,
pub total_lamports: u64,
pub redeemed_lamports: u64,
pub reneged_lamports: u64,
pub is_fully_vested: bool,
}
impl Default for VestState {
fn default() -> Self {
Self {
terminator_pubkey: Pubkey::default(),
payee_pubkey: Pubkey::default(),
start_date_time: Utc.timestamp(0, 0),
date_pubkey: Pubkey::default(),
total_lamports: 0,
redeemed_lamports: 0,
reneged_lamports: 0,
is_fully_vested: false,
}
}
}
impl VestState {
pub fn serialize(&self, output: &mut [u8]) -> Result<(), InstructionError> {
serialize_into(output, self).map_err(|_| InstructionError::AccountDataTooSmall)
}
pub fn deserialize(input: &[u8]) -> Result<Self, InstructionError> {
deserialize(input).map_err(|_| InstructionError::InvalidAccountData)
}
fn calc_vested_lamports(&self, current_date: Date<Utc>) -> u64 {
let total_lamports_after_reneged = self.total_lamports - self.reneged_lamports;
if self.is_fully_vested {
return total_lamports_after_reneged;
}
let schedule = create_vesting_schedule(self.start_date_time.date(), self.total_lamports);
let vested_lamports = schedule
.into_iter()
.take_while(|(dt, _)| *dt <= current_date)
.map(|(_, lamports)| lamports)
.sum::<u64>();
min(vested_lamports, total_lamports_after_reneged)
}
pub fn redeem_tokens(
&mut self,
contract_account: &mut Account,
current_date: Date<Utc>,
payee_account: &mut Account,
) {
let vested_lamports = self.calc_vested_lamports(current_date);
let redeemable_lamports = vested_lamports.saturating_sub(self.redeemed_lamports);
contract_account.lamports -= redeemable_lamports;
payee_account.lamports += redeemable_lamports;
self.redeemed_lamports += redeemable_lamports;
}
pub fn renege(
&mut self,
contract_account: &mut Account,
payee_account: &mut Account,
lamports: u64,
) {
let reneged_lamports = min(contract_account.lamports, lamports);
payee_account.lamports += reneged_lamports;
contract_account.lamports -= reneged_lamports;
self.reneged_lamports += reneged_lamports;
}
pub fn vest_all(&mut self) {
self.is_fully_vested = true;
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::id;
use solana_sdk::account::Account;
use solana_sdk::system_program;
#[test]
fn test_serializer() {
let mut a = Account::new(0, 512, &id());
let b = VestState::default();
b.serialize(&mut a.data).unwrap();
let c = VestState::deserialize(&a.data).unwrap();
assert_eq!(b, c);
}
#[test]
fn test_serializer_data_too_small() {
let mut a = Account::new(0, 1, &id());
let b = VestState::default();
assert_eq!(
b.serialize(&mut a.data),
Err(InstructionError::AccountDataTooSmall)
);
}
#[test]
fn test_schedule_after_renege() {
let total_lamports = 3;
let mut contract_account = Account::new(total_lamports, 512, &id());
let mut payee_account = Account::new(0, 0, &system_program::id());
let mut vest_state = VestState {
total_lamports,
start_date_time: Utc.ymd(2019, 1, 1).and_hms(0, 0, 0),
..VestState::default()
};
vest_state.serialize(&mut contract_account.data).unwrap();
let current_date = Utc.ymd(2020, 1, 1);
assert_eq!(vest_state.calc_vested_lamports(current_date), 1);
vest_state.renege(&mut contract_account, &mut payee_account, 1);
assert_eq!(vest_state.calc_vested_lamports(current_date), 1);
assert_eq!(vest_state.reneged_lamports, 1);
assert_eq!(vest_state.calc_vested_lamports(Utc.ymd(2022, 1, 1)), 2);
vest_state.vest_all();
assert_eq!(vest_state.calc_vested_lamports(Utc.ymd(2022, 1, 1)), 2);
}
#[test]
fn test_vest_all() {
let total_lamports = 3;
let mut contract_account = Account::new(total_lamports, 512, &id());
let mut vest_state = VestState {
total_lamports,
start_date_time: Utc.ymd(2019, 1, 1).and_hms(0, 0, 0),
..VestState::default()
};
vest_state.serialize(&mut contract_account.data).unwrap();
let current_date = Utc.ymd(2020, 1, 1);
assert_eq!(vest_state.calc_vested_lamports(current_date), 1);
vest_state.vest_all();
assert_eq!(vest_state.calc_vested_lamports(current_date), 3);
}
}