solana_fee_calculator/
lib.rs#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![allow(clippy::arithmetic_side_effects)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use log::*;
#[cfg(feature = "frozen-abi")]
extern crate std;
#[repr(C)]
#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct FeeCalculator {
pub lamports_per_signature: u64,
}
impl FeeCalculator {
pub fn new(lamports_per_signature: u64) -> Self {
Self {
lamports_per_signature,
}
}
}
#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct FeeRateGovernor {
#[cfg_attr(feature = "serde", serde(skip))]
pub lamports_per_signature: u64,
pub target_lamports_per_signature: u64,
pub target_signatures_per_slot: u64,
pub min_lamports_per_signature: u64,
pub max_lamports_per_signature: u64,
pub burn_percent: u8,
}
pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000;
const DEFAULT_MS_PER_SLOT: u64 = 400;
#[cfg(test)]
static_assertions::const_assert_eq!(DEFAULT_MS_PER_SLOT, solana_clock::DEFAULT_MS_PER_SLOT);
pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
pub const DEFAULT_BURN_PERCENT: u8 = 50;
impl Default for FeeRateGovernor {
fn default() -> Self {
Self {
lamports_per_signature: 0,
target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
min_lamports_per_signature: 0,
max_lamports_per_signature: 0,
burn_percent: DEFAULT_BURN_PERCENT,
}
}
}
impl FeeRateGovernor {
pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self {
let base_fee_rate_governor = Self {
target_lamports_per_signature,
lamports_per_signature: target_lamports_per_signature,
target_signatures_per_slot,
..FeeRateGovernor::default()
};
Self::new_derived(&base_fee_rate_governor, 0)
}
pub fn new_derived(
base_fee_rate_governor: &FeeRateGovernor,
latest_signatures_per_slot: u64,
) -> Self {
let mut me = base_fee_rate_governor.clone();
if me.target_signatures_per_slot > 0 {
me.min_lamports_per_signature = core::cmp::max(1, me.target_lamports_per_signature / 2);
me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
let desired_lamports_per_signature =
me.max_lamports_per_signature
.min(me.min_lamports_per_signature.max(
me.target_lamports_per_signature
* core::cmp::min(latest_signatures_per_slot, u32::MAX as u64)
/ me.target_signatures_per_slot,
));
trace!(
"desired_lamports_per_signature: {}",
desired_lamports_per_signature
);
let gap = desired_lamports_per_signature as i64
- base_fee_rate_governor.lamports_per_signature as i64;
if gap == 0 {
me.lamports_per_signature = desired_lamports_per_signature;
} else {
let gap_adjust =
core::cmp::max(1, me.target_lamports_per_signature / 20) as i64 * gap.signum();
trace!(
"lamports_per_signature gap is {}, adjusting by {}",
gap,
gap_adjust
);
me.lamports_per_signature =
me.max_lamports_per_signature
.min(me.min_lamports_per_signature.max(
(base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust)
as u64,
));
}
} else {
me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature;
me.min_lamports_per_signature = me.target_lamports_per_signature;
me.max_lamports_per_signature = me.target_lamports_per_signature;
}
debug!(
"new_derived(): lamports_per_signature: {}",
me.lamports_per_signature
);
me
}
pub fn clone_with_lamports_per_signature(&self, lamports_per_signature: u64) -> Self {
Self {
lamports_per_signature,
..*self
}
}
pub fn burn(&self, fees: u64) -> (u64, u64) {
let burned = fees * u64::from(self.burn_percent) / 100;
(fees - burned, burned)
}
pub fn create_fee_calculator(&self) -> FeeCalculator {
FeeCalculator::new(self.lamports_per_signature)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_rate_governor_burn() {
let mut fee_rate_governor = FeeRateGovernor::default();
assert_eq!(fee_rate_governor.burn(2), (1, 1));
fee_rate_governor.burn_percent = 0;
assert_eq!(fee_rate_governor.burn(2), (2, 0));
fee_rate_governor.burn_percent = 100;
assert_eq!(fee_rate_governor.burn(2), (0, 2));
}
#[test]
fn test_fee_rate_governor_derived_default() {
solana_logger::setup();
let f0 = FeeRateGovernor::default();
assert_eq!(
f0.target_signatures_per_slot,
DEFAULT_TARGET_SIGNATURES_PER_SLOT
);
assert_eq!(
f0.target_lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
);
assert_eq!(f0.lamports_per_signature, 0);
let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
assert_eq!(
f1.target_signatures_per_slot,
DEFAULT_TARGET_SIGNATURES_PER_SLOT
);
assert_eq!(
f1.target_lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
);
assert_eq!(
f1.lamports_per_signature,
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2
); }
#[test]
fn test_fee_rate_governor_derived_adjust() {
solana_logger::setup();
let mut f = FeeRateGovernor {
target_lamports_per_signature: 100,
target_signatures_per_slot: 100,
..FeeRateGovernor::default()
};
f = FeeRateGovernor::new_derived(&f, 0);
let mut count = 0;
loop {
let last_lamports_per_signature = f.lamports_per_signature;
f = FeeRateGovernor::new_derived(&f, u64::MAX);
info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
if f.lamports_per_signature == last_lamports_per_signature {
break;
}
assert!(count < 1000);
count += 1;
}
let mut count = 0;
loop {
let last_lamports_per_signature = f.lamports_per_signature;
f = FeeRateGovernor::new_derived(&f, 0);
info!(
"[down] f.lamports_per_signature={}",
f.lamports_per_signature
);
if f.lamports_per_signature == last_lamports_per_signature {
break;
}
assert!(count < 1000);
count += 1;
}
let mut count = 0;
while f.lamports_per_signature != f.target_lamports_per_signature {
f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
info!(
"[target] f.lamports_per_signature={}",
f.lamports_per_signature
);
assert!(count < 100);
count += 1;
}
}
}