去中心化金融合约里出现频率最高的一步算术,既不是加法也不是乘法,而是”先乘后除”:兑换率调整、利率累计、份额折算,全都是 x 乘 y 再除以 z。问题是EVM并没有这条指令,256位寄存器直接相乘会把高位截掉,精度就在这一刀里丢了。EIP-5000在2022年3月提出补上这块短板:新增一条 MULDIV 指令,按512位中间精度计算 x * y / z 再模2的256次方。提案目前是Stagnant状态,从未进入任何主网升级。
现成的替代品长什么样
在没有乘除指令的年代,Solidity等工具链用 mulmod 做底座实现全精度乘除:把乘积的高位和低位分开算、处理进位、再做长除法,整套下来大约要五十条指令,还没算栈操作的开销。对一个天天做汇率乘除的兑换合约来说,这是每一笔交易都在重复支付的冤枉钱。提案摘要里明确把”把五十步压成一步”列为首要动机。
规格里的门道
新指令操作码定为 0x1e,从栈上依次弹出 x、y、z 三个数。当 z 不为零时,结果取 (x * y / z) 模2的256次方,中间计算用512位精度,所以不会在中途丢高位。z 为零时是刻意设计的特殊分支:返回 (x * y) 除以2的256次方的商,也就是乘积的高256位。这个约定打破了EVM里”除零得零”的惯例,作者的解释是把它变成一个功能——配合普通的 mul(得低256位),一条指令对就能拿到完整的512位乘积,给密码学应用留了口子。指令成本定为8 Gas,与 addmod、mulmod 同档。作者还特意说明这条指令要能被”检查结果”和”不检查结果”两类算术场景使用,前者在溢出或除零时中止,后者按约定值继续。
为什么没往前走
EVM对加指令的空间一直非常吝啬:一次硬分叉塞进什么操作码,往往要看同期升级的打包优先级。乘除这类纯算力的改进,最终被证明可以走预编译甚至合约库的路子,优先级就排到了后面。EIP-5000与同期其他算术类提案一样进入了Stagnant,指令编号也因此从未被占用。
快速问答
问:现在合约里做乘除会丢精度吗?
答:取决于实现。编译器库的分步实现可以保住中间精度,只是要多付Gas;手写一句先乘后除则可能截断,这是代码质量问题,与协议无关。
问:z等于0为什么不算非法?
答:提案把 z=0 定义为取乘积高256位的便利写法,是特性不是错误;想要除零即回滚的调用者需要在调用前自行检查。
问:这条指令编号以后还能被别家用掉吗?
答:只要提案不复活,0x1e 编号仍空闲,但EVM操作码分配是严肃的共识决策,任何占用都要走完整EIP流程。
一次乘除的两种旅程
让读者对Gas差距有体感,可以对照两条路径。第一条是现行路线:合约要做 x 乘 y 除 z,编译器调进数学库,函数先判断会不会溢出、把乘积拆成高低两段、用 mulmod 找回高位、再走一遍长除法,全程五十条上下的指令逐条收摊,每条三到八Gas不等,合计轻松过几百Gas,还叠加库函数自己的栈帧开销。第二条是提案路线:一条 MULDIV,8Gas,出门。对一个每笔交易只做一两次乘除的合约,这差距可以忽略;但对复利结算、每区块重算份额价格的协议,这就是一年累下来都挂在用户账单上的固定税。提案没有改变兑换率算得对不对,它改变的是”算对”这件事的市场标价。
与近亲们的分岔口
EVM算术家族里已经有几位近亲,对照着看更清楚。ADDMOD 和 MULMOD 在2017年的拜占庭升级就随EIP-198入驻,专算加法与乘法的模运算;DIV 与 SDIV 是最朴素的256位除法,先除会把乘积高位截掉。MULDIV 想占的是四不像的正中间:先乘不截断、后除不丢精度、不带模数。同代的竞争思路还包括EIP-6690那条宽寄存器路线——与其加一条指令,不如把寄存器做宽。历史走向是两者都没赢:Solidity 0.8之后,社区用 unchecked 算术块加 mulmod 手搓出了性能足够的高精度乘除,“够用”又一次跑赢了”最优”。这条分岔口值得记住:EVM不缺聪明方案,缺的是把方案塞进硬分叉优先级的共识。
风险提示:本文讨论协议层技术提案,不构成投资建议;合约算术实现差异可能造成实际损失,重要计算请以经过审计的数学库为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。