#fee #rem

sanctum-fee-ratio

Abstractions over fees applied as ratios to u64 quantities

1 stable release

new 1.0.0 Mar 30, 2025

#14 in #rem

Custom license

25KB
412 lines

sanctum-fee-ratio

Abstractions over fees applied as ratios to u64 quantities.

Rationale

Token amounts on Solana are most commonly u64s. A very common operation is applying a fee to a token amount. These fees are usually calculated by multiplying the token amount with a ratio <=1.0. The remaining amount after substracting this product is the amount after fees. This library seeks to provide generalized code that can be reused across multiple such contexts.

Example Usage

Fee Application

use sanctum_fee_ratio::{Fee, ratio::{Ceil, Ratio}};

type FeeCeil = Fee<Ceil<Ratio<u16, u16>>>;
struct BpsFeeCeil(FeeCeil);

impl BpsFeeCeil {
    pub fn new(bps: u16) -> Option<Self> {
        FeeCeil::new(Ratio { n: bps, d: 10_000 }).map(Self)
    }
}

let four_bps_fee = BpsFeeCeil::new(4).unwrap();
let bef_fee = 1_000_000_001;
let aft_fee = four_bps_fee.0.apply(bef_fee).unwrap();

assert_eq!(aft_fee.rem(), 999_600_000);
assert_eq!(aft_fee.fee(), 400_001);
// bef_fee() performs rem() + fee() to get original amount before fee
assert_eq!(aft_fee.bef_fee(), bef_fee);

Fee Reversal

Uses sanctum_u64_ratio's reverse() functionality to obtain a range of amount before fees from quantities after fees.

From rem

Calling .apply() to any number in the returned range is guaranteed to return an AftFee with the same rem(), but it might not have the same fee()

use sanctum_fee_ratio::{Fee, ratio::{Ceil, Ratio}};

type FeeCeil = Fee<Ceil<Ratio<u64, u64>>>;

let fee = FeeCeil::new(Ratio { n: 1, d: 10 }).unwrap();
let bef_fee = 1_000_000_001;
let aft_fee = fee.apply(bef_fee).unwrap();
assert_eq!(aft_fee.rem(), 900_000_000);
assert_eq!(aft_fee.fee(), 100_000_001);

let range = fee.reverse_from_rem(aft_fee.rem()).unwrap();

// min results in a different fee, but max doesnt

let min = fee.apply(*range.start()).unwrap();
assert_eq!(min.rem(), aft_fee.rem());
assert_eq!(min.fee(), 100_000_000);

let max = fee.apply(*range.end()).unwrap();
assert_eq!(max.rem(), aft_fee.rem());
assert_eq!(max.fee(), 100_000_001);

From fee

Calling .apply() to any number in the returned range is guaranteed to return an AftFee with the same fee(), but it might not have the same rem()

use sanctum_fee_ratio::{Fee, ratio::{Floor, Ratio}};

type FeeFloor = Fee<Floor<Ratio<u64, u64>>>;

let fee = FeeFloor::new(Ratio { n: 1, d: 10 }).unwrap();
let bef_fee = 1_000_000_001;
let aft_fee = fee.apply(bef_fee).unwrap();
assert_eq!(aft_fee.rem(), 900_000_001);
assert_eq!(aft_fee.fee(), 100_000_000);

let range = fee.reverse_from_fee(aft_fee.fee()).unwrap();

// both min and max result in a different `rem()`

let min = fee.apply(*range.start()).unwrap();
assert_eq!(min.rem(), 900_000_000);
assert_eq!(min.fee(), aft_fee.fee());

let max = fee.apply(*range.end()).unwrap();
assert_eq!(max.rem(), 900_000_009);
assert_eq!(max.fee(), aft_fee.fee());

Dependencies