Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions interface/src/emulated_u128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use core::cmp::Ordering;

#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub struct U128 {
pub hi: u64,
pub lo: u64,
}

impl U128 {
pub const ZERO: Self = Self { hi: 0, lo: 0 };
pub const MAX: Self = Self {
hi: u64::MAX,
lo: u64::MAX,
};

pub const fn from_u64(x: u64) -> Self {
Self { hi: 0, lo: x }
}

pub const fn is_zero(self) -> bool {
self.hi == 0 && self.lo == 0
}

pub fn mul_u64(lhs: u64, rhs: u64) -> Self {
mul_u64_wide(lhs, rhs)
}

pub fn checked_mul_u64(self, rhs: u64) -> Option<Self> {
if rhs == 0 || self.is_zero() {
return Some(Self::ZERO);
}

// self * rhs = (self.lo * rhs) + (self.hi * rhs) << 64
let lo_prod = mul_u64_wide(self.lo, rhs); // 128-bit
let hi_prod = mul_u64_wide(self.hi, rhs); // 128-bit

// Shifting hi_prod left by 64 would discard hi_prod.hi beyond 128 -> overflow
if hi_prod.hi != 0 {
return None;
}

// new_hi = lo_prod.hi + hi_prod.lo
// overflow => exceed 128 bits
let (new_hi, overflow) = lo_prod.hi.overflowing_add(hi_prod.lo);
if overflow {
return None;
}

Some(Self {
hi: new_hi,
lo: lo_prod.lo,
})
}

pub fn saturating_mul_u64(self, rhs: u64) -> Self {
self.checked_mul_u64(rhs).unwrap_or(Self::MAX)
}

/// Some magic copied from the internet
pub fn div_floor_u64_clamped(numer: Self, denom: Self, clamp: u64) -> u64 {
if clamp == 0 || numer.is_zero() || denom.is_zero() {
return 0;
}
if numer < denom {
return 0;
}

if numer.hi == 0 && denom.hi == 0 {
return core::cmp::min(numer.lo / denom.lo, clamp);
}

// Fast path: if denom*clamp <= numer, clamp
if let Some(prod) = denom.checked_mul_u64(clamp) {
if prod <= numer {
return clamp;
}
}

let mut lo: u64 = 0;
let mut hi: u64 = clamp;

for _ in 0..64 {
if lo == hi {
break;
}

let diff = hi - lo;
let mid = lo + (diff / 2) + (diff & 1);

let ok = match denom.checked_mul_u64(mid) {
Some(prod) => prod <= numer,
None => false,
};

if ok {
lo = mid;
} else {
hi = mid - 1;
}
}

lo
}
}

impl Ord for U128 {
fn cmp(&self, other: &Self) -> Ordering {
match self.hi.cmp(&other.hi) {
Ordering::Equal => self.lo.cmp(&other.lo),
ord => ord,
}
}
}

impl PartialOrd for U128 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

/// In order to multiply a u64*u64 you need to use 32-bit halves
fn mul_u64_wide(a: u64, b: u64) -> U128 {
const MASK32: u64 = 0xFFFF_FFFF;

let a0 = a & MASK32;
let a1 = a >> 32;
let b0 = b & MASK32;
let b1 = b >> 32;

let w0 = a0 * b0; // 64-bit
let t = a1 * b0 + (w0 >> 32); // fits in u64
let w1 = t & MASK32;
let w2 = t >> 32;

let t = a0 * b1 + w1; // fits in u64
let lo = (t << 32) | (w0 & MASK32);
let hi = a1 * b1 + w2 + (t >> 32);

U128 { hi, lo }
}
1 change: 1 addition & 0 deletions interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#[allow(deprecated)]
pub mod config;
mod emulated_u128;
pub mod error;
pub mod instruction;
pub mod stake_flags;
Expand Down
69 changes: 61 additions & 8 deletions interface/src/warmup_cooldown_allowance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use {crate::stake_history::StakeHistoryEntry, solana_clock::Epoch};
use {
crate::{emulated_u128::U128, stake_history::StakeHistoryEntry},
solana_clock::Epoch,
};

pub const BASIS_POINTS_PER_UNIT: u64 = 10_000;
pub const ORIGINAL_WARMUP_COOLDOWN_RATE_BPS: u64 = 2_500; // 25%
Expand Down Expand Up @@ -79,17 +82,16 @@ fn rate_limited_stake_change(
// If the multiplication would overflow, we saturate to u128::MAX. This ensures
// that even in extreme edge cases, the rate-limiting invariant is maintained
// (fail-safe) rather than bypassing rate limits entirely (fail-open).
let numerator = (account_portion as u128)
.saturating_mul(cluster_effective as u128)
.saturating_mul(rate_bps as u128);
let denominator = (cluster_portion as u128).saturating_mul(BASIS_POINTS_PER_UNIT as u128);
let numerator = U128::from_u64(account_portion)
.saturating_mul_u64(cluster_effective)
.saturating_mul_u64(rate_bps);

let denominator = U128::mul_u64(cluster_portion, BASIS_POINTS_PER_UNIT);

// Safe unwrap as denominator cannot be zero due to early return guards above
let delta = numerator.checked_div(denominator).unwrap();
// The calculated delta can be larger than `account_portion` if the network's stake change
// allowance is greater than the total stake waiting to change. In this case, the account's
// entire portion is allowed to change.
delta.min(account_portion as u128) as u64
U128::div_floor_u64_clamped(numerator, denominator, account_portion)
}

#[cfg(test)]
Expand Down Expand Up @@ -382,9 +384,60 @@ mod test {
(weight * newly_effective_cluster_stake) as u64
}

// Integer math implementation using native `u128`.
// Kept in tests as an oracle to ensure behavior is unchanged.
fn rate_limited_stake_change_native_u128(
epoch: Epoch,
account_portion: u64,
cluster_portion: u64,
cluster_effective: u64,
new_rate_activation_epoch: Option<Epoch>,
) -> u64 {
if account_portion == 0 || cluster_portion == 0 || cluster_effective == 0 {
return 0;
}

let rate_bps = warmup_cooldown_rate_bps(epoch, new_rate_activation_epoch);

let numerator = (account_portion as u128)
.saturating_mul(cluster_effective as u128)
.saturating_mul(rate_bps as u128);
let denominator = (cluster_portion as u128).saturating_mul(BASIS_POINTS_PER_UNIT as u128);

let delta = numerator.checked_div(denominator).unwrap();
delta.min(account_portion as u128) as u64
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(10_000))]

#[test]
fn rate_limited_change_matches_native_u128(
account_portion in 0u64..=u64::MAX,
cluster_portion in 0u64..=u64::MAX,
cluster_effective in 0u64..=u64::MAX,
current_epoch in 0u64..=2000,
new_rate_activation_epoch_option in prop::option::of(0u64..=2000),
) {
let new_impl = rate_limited_stake_change(
current_epoch,
account_portion,
cluster_portion,
cluster_effective,
new_rate_activation_epoch_option,
);

let native_u128 = rate_limited_stake_change_native_u128(
current_epoch,
account_portion,
cluster_portion,
cluster_effective,
new_rate_activation_epoch_option,
);

prop_assert_eq!(new_impl, native_u128);
}

#[test]
fn rate_limited_change_consistent_with_legacy(
account_portion in 0u64..=u64::MAX,
Expand Down
Loading