一笔提案最多装多少事:捆绑执行的原子性与失败半径 图 1
一笔提案最多装多少事:捆绑执行的原子性与失败半径 · 图 1

你大概见过这样的治理提案:标题写「参数更新」,点进详情却有六笔调用——两个利率曲线、三个上限、外加一次权限交接。治理社区把这种做法叫捆绑:把多项操作压进同一次执行。它省 gas、省流程,也改变了「失败」的形状。看懂捆绑执行的原子规则,你才能正确评估一票下去到底在批准什么。

机制层面,开源时间锁合约把一笔操作定义为目标地址、调用数据、价值组成的列表,列表里可以有多项;提案的身份由整个列表的哈希确定。执行时,列表内的各笔调用在同一个交易里依次发起,要么全部成功落账,要么任何一笔失败都让整包回滚,像没有发生过。没有「执行到一半停下」这种中间态。

原子性带来第一个后果:连坐。六项操作里最不起眼的那项,比如一个代币的精度换算调用,只要与链上状态发生冲突(比如目标合约被升级、调用目标被禁用),整包就执行失败,包括前五项本来完全正常的变更。这时协议通常面临两个选择:把问题项剔除重新提案,或者找出根因修复后重跑原包。无论哪条路,你都要再等一轮完整的投票与延迟周期。

第二个后果与监督粒度有关。捆绑包的取消与重排都以整包为单位,治理页面往往只展示一个哈希和一串原始调用数据。对普通持有人,这意味着读提案的成本被推高:你必须逐条展开每笔调用的目标合约与参数含义,而不能只看标题。一项被塞在长列表末尾、毫不起眼的权限转移,与五项温和的参数调整绑在一起投票,是治理设计里最值得警惕的形态。

实操清单可以这样定。第一步,数一数包里有几笔调用,调用数越多,读提案和出错连坐的半径越大。第二步,把每笔调用的目标合约与已有治理记录比对:改参数的调用落在参数注册合约,改权限的调用落在代理或角色合约——出现目标合约与标题不符时,在投票前先提问。第三步,查这笔包执行后哪些调用不可逆,例如已完成的授权、已铸造的额度,不可逆项越多,越值得要求拆分。

社区对捆绑也有制衡工具:有的协议规定跨模块操作必须拆包,有的对含权限变更的调用强制更长的延迟期,也有的要求重大变更提案前做链上模拟、附执行断言。这些规则本身也写在治理合约或流程文档里,可以核验,不是口头传统。

再补一个容易被忽略的记账细节:批量操作的身份由整包数据哈希确定,这带来一个隐蔽的重名问题——两个内容完全相同的独立提案会算出同一个哈希,在部分实现里第二笔排期会直接顶掉第一笔,或者在链上无法区分。治理框架因此普遍在提案构造里掺入唯一性元素(如时间戳或提案编号),但机制各异,读提案时可以顺手确认两个提案的执行哈希是否可区分。此外,列表里各笔调用共享同一个 gas 预算,项数多的包更容易在链上资源紧张时因整体执行成本上升而失败或被迫改写执行参数,这解释了为什么有的协议给批量操作单独设了更保守的重试与重排流程。理解这些约束,你就不会把「包没执行」简单归因为治理怠工。

对活跃治理参与者还有一个平衡视角:完全拒绝捆绑会推高治理成本——每次链上执行都要走完整流程,参数维护的 gas 与注意力消耗都是真金白银。成熟社区的做法通常是折中:日常参数允许季度性打包、以模板化的调用清单降低阅读门槛,而涉及权限、资产与不可逆操作的调用强制单独提案、强制附加链下模拟结果。评估一个协议的治理质量,看它对「哪些操作不许捆绑」有没有明文共识,比看它开了多少次投票更能说明问题。

最后提示边界。提案的具体结构(数据格式、批量规则、哈希算法)取决于协议的合约实现,不同代际的开源库写法有差异。评估具体提案时,以协议自己的治理合约与官方文档为准,别把别家协议的经验直接套过来。本文只做机制说明,不构成投资建议。

一笔提案最多装多少事:捆绑执行的原子性与失败半径 图 2
一笔提案最多装多少事:捆绑执行的原子性与失败半径 · 图 2