Skip to main content

spl_feature_proposal/
state.rs

1//! Program state
2use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
3use solana_program::{
4    clock::UnixTimestamp,
5    info,
6    program_error::ProgramError,
7    program_pack::{Pack, Sealed},
8};
9
10/// Criteria for accepting a feature proposal
11#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
12pub struct AcceptanceCriteria {
13    /// The balance of the feature proposal's token account must be greater than this amount, and
14    /// tallied before the deadline for the feature to be accepted.
15    pub tokens_required: u64,
16
17    /// If the required tokens are not tallied by this deadline then the proposal will expire.
18    pub deadline: UnixTimestamp,
19}
20
21/// Contents of a Feature Proposal account
22#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
23pub enum FeatureProposal {
24    /// Default account state after creating it
25    Uninitialized,
26    /// Feature proposal is now pending
27    Pending(AcceptanceCriteria),
28    /// Feature proposal was accepted and the feature is now active
29    Accepted {
30        /// The balance of the feature proposal's token account at the time of activation.
31        #[allow(dead_code)] // not dead code..
32        tokens_upon_acceptance: u64,
33    },
34    /// Feature proposal was not accepted before the deadline
35    Expired,
36}
37impl Sealed for FeatureProposal {}
38
39impl Pack for FeatureProposal {
40    const LEN: usize = 17; // see `test_get_packed_len()` for justification of "18"
41
42    fn pack_into_slice(&self, dst: &mut [u8]) {
43        let data = self.try_to_vec().unwrap();
44        dst[..data.len()].copy_from_slice(&data);
45    }
46
47    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
48        let mut mut_src: &[u8] = src;
49        Self::deserialize(&mut mut_src).map_err(|err| {
50            info!(&format!(
51                "Error: failed to deserialize feature proposal account: {}",
52                err
53            ));
54            ProgramError::InvalidAccountData
55        })
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::borsh_utils;
63
64    #[test]
65    fn test_get_packed_len() {
66        assert_eq!(
67            FeatureProposal::get_packed_len(),
68            borsh_utils::get_packed_len::<FeatureProposal>()
69        );
70    }
71
72    #[test]
73    fn test_serialize_bytes() {
74        assert_eq!(FeatureProposal::Expired.try_to_vec().unwrap(), vec![3]);
75
76        assert_eq!(
77            FeatureProposal::Pending(AcceptanceCriteria {
78                tokens_required: 0xdeadbeefdeadbeef,
79                deadline: -1,
80            })
81            .try_to_vec()
82            .unwrap(),
83            vec![1, 239, 190, 173, 222, 239, 190, 173, 222, 255, 255, 255, 255, 255, 255, 255, 255],
84        );
85    }
86
87    #[test]
88    fn test_serialize_large_slice() {
89        let mut dst = vec![0xff; 4];
90        FeatureProposal::Expired.pack_into_slice(&mut dst);
91
92        // Extra bytes (0xff) ignored
93        assert_eq!(dst, vec![3, 0xff, 0xff, 0xff]);
94    }
95
96    #[test]
97    fn state_deserialize_invalid() {
98        assert_eq!(
99            FeatureProposal::unpack_from_slice(&[3]),
100            Ok(FeatureProposal::Expired),
101        );
102
103        // Extra bytes (0xff) ignored...
104        assert_eq!(
105            FeatureProposal::unpack_from_slice(&[3, 0xff, 0xff, 0xff]),
106            Ok(FeatureProposal::Expired),
107        );
108
109        assert_eq!(
110            FeatureProposal::unpack_from_slice(&[4]),
111            Err(ProgramError::InvalidAccountData),
112        );
113    }
114}