multiversx-defi-math
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMultiversX 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;| Level | Decimals | When 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.0 | Token amounts, prices, share ratios |
| RAY (27) | 1e27 = 1.0 | Interest 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 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中,这意味着协议会缓慢流失价值,攻击者可以利用小额粉尘存款进行剥削。
ManagedDecimalUnsigned 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 truncates):
rescalerust
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
}
}通过四舍五入在不同精度等级间转换(标准方法会截断):
rescalerust
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()框架内置方法:proportion()
proportion()The MultiversX framework provides for percentage math. This is the preferred approach:
BigUint::proportion(part, total)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
舍入攻击向量
| Attack | Mitigation |
|---|---|
| Dust deposits to steal rounding | Half-up rounding on all scaled operations |
| Repeated small operations to drain value | Minimum amounts + half-up on indices |
| Precision loss across conversions | Use highest needed precision for intermediate math |
| Exploiting truncation in fee calculations | Always 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 divisionrust
// 错误——截断会导致协议损失价值
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_upat RAY precisiondiv_half_up - 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手续费搭配向上取整除法