solana_program_runtime/
accounts_data_meter.rs

1//! The accounts data space has a maximum size it is permitted to grow to.  This module contains
2//! the constants and types for tracking and metering the accounts data space during program
3//! runtime.
4use solana_sdk::instruction::InstructionError;
5
6/// The maximum allowed size, in bytes, of the accounts data
7/// 128 GB was chosen because it is the RAM amount listed under Hardware Recommendations on
8/// [Validator Requirements](https://docs.solana.com/running-validator/validator-reqs), and
9/// validators often put the ledger on a RAM disk (i.e. tmpfs).
10pub const MAX_ACCOUNTS_DATA_LEN: u64 = 128_000_000_000;
11
12/// Meter and track the amount of available accounts data space
13#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
14pub struct AccountsDataMeter {
15    /// The maximum amount of accounts data space that can be used (in bytes)
16    maximum: u64,
17
18    /// The initial amount of accounts data space used (in bytes)
19    initial: u64,
20
21    /// The amount of accounts data space that has changed since `initial` (in bytes)
22    delta: i64,
23}
24
25impl AccountsDataMeter {
26    /// Make a new AccountsDataMeter
27    #[must_use]
28    pub fn new(initial_accounts_data_len: u64) -> Self {
29        let accounts_data_meter = Self {
30            maximum: MAX_ACCOUNTS_DATA_LEN,
31            initial: initial_accounts_data_len,
32            delta: 0,
33        };
34        debug_assert!(accounts_data_meter.initial <= accounts_data_meter.maximum);
35        accounts_data_meter
36    }
37
38    /// Return the maximum amount of accounts data space that can be used (in bytes)
39    pub fn maximum(&self) -> u64 {
40        self.maximum
41    }
42
43    /// Return the initial amount of accounts data space used (in bytes)
44    pub fn initial(&self) -> u64 {
45        self.initial
46    }
47
48    /// Return the amount of accounts data space that has changed (in bytes)
49    pub fn delta(&self) -> i64 {
50        self.delta
51    }
52
53    /// Return the current amount of accounts data space used (in bytes)
54    pub fn current(&self) -> u64 {
55        /// NOTE: Mixed integer ops currently not stable, so copying the impl.
56        /// * https://github.com/rust-lang/rust/issues/87840
57        /// * https://github.com/a1phyr/rust/blob/47edde1086412b36e9efd6098b191ec15a2a760a/library/core/src/num/uint_macros.rs#L1039-L1048
58        const fn saturating_add_signed(lhs: u64, rhs: i64) -> u64 {
59            let (res, overflow) = lhs.overflowing_add(rhs as u64);
60            if overflow == (rhs < 0) {
61                res
62            } else if overflow {
63                u64::MAX
64            } else {
65                u64::MIN
66            }
67        }
68        saturating_add_signed(self.initial, self.delta)
69    }
70
71    /// Get the remaining amount of accounts data space (in bytes)
72    pub fn remaining(&self) -> u64 {
73        self.maximum.saturating_sub(self.current())
74    }
75
76    /// Adjust the space used by accounts data by `amount` (in bytes).
77    ///
78    /// If `amount` is *positive*, we *increase* the space used by accounts data.  If `amount` is
79    /// *negative*, we *decrease* the space used by accounts data.  If `amount` is greater than
80    /// the remaining space, return an error and *do not* adjust accounts data space.
81    pub fn adjust_delta(&mut self, amount: i64) -> Result<(), InstructionError> {
82        if amount > self.remaining() as i64 {
83            return Err(InstructionError::MaxAccountsDataSizeExceeded);
84        }
85        self.adjust_delta_unchecked(amount);
86        Ok(())
87    }
88
89    /// Unconditionally adjust accounts data space.  Refer to `adjust_delta()` for more
90    /// documentation.
91    pub fn adjust_delta_unchecked(&mut self, amount: i64) {
92        self.delta = self.delta.saturating_add(amount);
93    }
94}
95
96#[cfg(test)]
97impl AccountsDataMeter {
98    pub fn set_maximum(&mut self, maximum: u64) {
99        self.maximum = maximum;
100    }
101    pub fn set_initial(&mut self, initial: u64) {
102        self.initial = initial;
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_new() {
112        let initial = 1234;
113        let accounts_data_meter = AccountsDataMeter::new(initial);
114        assert_eq!(accounts_data_meter.maximum, MAX_ACCOUNTS_DATA_LEN);
115        assert_eq!(accounts_data_meter.initial, initial);
116    }
117
118    #[test]
119    fn test_new_can_use_max_len() {
120        let _ = AccountsDataMeter::new(MAX_ACCOUNTS_DATA_LEN);
121    }
122
123    #[test]
124    #[should_panic]
125    fn test_new_panics_if_initial_len_too_big() {
126        let _ = AccountsDataMeter::new(MAX_ACCOUNTS_DATA_LEN + 1);
127    }
128
129    #[test]
130    fn test_remaining() {
131        let initial_accounts_data_len = 0;
132        let accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
133        assert_eq!(accounts_data_meter.remaining(), MAX_ACCOUNTS_DATA_LEN);
134    }
135
136    #[test]
137    fn test_remaining_saturates() {
138        let initial_accounts_data_len = 0;
139        let mut accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
140        // To test that remaining() saturates, need to break the invariant that initial <= maximum
141        accounts_data_meter.initial = MAX_ACCOUNTS_DATA_LEN + 1;
142        assert_eq!(accounts_data_meter.remaining(), 0);
143    }
144
145    #[test]
146    fn test_adjust_delta() {
147        let initial_accounts_data_len = 0;
148        let mut accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
149
150        // Test: simple, positive numbers
151        let result = accounts_data_meter.adjust_delta(0);
152        assert!(result.is_ok());
153        let result = accounts_data_meter.adjust_delta(1);
154        assert!(result.is_ok());
155        let result = accounts_data_meter.adjust_delta(4);
156        assert!(result.is_ok());
157        let result = accounts_data_meter.adjust_delta(9);
158        assert!(result.is_ok());
159
160        // Test: adjust_delta can use up the remaining amount
161        let remaining = accounts_data_meter.remaining() as i64;
162        let result = accounts_data_meter.adjust_delta(remaining);
163        assert!(result.is_ok());
164        assert_eq!(accounts_data_meter.remaining(), 0);
165    }
166
167    #[test]
168    fn test_adjust_delta_deallocate() {
169        let initial_accounts_data_len = 10_000;
170        let mut accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
171        let remaining_before = accounts_data_meter.remaining();
172
173        let amount = (initial_accounts_data_len / 2) as i64;
174        let amount = -amount;
175        let result = accounts_data_meter.adjust_delta(amount);
176        assert!(result.is_ok());
177        let remaining_after = accounts_data_meter.remaining();
178        assert_eq!(remaining_after, remaining_before + amount.unsigned_abs());
179    }
180
181    #[test]
182    fn test_adjust_delta_exceeding() {
183        let initial_accounts_data_len = 0;
184        let mut accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
185
186        // Test: adjusting delta by more than what's available
187        // (1) returns an error,
188        // (2) does not adjust delta
189        let remaining = accounts_data_meter.remaining();
190        let result = accounts_data_meter.adjust_delta(remaining as i64 + 1);
191        assert!(result.is_err());
192        assert_eq!(accounts_data_meter.remaining(), remaining);
193    }
194
195    #[test]
196    fn test_adjust_delta_zero() {
197        // Pre-condition: set up the accounts data meter such that there is no remaining space
198        let initial_accounts_data_len = 1234;
199        let mut accounts_data_meter = AccountsDataMeter::new(initial_accounts_data_len);
200        accounts_data_meter.maximum = initial_accounts_data_len;
201        assert_eq!(accounts_data_meter.remaining(), 0);
202
203        // Test: can always adjust delta by zero, even if there is no remaining space
204        let result = accounts_data_meter.adjust_delta(0);
205        assert!(result.is_ok());
206    }
207}