name: multiversx-defi-math description: MultiversX 智能合约的金融数学模式 — 精度管理、半入舍入、安全缩放和百分比计算。在构建处理金融计算、费用、利率或代币数学的任何 DeFi 合约时使用。
MultiversX DeFi 数学模式
在 MultiversX 智能合约中用于金融安全的可重用数学模式。
精度管理
选择精度级别
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 | 利息指数、复利利率、任何需要最小精度损失的数学 |
经验法则:使用在您的领域中避免舍入错误的最低精度。对于中间计算,始终使用比最终结果更高的精度。
舍入策略
为什么使用半入舍入?
标准的 ManagedDecimal 操作会截断(向零舍入)。经过多次操作,这会导致系统性价值损失。在 DeFi 中,这意味着协议缓慢泄漏价值,攻击者可以利用微小存款进行攻击。
无符号半入乘法
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)
}
无符号半入除法
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)
}
有符号远离零舍入
对于有符号值(例如,损益、价格差),舍入远离零以防止系统性偏差:
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)
}
安全缩放
在精度级别之间转换时使用半入舍入(标准的 rescale 会截断):
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), // 向上缩放 — 无需舍入
}
}
百分比计算
框架内置:proportion()
MultiversX 框架提供 BigUint::proportion(part, total) 用于百分比数学。这是首选方法:
// BigUint::proportion(分子, 分母) — 内置框架方法
let fee = amount.proportion(fee_percent, PERCENT_BASE_POINTS);
生产中常用的基点常量:
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(百万分比)
使用 proportion() 计算费用
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(基点) — 手动
当需要显式控制计算时:
pub fn apply_bps(amount: &BigUint, bps: u64) -> BigUint {
require!(bps <= 10_000, "BPS 超过 100%");
(amount * bps) / 10_000u64
}
PPM(百万分比) — 手动
pub fn apply_ppm(amount: &BigUint, ppm: u32) -> BigUint {
require!(ppm <= 1_000_000, "PPM 超过 100%");
(amount * ppm) / 1_000_000u64
}
常见数学模块模板
#![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 }
}
}
舍入攻击向量
| 攻击 | 缓解措施 |
|---|---|
| 微小存款以窃取舍入 | 在所有缩放操作中使用半入舍入 |
| 重复小操作以耗尽价值 | 最小金额 + 指数上的半入 |
| 跨转换的精度损失 | 中间计算使用最高所需精度 |
| 利用费用计算中的截断 | 始终将费用向上舍入(有利于协议) |
坏/好示例
坏
// 不要:先除后乘 — 损失精度
let shares = (&amount / &total_supply) * &total_shares; // 对小金额截断为 0!
好
// 做:先乘后除以保存精度
let shares = (&amount * &total_shares) / &total_supply;
坏
// 不要:硬编码小数假设 — 代币可以有 0-18 位小数
let one_token = BigUint::from(10u64).pow(18); // 假设 18 位小数!
好
// 做:从代币属性获取小数或作为参数传递
let one_token = BigUint::from(10u64).pow(token_decimals as u32);
反模式
1. 混合精度而不缩放
// 错误 — 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. 使用截断除法计算费用
// 错误 — 截断损失协议价值
let fee = amount / 100u64; // 截断
// 正确 — 向上舍入以利于协议
let fee = (amount + 99u64) / 100u64; // 天花板除法
3. 中间结果在低精度
// 错误 — 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);
领域应用
这些通用模式在 DeFi 领域中不同使用:
- 借贷:利率模型、复利(泰勒展开)、利用率比率 — 全部基于 RAY 精度的
mul_half_up/div_half_up - DEX/AMM:价格影响计算、LP 份额数学 — WAD 精度带半入舍入
- 质押:奖励分配、份额到代币比率 — RAY 指数带安全缩放
- 金库:费用计算、收益累积 — BPS 费用带天花板除法