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