Skip to main content

solana_message/versions/v1/
config.rs

1#[cfg(feature = "serde")]
2use serde_derive::{Deserialize, Serialize};
3#[cfg(feature = "frozen-abi")]
4use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
5
6/// Compute budget configuration for V1 transactions.
7#[cfg_attr(
8    feature = "frozen-abi",
9    derive(AbiExample, StableAbi, StableAbiSample),
10    frozen_abi(
11        abi_digest = "7vsBq6gqhX7ZZE6LHVDjiwK5YmSA9Dvc5rCXgLyr32UP",
12        test_roundtrip = "eq_and_wire"
13    )
14)]
15#[cfg_attr(
16    feature = "serde",
17    derive(Serialize, Deserialize),
18    serde(rename_all = "camelCase")
19)]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub struct TransactionConfig {
22    /// Priority fee in lamports.
23    pub priority_fee: Option<u64>,
24
25    /// Maximum compute units. None means use `0`.
26    pub compute_unit_limit: Option<u32>,
27
28    /// Maximum bytes of account data that may be loaded. None means use `0`.
29    pub loaded_accounts_data_size_limit: Option<u32>,
30
31    /// Heap size in bytes. Must be multiple of 1024. `None` = 32KB.
32    pub heap_size: Option<u32>,
33}
34
35impl TransactionConfig {
36    pub const fn empty() -> Self {
37        Self {
38            priority_fee: None,
39            compute_unit_limit: None,
40            loaded_accounts_data_size_limit: None,
41            heap_size: None,
42        }
43    }
44
45    #[must_use]
46    pub const fn with_priority_fee(mut self, fee: u64) -> Self {
47        self.priority_fee = Some(fee);
48        self
49    }
50
51    #[must_use]
52    pub const fn with_compute_unit_limit(mut self, limit: u32) -> Self {
53        self.compute_unit_limit = Some(limit);
54        self
55    }
56
57    #[must_use]
58    pub const fn with_loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
59        self.loaded_accounts_data_size_limit = Some(limit);
60        self
61    }
62
63    /// Heap size must be a multiple of 1024. Validated during deserialization.
64    #[must_use]
65    pub const fn with_heap_size(mut self, size: u32) -> Self {
66        self.heap_size = Some(size);
67        self
68    }
69
70    /// Total size in bytes required to store the config values.
71    pub const fn size(&self) -> usize {
72        let mut size: usize = 0;
73
74        if self.priority_fee.is_some() {
75            size = size.saturating_add(size_of::<u64>());
76        }
77
78        if self.compute_unit_limit.is_some() {
79            size = size.saturating_add(size_of::<u32>());
80        }
81
82        if self.loaded_accounts_data_size_limit.is_some() {
83            size = size.saturating_add(size_of::<u32>());
84        }
85
86        if self.heap_size.is_some() {
87            size = size.saturating_add(size_of::<u32>());
88        }
89
90        size
91    }
92}
93
94impl From<&TransactionConfig> for TransactionConfigMask {
95    fn from(config: &TransactionConfig) -> Self {
96        let mut mask = 0u32;
97
98        if config.priority_fee.is_some() {
99            mask |= Self::PRIORITY_FEE;
100        }
101
102        if config.compute_unit_limit.is_some() {
103            mask |= Self::COMPUTE_UNIT_LIMIT;
104        }
105
106        if config.loaded_accounts_data_size_limit.is_some() {
107            mask |= Self::LOADED_ACCOUNTS_DATA_SIZE;
108        }
109
110        if config.heap_size.is_some() {
111            mask |= Self::HEAP_SIZE;
112        }
113
114        TransactionConfigMask(mask)
115    }
116}
117
118impl From<TransactionConfig> for TransactionConfigMask {
119    fn from(config: TransactionConfig) -> Self {
120        TransactionConfigMask::from(&config)
121    }
122}
123
124/// Bitmask indicating which configuration values are present in a V1 transaction.
125///
126/// Each bit (or bit pair) corresponds to a specific configuration field.
127/// The config values array contains entries only for fields whose bits are set.
128#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
130pub struct TransactionConfigMask(pub u32);
131
132impl TransactionConfigMask {
133    /// Mask for priority fee config.
134    ///
135    /// Bits 0-1 (requires both bits set, 8 bytes as u64 LE).
136    pub const PRIORITY_FEE: u32 = 0b11;
137
138    /// Mask for compute unit limit
139    ///
140    /// Bit 2 (4 bytes as u32 LE).
141    pub const COMPUTE_UNIT_LIMIT: u32 = 0b100;
142
143    /// Mask for loaded accounts data size limit
144    ///
145    /// Bit 3 (4 bytes as u32 LE).
146    pub const LOADED_ACCOUNTS_DATA_SIZE: u32 = 0b1000;
147
148    /// Mask for requested heap size.
149    ///
150    /// Bit 4 (4 bytes as u32 LE).
151    pub const HEAP_SIZE: u32 = 0b10000;
152
153    /// Mask of all known/supported bits (bits 0-4).
154    pub const KNOWN_BITS: u32 = Self::PRIORITY_FEE
155        | Self::COMPUTE_UNIT_LIMIT
156        | Self::LOADED_ACCOUNTS_DATA_SIZE
157        | Self::HEAP_SIZE;
158
159    pub const fn new(mask: u32) -> Self {
160        Self(mask)
161    }
162
163    /// Returns `true`` if any unknown bits are set.
164    ///
165    /// An unknown bit is any bit that is not defined in `KNOWN_BITS`.
166    pub const fn has_unknown_bits(&self) -> bool {
167        (self.0 | Self::KNOWN_BITS) != Self::KNOWN_BITS
168    }
169
170    pub const fn has_priority_fee(&self) -> bool {
171        (self.0 & Self::PRIORITY_FEE) == Self::PRIORITY_FEE
172    }
173
174    /// Returns true if only one of the two priority fee bits is set (invalid).
175    pub const fn has_invalid_priority_fee_bits(&self) -> bool {
176        let bits = self.0 & Self::PRIORITY_FEE;
177        bits != 0 && bits != Self::PRIORITY_FEE
178    }
179
180    pub const fn has_compute_unit_limit(&self) -> bool {
181        (self.0 & Self::COMPUTE_UNIT_LIMIT) != 0
182    }
183
184    pub const fn has_loaded_accounts_data_size(&self) -> bool {
185        (self.0 & Self::LOADED_ACCOUNTS_DATA_SIZE) != 0
186    }
187
188    pub const fn has_heap_size(&self) -> bool {
189        (self.0 & Self::HEAP_SIZE) != 0
190    }
191
192    /// Total size in bytes required to store the config values.
193    pub const fn size_of_config(&self) -> usize {
194        let mut size: usize = 0;
195
196        if self.has_priority_fee() {
197            size = size.saturating_add(size_of::<u64>());
198        }
199
200        if self.has_compute_unit_limit() {
201            size = size.saturating_add(size_of::<u32>());
202        }
203
204        if self.has_loaded_accounts_data_size() {
205            size = size.saturating_add(size_of::<u32>());
206        }
207
208        if self.has_heap_size() {
209            size = size.saturating_add(size_of::<u32>());
210        }
211
212        size
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn has_unknown_bits_detects_unsupported_bits() {
222        assert!(!TransactionConfigMask::new(0).has_unknown_bits());
223        assert!(!TransactionConfigMask::new(0b11111).has_unknown_bits());
224        assert!(TransactionConfigMask::new(0b100000).has_unknown_bits());
225        assert!(TransactionConfigMask::new(0x80000000).has_unknown_bits());
226        assert!(TransactionConfigMask::new(0b111111).has_unknown_bits());
227    }
228
229    #[test]
230    fn has_priority_fee_requires_both_bits() {
231        assert!(!TransactionConfigMask::new(0).has_priority_fee());
232        assert!(!TransactionConfigMask::new(0b01).has_priority_fee());
233        assert!(!TransactionConfigMask::new(0b10).has_priority_fee());
234        assert!(TransactionConfigMask::new(0b11).has_priority_fee());
235    }
236
237    #[test]
238    fn has_invalid_priority_fee_bits_detects_partial() {
239        assert!(!TransactionConfigMask::new(0).has_invalid_priority_fee_bits());
240        assert!(TransactionConfigMask::new(0b01).has_invalid_priority_fee_bits());
241        assert!(TransactionConfigMask::new(0b10).has_invalid_priority_fee_bits());
242        assert!(!TransactionConfigMask::new(0b11).has_invalid_priority_fee_bits());
243    }
244
245    #[test]
246    fn has_field_methods_check_individual_bits() {
247        let mask = TransactionConfigMask::new(0b11100);
248        assert!(mask.has_compute_unit_limit());
249        assert!(mask.has_loaded_accounts_data_size());
250        assert!(mask.has_heap_size());
251
252        let mask = TransactionConfigMask::new(0);
253        assert!(!mask.has_compute_unit_limit());
254        assert!(!mask.has_loaded_accounts_data_size());
255        assert!(!mask.has_heap_size());
256    }
257
258    #[test]
259    fn config_values_size_sums_field_sizes() {
260        assert_eq!(TransactionConfigMask::new(0).size_of_config(), 0);
261        assert_eq!(TransactionConfigMask::new(0b11).size_of_config(), 8);
262        assert_eq!(TransactionConfigMask::new(0b100).size_of_config(), 4);
263        assert_eq!(TransactionConfigMask::new(0b11111).size_of_config(), 20);
264    }
265
266    #[test]
267    fn from_config_sets_correct_bits() {
268        let config = TransactionConfig::empty()
269            .with_priority_fee(1000)
270            .with_compute_unit_limit(200_000);
271
272        let mask = TransactionConfigMask::from(&config);
273        assert!(mask.has_priority_fee());
274        assert!(mask.has_compute_unit_limit());
275        assert!(!mask.has_loaded_accounts_data_size());
276        assert!(!mask.has_heap_size());
277    }
278
279    #[test]
280    fn mask_invariants_hold_for_all_known_bit_patterns() {
281        for raw in 0u32..(1u32 << 5) {
282            let mask = TransactionConfigMask::new(raw);
283
284            assert!(!mask.has_unknown_bits());
285
286            if mask.has_priority_fee() {
287                assert!(!mask.has_invalid_priority_fee_bits());
288            }
289
290            let mut expected_size = 0;
291            if mask.has_priority_fee() {
292                expected_size += size_of::<u64>();
293            }
294            if mask.has_compute_unit_limit() {
295                expected_size += size_of::<u32>();
296            }
297            if mask.has_loaded_accounts_data_size() {
298                expected_size += size_of::<u32>();
299            }
300            if mask.has_heap_size() {
301                expected_size += size_of::<u32>();
302            }
303            assert_eq!(mask.size_of_config(), expected_size);
304        }
305    }
306
307    #[test]
308    fn builder_sets_all_fields() {
309        let config = TransactionConfig::empty()
310            .with_priority_fee(1000)
311            .with_compute_unit_limit(200_000)
312            .with_loaded_accounts_data_size_limit(64 * 1024)
313            .with_heap_size(64 * 1024);
314
315        assert_eq!(config.priority_fee, Some(1000));
316        assert_eq!(config.compute_unit_limit, Some(200_000));
317        assert_eq!(config.loaded_accounts_data_size_limit, Some(64 * 1024));
318        assert_eq!(config.heap_size, Some(64 * 1024));
319    }
320}