light_merkle_tree_metadata/
rollover.rs

1use bytemuck::{Pod, Zeroable};
2use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
3
4use crate::{errors::MerkleTreeMetadataError, AnchorDeserialize, AnchorSerialize};
5
6#[repr(C)]
7#[derive(
8    AnchorDeserialize,
9    AnchorSerialize,
10    Debug,
11    PartialEq,
12    Default,
13    Pod,
14    Zeroable,
15    Clone,
16    Copy,
17    FromBytes,
18    IntoBytes,
19    KnownLayout,
20    Immutable,
21)]
22pub struct RolloverMetadata {
23    /// Unique index.
24    pub index: u64,
25    /// This fee is used for rent for the next account.
26    /// It accumulates in the account so that once the corresponding Merkle tree account is full it can be rolled over
27    pub rollover_fee: u64,
28    /// The threshold in percentage points when the account should be rolled over (95 corresponds to 95% filled).
29    pub rollover_threshold: u64,
30    /// Tip for maintaining the account.
31    pub network_fee: u64,
32    /// The slot when the account was rolled over, a rolled over account should not be written to.
33    pub rolledover_slot: u64,
34    /// If current slot is greater than rolledover_slot + close_threshold and
35    /// the account is empty it can be closed. No 'close' functionality has been
36    /// implemented yet.
37    pub close_threshold: u64,
38    /// Placeholder for bytes of additional accounts which are tied to the
39    /// Merkle trees operation and need to be rolled over as well.
40    pub additional_bytes: u64,
41}
42
43impl RolloverMetadata {
44    pub fn new(
45        index: u64,
46        rollover_fee: u64,
47        rollover_threshold: Option<u64>,
48        network_fee: u64,
49        close_threshold: Option<u64>,
50        additional_bytes: Option<u64>,
51    ) -> Self {
52        Self {
53            index,
54            rollover_fee,
55            rollover_threshold: rollover_threshold.unwrap_or(u64::MAX),
56            network_fee,
57            rolledover_slot: u64::MAX,
58            close_threshold: close_threshold.unwrap_or(u64::MAX),
59            additional_bytes: additional_bytes.unwrap_or_default(),
60        }
61    }
62
63    pub fn rollover(&mut self) -> Result<(), MerkleTreeMetadataError> {
64        if self.rollover_threshold == u64::MAX {
65            return Err(MerkleTreeMetadataError::RolloverNotConfigured);
66        }
67        if self.rolledover_slot != u64::MAX {
68            return Err(MerkleTreeMetadataError::MerkleTreeAlreadyRolledOver);
69        }
70        #[cfg(target_os = "solana")]
71        {
72            #[cfg(feature = "pinocchio")]
73            {
74                use pinocchio::sysvars::{clock::Clock, Sysvar};
75                self.rolledover_slot = Clock::get().unwrap().slot;
76            }
77            #[cfg(not(feature = "pinocchio"))]
78            {
79                use solana_sysvar::{clock::Clock, Sysvar};
80                self.rolledover_slot = Clock::get().unwrap().slot;
81            }
82        }
83        #[cfg(not(target_os = "solana"))]
84        {
85            // Mock for testing.
86            self.rolledover_slot = 1;
87        }
88        Ok(())
89    }
90}
91
92pub fn check_rollover_fee_sufficient(
93    rollover_fee: u64,
94    queue_rent: u64,
95    merkle_tree_rent: u64,
96    rollover_threshold: u64,
97    height: u32,
98) -> Result<(), MerkleTreeMetadataError> {
99    if rollover_threshold == 0 && rollover_fee >= queue_rent + merkle_tree_rent {
100        return Ok(());
101    }
102    if (rollover_fee * rollover_threshold * (2u64.pow(height))) / 100
103        < queue_rent + merkle_tree_rent
104    {
105        #[cfg(feature = "solana")]
106        {
107            use solana_msg::msg;
108            msg!("rollover_fee: {}", rollover_fee);
109            msg!("rollover_threshold: {}", rollover_threshold);
110            msg!("height: {}", height);
111            msg!("merkle_tree_rent: {}", merkle_tree_rent);
112            msg!("queue_rent: {}", queue_rent);
113            msg!(
114                "((rollover_fee * rollover_threshold * (2u64.pow(height))) / 100): {} < {} rent",
115                ((rollover_fee * rollover_threshold * (2u64.pow(height))) / 100),
116                queue_rent + merkle_tree_rent
117            );
118        }
119        return Err(MerkleTreeMetadataError::InsufficientRolloverFee);
120    }
121    Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::fee::compute_rollover_fee;
128
129    #[test]
130    fn test_rollover_metadata() {
131        let mut metadata = RolloverMetadata::new(0, 0, Some(95), 0, Some(100), Some(1));
132        assert_eq!(metadata.rollover_threshold, 95);
133        assert_eq!(metadata.close_threshold, 100);
134        assert_eq!(metadata.rolledover_slot, u64::MAX);
135        assert_eq!(metadata.additional_bytes, 1);
136
137        metadata.rollover().unwrap();
138
139        let mut metadata = RolloverMetadata::new(0, 0, None, 0, None, None);
140        assert_eq!(metadata.rollover_threshold, u64::MAX);
141        assert_eq!(metadata.close_threshold, u64::MAX);
142        assert_eq!(metadata.additional_bytes, 0);
143
144        assert_eq!(
145            metadata.rollover(),
146            Err(MerkleTreeMetadataError::RolloverNotConfigured)
147        );
148        let mut metadata = RolloverMetadata::new(0, 0, Some(95), 0, None, None);
149        assert_eq!(metadata.close_threshold, u64::MAX);
150
151        metadata.rollover().unwrap();
152        let mut metadata = RolloverMetadata::new(0, 0, Some(95), 0, None, None);
153        metadata.rolledover_slot = 0;
154        assert_eq!(metadata.close_threshold, u64::MAX);
155
156        assert_eq!(
157            metadata.rollover(),
158            Err(MerkleTreeMetadataError::MerkleTreeAlreadyRolledOver)
159        );
160    }
161
162    #[test]
163    fn test_check_rollover_fee_sufficient() {
164        let queue_rent = 1_000_000_000;
165        let merkle_tree_rent = 1_000_000_000;
166        let rollover_threshold = 95;
167        let tree_height = 20;
168        let total_rent = queue_rent + merkle_tree_rent;
169        let rollover_fee =
170            compute_rollover_fee(rollover_threshold, tree_height, total_rent).unwrap();
171        println!("rollover_fee: {}", rollover_fee);
172        assert!(check_rollover_fee_sufficient(
173            rollover_fee,
174            queue_rent,
175            merkle_tree_rent,
176            rollover_threshold,
177            tree_height
178        )
179        .is_ok());
180
181        {
182            let invalid_height = 19;
183            assert_eq!(
184                check_rollover_fee_sufficient(
185                    rollover_fee,
186                    queue_rent,
187                    merkle_tree_rent,
188                    rollover_threshold,
189                    invalid_height
190                ),
191                Err(MerkleTreeMetadataError::InsufficientRolloverFee)
192            );
193        }
194        {
195            let invalid_threshold = 90;
196            assert_eq!(
197                check_rollover_fee_sufficient(
198                    rollover_fee,
199                    queue_rent,
200                    merkle_tree_rent,
201                    invalid_threshold,
202                    tree_height
203                ),
204                Err(MerkleTreeMetadataError::InsufficientRolloverFee)
205            );
206        }
207        {
208            let invalid_queue_rent = queue_rent + 1_000_000_000;
209            assert_eq!(
210                check_rollover_fee_sufficient(
211                    rollover_fee,
212                    invalid_queue_rent,
213                    merkle_tree_rent,
214                    rollover_threshold,
215                    tree_height
216                ),
217                Err(MerkleTreeMetadataError::InsufficientRolloverFee)
218            );
219        }
220        {
221            let rollover_fee = rollover_fee - 1;
222            assert_eq!(
223                check_rollover_fee_sufficient(
224                    rollover_fee,
225                    queue_rent,
226                    merkle_tree_rent,
227                    rollover_threshold,
228                    tree_height
229                ),
230                Err(MerkleTreeMetadataError::InsufficientRolloverFee)
231            );
232        }
233    }
234}