#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "frozen-abi")]
use solana_frozen_abi_macro::AbiExample;
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TransactionConfig {
pub priority_fee: Option<u64>,
pub compute_unit_limit: Option<u32>,
pub loaded_accounts_data_size_limit: Option<u32>,
pub heap_size: Option<u32>,
}
impl TransactionConfig {
pub const fn empty() -> Self {
Self {
priority_fee: None,
compute_unit_limit: None,
loaded_accounts_data_size_limit: None,
heap_size: None,
}
}
#[must_use]
pub const fn with_priority_fee(mut self, fee: u64) -> Self {
self.priority_fee = Some(fee);
self
}
#[must_use]
pub const fn with_compute_unit_limit(mut self, limit: u32) -> Self {
self.compute_unit_limit = Some(limit);
self
}
#[must_use]
pub const fn with_loaded_accounts_data_size_limit(mut self, limit: u32) -> Self {
self.loaded_accounts_data_size_limit = Some(limit);
self
}
#[must_use]
pub const fn with_heap_size(mut self, size: u32) -> Self {
self.heap_size = Some(size);
self
}
pub const fn size(&self) -> usize {
let mut size: usize = 0;
if self.priority_fee.is_some() {
size = size.saturating_add(size_of::<u64>());
}
if self.compute_unit_limit.is_some() {
size = size.saturating_add(size_of::<u32>());
}
if self.loaded_accounts_data_size_limit.is_some() {
size = size.saturating_add(size_of::<u32>());
}
if self.heap_size.is_some() {
size = size.saturating_add(size_of::<u32>());
}
size
}
}
impl From<&TransactionConfig> for TransactionConfigMask {
fn from(config: &TransactionConfig) -> Self {
let mut mask = 0u32;
if config.priority_fee.is_some() {
mask |= Self::PRIORITY_FEE;
}
if config.compute_unit_limit.is_some() {
mask |= Self::COMPUTE_UNIT_LIMIT;
}
if config.loaded_accounts_data_size_limit.is_some() {
mask |= Self::LOADED_ACCOUNTS_DATA_SIZE;
}
if config.heap_size.is_some() {
mask |= Self::HEAP_SIZE;
}
TransactionConfigMask(mask)
}
}
impl From<TransactionConfig> for TransactionConfigMask {
fn from(config: TransactionConfig) -> Self {
TransactionConfigMask::from(&config)
}
}
#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TransactionConfigMask(pub u32);
impl TransactionConfigMask {
pub const PRIORITY_FEE: u32 = 0b11;
pub const COMPUTE_UNIT_LIMIT: u32 = 0b100;
pub const LOADED_ACCOUNTS_DATA_SIZE: u32 = 0b1000;
pub const HEAP_SIZE: u32 = 0b10000;
pub const KNOWN_BITS: u32 = Self::PRIORITY_FEE
| Self::COMPUTE_UNIT_LIMIT
| Self::LOADED_ACCOUNTS_DATA_SIZE
| Self::HEAP_SIZE;
pub const fn new(mask: u32) -> Self {
Self(mask)
}
pub const fn has_unknown_bits(&self) -> bool {
(self.0 | Self::KNOWN_BITS) != Self::KNOWN_BITS
}
pub const fn has_priority_fee(&self) -> bool {
(self.0 & Self::PRIORITY_FEE) == Self::PRIORITY_FEE
}
pub const fn has_invalid_priority_fee_bits(&self) -> bool {
let bits = self.0 & Self::PRIORITY_FEE;
bits != 0 && bits != Self::PRIORITY_FEE
}
pub const fn has_compute_unit_limit(&self) -> bool {
(self.0 & Self::COMPUTE_UNIT_LIMIT) != 0
}
pub const fn has_loaded_accounts_data_size(&self) -> bool {
(self.0 & Self::LOADED_ACCOUNTS_DATA_SIZE) != 0
}
pub const fn has_heap_size(&self) -> bool {
(self.0 & Self::HEAP_SIZE) != 0
}
pub const fn size_of_config(&self) -> usize {
let mut size: usize = 0;
if self.has_priority_fee() {
size = size.saturating_add(size_of::<u64>());
}
if self.has_compute_unit_limit() {
size = size.saturating_add(size_of::<u32>());
}
if self.has_loaded_accounts_data_size() {
size = size.saturating_add(size_of::<u32>());
}
if self.has_heap_size() {
size = size.saturating_add(size_of::<u32>());
}
size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn has_unknown_bits_detects_unsupported_bits() {
assert!(!TransactionConfigMask::new(0).has_unknown_bits());
assert!(!TransactionConfigMask::new(0b11111).has_unknown_bits());
assert!(TransactionConfigMask::new(0b100000).has_unknown_bits());
assert!(TransactionConfigMask::new(0x80000000).has_unknown_bits());
assert!(TransactionConfigMask::new(0b111111).has_unknown_bits());
}
#[test]
fn has_priority_fee_requires_both_bits() {
assert!(!TransactionConfigMask::new(0).has_priority_fee());
assert!(!TransactionConfigMask::new(0b01).has_priority_fee());
assert!(!TransactionConfigMask::new(0b10).has_priority_fee());
assert!(TransactionConfigMask::new(0b11).has_priority_fee());
}
#[test]
fn has_invalid_priority_fee_bits_detects_partial() {
assert!(!TransactionConfigMask::new(0).has_invalid_priority_fee_bits());
assert!(TransactionConfigMask::new(0b01).has_invalid_priority_fee_bits());
assert!(TransactionConfigMask::new(0b10).has_invalid_priority_fee_bits());
assert!(!TransactionConfigMask::new(0b11).has_invalid_priority_fee_bits());
}
#[test]
fn has_field_methods_check_individual_bits() {
let mask = TransactionConfigMask::new(0b11100);
assert!(mask.has_compute_unit_limit());
assert!(mask.has_loaded_accounts_data_size());
assert!(mask.has_heap_size());
let mask = TransactionConfigMask::new(0);
assert!(!mask.has_compute_unit_limit());
assert!(!mask.has_loaded_accounts_data_size());
assert!(!mask.has_heap_size());
}
#[test]
fn config_values_size_sums_field_sizes() {
assert_eq!(TransactionConfigMask::new(0).size_of_config(), 0);
assert_eq!(TransactionConfigMask::new(0b11).size_of_config(), 8);
assert_eq!(TransactionConfigMask::new(0b100).size_of_config(), 4);
assert_eq!(TransactionConfigMask::new(0b11111).size_of_config(), 20);
}
#[test]
fn from_config_sets_correct_bits() {
let config = TransactionConfig::empty()
.with_priority_fee(1000)
.with_compute_unit_limit(200_000);
let mask = TransactionConfigMask::from(&config);
assert!(mask.has_priority_fee());
assert!(mask.has_compute_unit_limit());
assert!(!mask.has_loaded_accounts_data_size());
assert!(!mask.has_heap_size());
}
#[test]
fn mask_invariants_hold_for_all_known_bit_patterns() {
for raw in 0u32..(1u32 << 5) {
let mask = TransactionConfigMask::new(raw);
assert!(!mask.has_unknown_bits());
if mask.has_priority_fee() {
assert!(!mask.has_invalid_priority_fee_bits());
}
let mut expected_size = 0;
if mask.has_priority_fee() {
expected_size += size_of::<u64>();
}
if mask.has_compute_unit_limit() {
expected_size += size_of::<u32>();
}
if mask.has_loaded_accounts_data_size() {
expected_size += size_of::<u32>();
}
if mask.has_heap_size() {
expected_size += size_of::<u32>();
}
assert_eq!(mask.size_of_config(), expected_size);
}
}
#[test]
fn builder_sets_all_fields() {
let config = TransactionConfig::empty()
.with_priority_fee(1000)
.with_compute_unit_limit(200_000)
.with_loaded_accounts_data_size_limit(64 * 1024)
.with_heap_size(64 * 1024);
assert_eq!(config.priority_fee, Some(1000));
assert_eq!(config.compute_unit_limit, Some(200_000));
assert_eq!(config.loaded_accounts_data_size_limit, Some(64 * 1024));
assert_eq!(config.heap_size, Some(64 * 1024));
}
}