Skip to main content

gpl_feature_proposal/
state.rs

1//! Program state
2use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
3use gemachain_program::{
4    clock::UnixTimestamp,
5    msg,
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            msg!(
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
63    #[test]
64    fn test_get_packed_len() {
65        assert_eq!(
66            FeatureProposal::get_packed_len(),
67            gemachain_program::borsh::get_packed_len::<FeatureProposal>()
68        );
69    }
70
71    #[test]
72    fn test_serialize_bytes() {
73        assert_eq!(FeatureProposal::Expired.try_to_vec().unwrap(), vec![3]);
74
75        assert_eq!(
76            FeatureProposal::Pending(AcceptanceCriteria {
77                tokens_required: 0xdeadbeefdeadbeef,
78                deadline: -1,
79            })
80            .try_to_vec()
81            .unwrap(),
82            vec![1, 239, 190, 173, 222, 239, 190, 173, 222, 255, 255, 255, 255, 255, 255, 255, 255],
83        );
84    }
85
86    #[test]
87    fn test_serialize_large_slice() {
88        let mut dst = vec![0xff; 4];
89        FeatureProposal::Expired.pack_into_slice(&mut dst);
90
91        // Extra bytes (0xff) ignored
92        assert_eq!(dst, vec![3, 0xff, 0xff, 0xff]);
93    }
94
95    #[test]
96    fn state_deserialize_invalid() {
97        assert_eq!(
98            FeatureProposal::unpack_from_slice(&[3]),
99            Ok(FeatureProposal::Expired),
100        );
101
102        // Extra bytes (0xff) ignored...
103        assert_eq!(
104            FeatureProposal::unpack_from_slice(&[3, 0xff, 0xff, 0xff]),
105            Ok(FeatureProposal::Expired),
106        );
107
108        assert_eq!(
109            FeatureProposal::unpack_from_slice(&[4]),
110            Err(ProgramError::InvalidAccountData),
111        );
112    }
113}