multiversx-defi-math

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MultiversX DeFi Math Patterns

MultiversX DeFi 数学模式

Reusable mathematical patterns for financial safety in MultiversX smart contracts.
MultiversX智能合约中保障金融安全的可复用数学模式。

Precision Management

精度管理

Choosing Precision Levels

选择精度等级

rust
pub const BPS_PRECISION: usize = 4;       // Basis points: 10,000 = 100%
pub const BPS: u64 = 10_000;

pub const PPM_PRECISION: usize = 6;       // Parts per million: 1,000,000 = 100%
pub const PPM: u64 = 1_000_000;

pub const WAD_PRECISION: usize = 18;      // Standard token decimals: 1e18 = 1.0
pub const WAD: u128 = 1_000_000_000_000_000_000;

pub const RAY_PRECISION: usize = 27;      // High-precision: 1e27 = 1.0
pub const RAY: u128 = 1_000_000_000_000_000_000_000_000_000;
LevelDecimalsWhen to Use
BPS (4)10,000 = 100%Fees, simple percentages, reserve factors
PPM (6)1,000,000 = 100%Fine-grained percentages, partial withdrawals
WAD (18)1e18 = 1.0Token amounts, prices, share ratios
RAY (27)1e27 = 1.0Interest indices, compounding rates, any math needing minimal precision loss
Rule of thumb: Use the lowest precision that avoids rounding errors in your domain. For intermediate calculations, always use a higher precision than the final result.
rust
pub const BPS_PRECISION: usize = 4;       // 基点:10,000 = 100%
pub const BPS: u64 = 10_000;

pub const PPM_PRECISION: usize = 6;       // 百万分比:1,000,000 = 100%
pub const PPM: u64 = 1_000_000;

pub const WAD_PRECISION: usize = 18;      // 标准通证小数位数:1e18 = 1.0
pub const WAD: u128 = 1_000_000_000_000_000_000;

pub const RAY_PRECISION: usize = 27;      // 高精度:1e27 = 1.0
pub const RAY: u128 = 1_000_000_000_000_000_000_000_000_000;
等级小数换算适用场景
BPS (4)10,000 = 100%手续费、简单百分比、准备金率
PPM (6)1,000,000 = 100%细粒度百分比、部分提取
WAD (18)1e18 = 1.0通证数量、价格、份额比例
RAY (27)1e27 = 1.0利率指数、复利计算、任何需要最小精度损失的数学运算
经验法则:使用能避免业务场景中舍入误差的最低精度。对于中间计算,始终使用比最终结果更高的精度。

Rounding Strategies

舍入策略

Why Half-Up Rounding?

为什么选择四舍五入?

Standard
ManagedDecimal
operations truncate (round toward zero). Over many operations, this causes systematic value loss. In DeFi, this means the protocol slowly leaks value, and attackers can exploit it with dust deposits.
标准的
ManagedDecimal
操作会截断(向零舍入)。多次操作后,这会导致系统性的价值损失。在DeFi中,这意味着协议会缓慢流失价值,攻击者可以利用小额粉尘存款进行剥削。

Unsigned Half-Up Multiplication

无符号数四舍五入乘法

rust
fn mul_half_up(
    &self,
    a: &ManagedDecimal<Self::Api, NumDecimals>,
    b: &ManagedDecimal<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let product = scaled_a.into_raw_units() * scaled_b.into_raw_units();
    let scaled = BigUint::from(10u64).pow(precision as u32);
    let half_scaled = &scaled / &BigUint::from(2u64);
    let rounded_product = (product + half_scaled) / scaled;
    self.to_decimal(rounded_product, precision)
}
rust
fn mul_half_up(
    &self,
    a: &ManagedDecimal<Self::Api, NumDecimals>,
    b: &ManagedDecimal<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let product = scaled_a.into_raw_units() * scaled_b.into_raw_units();
    let scaled = BigUint::from(10u64).pow(precision as u32);
    let half_scaled = &scaled / &BigUint::from(2u64);
    let rounded_product = (product + half_scaled) / scaled;
    self.to_decimal(rounded_product, precision)
}

Unsigned Half-Up Division

无符号数四舍五入除法

rust
fn div_half_up(
    &self,
    a: &ManagedDecimal<Self::Api, NumDecimals>,
    b: &ManagedDecimal<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let scaled = BigUint::from(10u64).pow(precision as u32);
    let numerator = scaled_a.into_raw_units() * &scaled;
    let denominator = scaled_b.into_raw_units();
    let half_denominator = denominator / &BigUint::from(2u64);
    let rounded_quotient = (numerator + half_denominator) / denominator;
    self.to_decimal(rounded_quotient, precision)
}
rust
fn div_half_up(
    &self,
    a: &ManagedDecimal<Self::Api, NumDecimals>,
    b: &ManagedDecimal<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let scaled = BigUint::from(10u64).pow(precision as u32);
    let numerator = scaled_a.into_raw_units() * &scaled;
    let denominator = scaled_b.into_raw_units();
    let half_denominator = denominator / &BigUint::from(2u64);
    let rounded_quotient = (numerator + half_denominator) / denominator;
    self.to_decimal(rounded_quotient, precision)
}

Signed Away-From-Zero Rounding

有符号数向零反方向舍入

For signed values (e.g., PnL, price deltas), round AWAY from zero to prevent systematic bias:
rust
fn mul_half_up_signed(
    &self,
    a: &ManagedDecimalSigned<Self::Api, NumDecimals>,
    b: &ManagedDecimalSigned<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimalSigned<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let product = scaled_a.into_raw_units() * scaled_b.into_raw_units();
    let scaled = BigInt::from(10i64).pow(precision as u32);
    let half_scaled = &scaled / &BigInt::from(2i64);

    let rounded_product = if product.sign() == Sign::Minus {
        (product - half_scaled) / scaled  // More negative
    } else {
        (product + half_scaled) / scaled  // More positive
    };
    ManagedDecimalSigned::from_raw_units(rounded_product, precision)
}
对于有符号值(如盈亏、价格变动),向远离零的方向舍入以避免系统性偏差:
rust
fn mul_half_up_signed(
    &self,
    a: &ManagedDecimalSigned<Self::Api, NumDecimals>,
    b: &ManagedDecimalSigned<Self::Api, NumDecimals>,
    precision: NumDecimals,
) -> ManagedDecimalSigned<Self::Api, NumDecimals> {
    let scaled_a = a.rescale(precision);
    let scaled_b = b.rescale(precision);
    let product = scaled_a.into_raw_units() * scaled_b.into_raw_units();
    let scaled = BigInt::from(10i64).pow(precision as u32);
    let half_scaled = &scaled / &BigInt::from(2i64);

    let rounded_product = if product.sign() == Sign::Minus {
        (product - half_scaled) / scaled  // 更负
    } else {
        (product + half_scaled) / scaled  // 更正
    };
    ManagedDecimalSigned::from_raw_units(rounded_product, precision)
}

Safe Rescaling

安全重缩放

Converting between precision levels with half-up rounding (standard
rescale
truncates):
rust
fn rescale_half_up(
    &self,
    value: &ManagedDecimal<Self::Api, NumDecimals>,
    new_precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let old_precision = value.scale();
    match new_precision.cmp(&old_precision) {
        Ordering::Equal => value.clone(),
        Ordering::Less => {
            // Downscaling — rounding matters
            let precision_diff = old_precision - new_precision;
            let factor = BigUint::from(10u64).pow(precision_diff as u32);
            let half_factor = &factor / 2u64;
            let rounded = (value.into_raw_units() + &half_factor) / factor;
            ManagedDecimal::from_raw_units(rounded, new_precision)
        },
        Ordering::Greater => value.rescale(new_precision), // Upscaling — no rounding needed
    }
}
通过四舍五入在不同精度等级间转换(标准
rescale
方法会截断):
rust
fn rescale_half_up(
    &self,
    value: &ManagedDecimal<Self::Api, NumDecimals>,
    new_precision: NumDecimals,
) -> ManagedDecimal<Self::Api, NumDecimals> {
    let old_precision = value.scale();
    match new_precision.cmp(&old_precision) {
        Ordering::Equal => value.clone(),
        Ordering::Less => {
            // 降精度——舍入很重要
            let precision_diff = old_precision - new_precision;
            let factor = BigUint::from(10u64).pow(precision_diff as u32);
            let half_factor = &factor / 2u64;
            let rounded = (value.into_raw_units() + &half_factor) / factor;
            ManagedDecimal::from_raw_units(rounded, new_precision)
        },
        Ordering::Greater => value.rescale(new_precision), // 升精度——无需舍入
    }
}

Percentage Calculations

百分比计算

Framework Built-in:
proportion()

框架内置方法:
proportion()

The MultiversX framework provides
BigUint::proportion(part, total)
for percentage math. This is the preferred approach:
rust
// BigUint::proportion(numerator, denominator) — built-in framework method
let fee = amount.proportion(fee_percent, PERCENT_BASE_POINTS);
Common base point constants used in production:
rust
pub const PERCENT_BASE_POINTS: u64 = 100_000;  // 100% = 100_000 (5-digit precision)
pub const BPS: u64 = 10_000;                     // 100% = 10_000 (basis points)
pub const PPM: u64 = 1_000_000;                  // 100% = 1_000_000 (parts per million)
MultiversX框架提供
BigUint::proportion(part, total)
用于百分比运算,这是推荐的实现方式:
rust
// BigUint::proportion(numerator, denominator) — 框架内置方法
let fee = amount.proportion(fee_percent, PERCENT_BASE_POINTS);
生产环境中常用的基点常量:
rust
pub const PERCENT_BASE_POINTS: u64 = 100_000;  // 100% = 100_000(5位精度)
pub const BPS: u64 = 10_000;                     // 100% = 10_000(基点)
pub const PPM: u64 = 1_000_000;                  // 100% = 1_000_000(百万分比)

Using proportion() for Fees

使用proportion()计算手续费

rust
multiversx_sc::imports!();

pub const PERCENT_BASE_POINTS: u64 = 100_000;

/// Apply a percentage fee using framework's proportion()
fn calculate_fee(&self, amount: &BigUint, fee_percent: u64) -> BigUint {
    amount.proportion(fee_percent, PERCENT_BASE_POINTS)
}

/// Amount after deducting fee
fn amount_after_fee(&self, amount: &BigUint, fee_percent: u64) -> BigUint {
    amount - &amount.proportion(fee_percent, PERCENT_BASE_POINTS)
}
rust
multiversx_sc::imports!();

pub const PERCENT_BASE_POINTS: u64 = 100_000;

/// 使用框架的proportion()方法计算百分比手续费
fn calculate_fee(&self, amount: &BigUint, fee_percent: u64) -> BigUint {
    amount.proportion(fee_percent, PERCENT_BASE_POINTS)
}

/// 扣除手续费后的金额
fn amount_after_fee(&self, amount: &BigUint, fee_percent: u64) -> BigUint {
    amount - &amount.proportion(fee_percent, PERCENT_BASE_POINTS)
}

BPS (Basis Points) — Manual

BPS(基点)——手动实现

When you need explicit control over the calculation:
rust
pub fn apply_bps(amount: &BigUint, bps: u64) -> BigUint {
    require!(bps <= 10_000, "BPS exceeds 100%");
    (amount * bps) / 10_000u64
}
当需要对计算进行显式控制时:
rust
pub fn apply_bps(amount: &BigUint, bps: u64) -> BigUint {
    require!(bps <= 10_000, "BPS超过100%");
    (amount * bps) / 10_000u64
}

PPM (Parts Per Million) — Manual

PPM(百万分比)——手动实现

rust
pub fn apply_ppm(amount: &BigUint, ppm: u32) -> BigUint {
    require!(ppm <= 1_000_000, "PPM exceeds 100%");
    (amount * ppm) / 1_000_000u64
}
rust
pub fn apply_ppm(amount: &BigUint, ppm: u32) -> BigUint {
    require!(ppm <= 1_000_000, "PPM超过100%");
    (amount * ppm) / 1_000_000u64
}

Common Math Module Template

通用数学模块模板

rust
#![no_std]
multiversx_sc::imports!();

#[multiversx_sc::module]
pub trait SharedMathModule {
    fn mul_half_up(
        &self,
        a: &ManagedDecimal<Self::Api, NumDecimals>,
        b: &ManagedDecimal<Self::Api, NumDecimals>,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        // ... (implementation above)
    }

    fn div_half_up(
        &self,
        a: &ManagedDecimal<Self::Api, NumDecimals>,
        b: &ManagedDecimal<Self::Api, NumDecimals>,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        // ... (implementation above)
    }

    fn to_decimal(
        &self,
        value: BigUint,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        ManagedDecimal::from_raw_units(value, precision)
    }

    fn min(
        &self,
        a: ManagedDecimal<Self::Api, NumDecimals>,
        b: ManagedDecimal<Self::Api, NumDecimals>,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        if a < b { a } else { b }
    }
}
rust
#![no_std]
multiversx_sc::imports!();

#[multiversx_sc::module]
pub trait SharedMathModule {
    fn mul_half_up(
        &self,
        a: &ManagedDecimal<Self::Api, NumDecimals>,
        b: &ManagedDecimal<Self::Api, NumDecimals>,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        // ...(上述实现代码)
    }

    fn div_half_up(
        &self,
        a: &ManagedDecimal<Self::Api, NumDecimals>,
        b: &ManagedDecimal<Self::Api, NumDecimals>,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        // ...(上述实现代码)
    }

    fn to_decimal(
        &self,
        value: BigUint,
        precision: NumDecimals,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        ManagedDecimal::from_raw_units(value, precision)
    }

    fn min(
        &self,
        a: ManagedDecimal<Self::Api, NumDecimals>,
        b: ManagedDecimal<Self::Api, NumDecimals>,
    ) -> ManagedDecimal<Self::Api, NumDecimals> {
        if a < b { a } else { b }
    }
}

Rounding Attack Vectors

舍入攻击向量

AttackMitigation
Dust deposits to steal roundingHalf-up rounding on all scaled operations
Repeated small operations to drain valueMinimum amounts + half-up on indices
Precision loss across conversionsUse highest needed precision for intermediate math
Exploiting truncation in fee calculationsAlways round fees UP (in protocol's favor)
攻击方式缓解措施
利用粉尘存款窃取舍入收益所有缩放操作均使用四舍五入
通过重复小额操作消耗价值设置最小金额 + 对指数使用四舍五入
转换过程中精度损失中间运算使用所需的最高精度
利用手续费计算中的截断漏洞手续费始终向上舍入(对协议有利)

Bad/Good Examples

反例/正例

Bad

反例

rust
// DON'T: Divide before multiply — loses precision
let shares = (&amount / &total_supply) * &total_shares; // Truncates to 0 for small amounts!
rust
// 错误:先除后乘——损失精度
let shares = (&amount / &total_supply) * &total_shares; // 小额金额会被截断为0!

Good

正例

rust
// DO: Multiply first, then divide to preserve precision
let shares = (&amount * &total_shares) / &total_supply;
rust
// 正确:先乘后除以保留精度
let shares = (&amount * &total_shares) / &total_supply;

Bad

反例

rust
// DON'T: Hardcode decimal assumptions — tokens can have 0-18 decimals
let one_token = BigUint::from(10u64).pow(18); // Assumes 18 decimals!
rust
// 错误:硬编码小数位数假设——通证的小数位数可以是0-18位
let one_token = BigUint::from(10u64).pow(18); // 假设是18位小数!

Good

正例

rust
// DO: Fetch decimals from token properties or pass as parameter
let one_token = BigUint::from(10u64).pow(token_decimals as u32);
rust
// 正确:从通证属性中获取小数位数或作为参数传入
let one_token = BigUint::from(10u64).pow(token_decimals as u32);

Anti-Patterns

反模式

1. Mixing Precisions Without Rescaling

1. 未重缩放就混合不同精度

rust
// WRONG — BPS and RAY have different scales
let result = bps_value + ray_value;

// CORRECT — rescale first
let bps_as_ray = bps_value.rescale(RAY_PRECISION);
let result = bps_as_ray + ray_value;
rust
// 错误——BPS和RAY的精度不同
let result = bps_value + ray_value;

// 正确——先重缩放
let bps_as_ray = bps_value.rescale(RAY_PRECISION);
let result = bps_as_ray + ray_value;

2. Using Truncating Division for Fees

2. 对手续费使用截断除法

rust
// WRONG — truncation loses value for the protocol
let fee = amount / 100u64; // Truncates

// CORRECT — round up to favor protocol
let fee = (amount + 99u64) / 100u64; // Ceiling division
rust
// 错误——截断会导致协议损失价值
let fee = amount / 100u64; // 截断

// 正确——向上舍入以保障协议利益
let fee = (amount + 99u64) / 100u64; // 向上取整除法

3. Intermediate Results at Low Precision

3. 中间结果使用低精度

rust
// WRONG — BPS precision loses significant digits in intermediate calc
let ratio = self.div_half_up(&a, &b, BPS_PRECISION);
let result = self.mul_half_up(&ratio, &c, BPS_PRECISION);

// CORRECT — compute at RAY, downscale at the end
let ratio = self.div_half_up(&a, &b, RAY_PRECISION);
let result = self.mul_half_up(&ratio, &c, RAY_PRECISION);
let final_result = self.rescale_half_up(&result, BPS_PRECISION);
rust
// 错误——BPS精度会在中间计算中丢失有效数字
let ratio = self.div_half_up(&a, &b, BPS_PRECISION);
let result = self.mul_half_up(&ratio, &c, BPS_PRECISION);

// 正确——使用RAY精度计算,最后再降精度
let ratio = self.div_half_up(&a, &b, RAY_PRECISION);
let result = self.mul_half_up(&ratio, &c, RAY_PRECISION);
let final_result = self.rescale_half_up(&result, BPS_PRECISION);

Domain Applications

领域应用

These generic patterns are used differently across DeFi domains:
  • Lending: Interest rate models, compound interest (Taylor expansion), utilization ratios — all built on
    mul_half_up
    /
    div_half_up
    at RAY precision
  • DEX/AMM: Price impact calculations, LP share math — WAD precision with half-up rounding
  • Staking: Reward distribution, share-to-token ratios — RAY indices with safe rescaling
  • Vaults: Fee calculations, yield accrual — BPS fees with ceiling division
这些通用模式在不同DeFi领域的应用方式有所不同:
  • 借贷:利率模型、复利计算(泰勒展开)、利用率比例——均基于RAY精度的
    mul_half_up
    /
    div_half_up
    实现
  • 去中心化交易所/自动做市商:价格影响计算、流动性提供者份额数学——WAD精度搭配四舍五入
  • 质押:奖励分配、份额与通证比例——RAY指数搭配安全重缩放
  • 金库:手续费计算、收益累积——BPS手续费搭配向上取整除法