你在合约里调用另一个合约时,可以把想转手的Gas数额写进CALL指令。但这条指令从不承诺足额送达——它把那个数字当愿望清单:手头只剩三分之一,也照给三分之一,调用照样开始。EIP-1930在2019年4月由Ronan Sandford提出,想把愿望变成合同:说给多少就必须给到,给不出就当前调用直接revert,而不是硬着头皮少给。提案停在Stagnant,问题本身却一直活在每一笔带Gas参数的调用里。
现行语义的两层折扣
调用现场拿到的Gas先要过两道坎:先扣掉这次调用本身的费用,再受63/64规则裁剪——按EIP-150,子调用最多只能拿到剩余额度的六十三分之六十四,留一点余粮给父上下文善后。也就是说,CALL参数哪怕写了天文字数,实际送达也是父级余额的折扣价。对大多数应用这无所谓,Gas本来就是尽力而为的共享资源。但对需要精确预算的场景,尽力而为是灾难源头:转发交易的应用里,签名者签的是带Gas承诺的调用,执行层给多给少全看现场余额;还有用静态调用探测接口支持的场合,Gas不够会让探测结果与真实行为悄悄分叉。
提案给的两种实现路径
方案A:给CALL、DELEGATECALL、STATICCALL各加一条严格变体指令,规则直白——若点Call时的剩余Gas不足指定值(考虑63/64扣减),当前调用revert。干净但要多三条操作码。方案B更取巧:不加指令,借一个没人用过的数值区间当暗号——Gas参数最高位为1时,低63位按严格语义解释;最高位为0时保持旧行为。作者的依据是现实里不会有编译器写大于等于二的六十三次方的Gas值,Solidity的惯例是直接把剩余Gas整个传下去,成本更低。两种方案最终都没进任何升级。
沿用的模糊带
2019年没有采纳它的原因今天仍然成立:严格语义与Gas市场的基本假设摩擦——Gas是区块级的共享资源,强制足额会让某些合法交易因现场余额永远无法被打包,可组合性的灵活性正是靠宽松语义撑着的。于是这条模糊带留到了现在:合约作者能依赖的共识保证只有六十三分之六十四,写死了指定值不等于到手值的教训,被一代代编译器和协议文档反复标注。后来的Gas聚合、64/63规则的再审视等提案处理的是同一块地基,但1930式的足额承诺至今仍未上线,EVM对Gas的态度依旧是尽力而为。
两种失败方式的账
严格语义与宽松语义的分岔在失败时刻看得最清楚。宽松世界:父合约给子调用指定一万Gas,现场余额只够八千,子调用拿八千开始跑——跑到一半Gas耗尽,子调用内部可能已经改了存储、发了事件,这些半成品效果按各自的回滚规则处置,父合约拿到一个含义模糊的失败标志。父合约想安全处理,必须假设子调用留下了最坏状态,防御代码写到头秃。严格世界:同样场景,交易在点Call那一步整体revert,链上什么都没发生,失败干净得像没存在过。两种失败在事故报告里的成本天差地别:前者叫部分执行,后者的部分执行一词直接不存在。这就是为什么转发类应用对严格语义念念不忘——它们的安全模型建立在要么全成要么全无的假设上,宽松语义让假设在最需要它的时刻失效。
快速问答
问:revert比少给更重,为什么有人认为更公平? 答:失败的确定性好于半执行的歧义——前者整笔回滚不留痕,后者可能在子调用里留下已完成的部分效应。 问:63/64规则和这个提案谁改谁? 答:提案不改该规则,严格语义在其上叠加:指定值必须小于等于剩余额度才能成交。 问:Solidity现在会踩这个坑吗? 答:常规写法传的是剩余Gas变量,天然避开指定值大于余额的情况;风险集中在手填常数的场景。
风险提示:本文描述技术提案,不构成任何投资建议。Gas计价规则以EIP原文与现行规范为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。