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
//! vest state
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 {
    /// The address authorized to terminate this contract with a signed Terminate instruction
    pub terminator_pubkey: Pubkey,

    /// The address authorized to redeem vested tokens
    pub payee_pubkey: Pubkey,

    /// The day from which the vesting contract begins
    #[serde(with = "ts_seconds")]
    pub start_date_time: DateTime<Utc>,

    /// Address of an account containing a trusted date, used to drive the vesting schedule
    pub date_pubkey: Pubkey,

    /// The number of lamports to send the payee if the schedule completes
    pub total_lamports: u64,

    /// The number of lamports the payee has already redeemed
    pub redeemed_lamports: u64,

    /// The number of lamports the terminator repurchased
    pub reneged_lamports: u64,

    /// True if the terminator has declared this contract fully vested.
    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)
    }

    /// Redeem vested tokens.
    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;
    }

    /// Renege on the given number of tokens and send them to the given payee.
    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;
    }

    /// Mark this contract as fully vested, regardless of the date.
    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);

        // Verify vesting schedule is calculated with original amount.
        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);

        // Verify reneged tokens aren't redeemable.
        assert_eq!(vest_state.calc_vested_lamports(Utc.ymd(2022, 1, 1)), 2);

        // Verify reneged tokens aren't redeemable after fully vesting.
        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);
    }
}