本工程旨在比较多种模式下,利用 zk-verifier 和多种 Solidity 实现方式对于 Arbitrum 中 Gas 消耗的影响。 ## 1. 场景选取 选择了 OpenZeppelin 中的 ERC20 标准 Mintable 项目作为基础,主要测试不同账户间 Mint Token 并进行转账的场景。 ## 2. Solidity 模式设计 用户需要对所采取的行动(mint 或 transfer)进行签名。 ### case 1 由 Executor 对每笔订单进行操作。此 case 不涉及任何 zk 验证以及最终状态压缩,起对照作用,入参无哈希。 ### case 2 所有单组用户数据在链下验证签名后,经过 zk verifier 统一验证并逐一操作。入参哈希。 ### case 3 所有单组数据在链下验证签名并计算出最终状态后,通过 zk verifier 验证。仅更新用户对最终 balance 数量。入参哈希。 ### case 4 在链上利用默克尔树结构,利用 state root 映射所有账户的总状态。所有单组数据在链下验证签名并计算出最终状态后,通过 zk verifier 验证,仅更新 state root。入参无哈希。 ## 3. Gas 构成与不同 case 主要侧重点 ### 构成 在 Arbitrum 中,由于 rollup 需要将用户的 transaction 以特定形式发送到 L1,因此 calldata 的字节大小消耗远大于 VM 执行过程中的消耗。总体消耗可以分为三种类型: 1. Slot storage 存储。 2. VM 执行(主要是哈希算法)。 3. Calldata 的消耗。 ### case 2 侧重于节省验证用户签名的部分(即 VM 执行),将用户签名的验证放置链下,并通过 zk-proof 进行证明。 ### case 3 在节省验证用户签名的部分(VM 执行)的前提下,引入了只修改用户最终状态的优化(Slot storage),避免了对同一 slot 的多次修改。同时,只传递了最终修改的账户状态为入参(Calldata)。 ### case 4 综合(Slot storage)、(VM 执行)和(Calldata)三种方式,通过验证签名、确认最终状态和极低的 calldata 上传,全面节省了链上 Gas。 ## 4. 实验数据 用户订单结构定义如下: ```solidity struct TransferRequest { address user; address to; // Address to transfer to uint256 mintAmount; uint256 transferAmount; } ``` 选择的测试数据点为 [20, 50, 100, 240],其中 240 的选择原因是在 case 2 中,这是 Arbitrum 网络能够在一个块中处理的最大 Gas 消耗量 | Case | Gas (1 req) | Gas (20 req) | Gas (50 req) | Gas (100 req) | Gas (240 req) | Avg Gas (20 req) | Avg Gas (50 req) | Avg Gas (100 req) | Avg Gas (240 req) | 与case 1 交汇订单数 | |-------|-------------|--------------|--------------|---------------|---------------|------------------|------------------|-------------------|-------------------|-------------------| | case1 | 1,509,625 | 7,690,086 | 14,946,640 | 22,605,272 | 38,137,508 | 384,504 | 298,932 | 226,052 | 158,906 | xxx | | case2 | 2,876,451 | 4,567,712 | 5,813,751 | 9,269,974 | 38,515,246 | 228,385 | 116,275 | 92,699 | 160,480 | 6 req | | case3 | 3,567,630 | 3,746,724 | 3,730,772 | 3,724,285 | 3,807,196 | 187,336 | 74,615 | 37,242 | 15,696 | 6 req | | case4 | 2,631,073 | 2,627,910 | 2,610,810 | 2,610,810 | 2,610,810 | 131,395 | 52,216 | 26,108 | 10,878 | 5 req | ## 图表分析 ![image](https://hackmd.io/_uploads/SyIAaXKua.png) 上图展示了四个不同案例(Case 1, Case 2, Case 3, Case 4)在不同签名数量下的Gas消耗情况。每个案例通过不同的颜色和标记进行了区分,以便于观察和比较。图中明显显示了随着签名数量的增加,各案例的Gas消耗也相应地发生了变化。 ## 5. 结论 在 Arbitrum 网络中,Storage 存储并不会造成显著的差异,这一点从 Case 3 和 Case 4 的比较中可以看出。然而,Calldata 和哈希计算会对Gas消耗产生较大的影响,这一现象在 Case 1、Case 2 和 Case 3 的比较中得到了明显的展示。 因此,当单次打包的交易超过6笔时,建议采用 Case 3 的实现方式,因为它在处理大量交易时能够更有效地节省Gas消耗。 ## 6. 补充 ### 差异率 差异率的概念通常用于描述在一组数据中,对同一数据槽进行修改的频率。在case3下,差异率是指在执行多个交易测试时,在原始交易中,“from地址”和“to地址”重复出现的频率。 可以简单理解为,差异率越低,在同数量单子的情况下用户越少,对重复插槽修改的越多 差异率的计算 假设您有一系列交易,每个交易都有一个发送者("from")和一个接收者("to")。 差异率的计算可以基于以下公式: `差异率 = 已使用地址数量 / 总地址数量` **已使用地址数量**:在一定数量的交易中,已经被使用过的唯一地址的数量。 **总地址数量**:考虑的所有交易中的地址数量的总和。在case3的案例中,这个数字是 req * 2,因为每个交易有两个地址("from" 和 "to"),并且有 reqs 个这样的交易。 #### 示例 假设您进行了 10 次交易(count = 10),并且您的差异率阈值设为 50%。 如果在前几次交易中,您已经使用了 8 个不同的“to”地址,则当前的差异率为 8 / (10 * 2) = 40%。 如果差异率低于您设定的 50%,则您会尽量选择一个新的“to”地址。 随着交易的进行,如果差异率达到或超过 50%,开始差异使用已经出现过的“to/from”地址。 ### 基于差异率的 Case3 数据 | Duplication Rate | Gas (1 req) | Gas (3 req) | Gas (5 req) | Gas (10 req) | Gas (20 req) | Gas (50 req) | Gas (100 req) | Gas (240 req) | |------------------|-------------|-------------|-------------|--------------|--------------|--------------|---------------|---------------| | 0.1 | 2,960,476 | 3,282,386 | 3,686,098 | 3,876,903 | 4,039,838 | 4,377,502 | 5,193,268 | 8,510,045 | | 0.3 | 2,902,874 | 3,218,458 | 3,669,010 | 4,089,629 | 4,451,992 | 6,269,868 | 9,897,882 | 20,963,286 | | 0.5 | 2,913,475 | 3,301,197 | 3,741,313 | 4,218,704 | 5,045,257 | 8,503,185 | 14,203,798 | 30,524,482 | | 0.7 | 2,902,874 | 3,306,351 | 3,785,358 | 4,393,137 | 5,798,674 | 8,930,627 | 14,572,211 | 28,913,704 | | 1.0 | 2,915,478 | 3,310,847 | 3,799,307 | 4,616,278 | 5,593,335 | 8,761,361 | 13,979,427 | 28,398,609 | ![image](https://hackmd.io/_uploads/r1ZCJ_q_a.png) 结合case1 图表 ⬇️ ![image](https://hackmd.io/_uploads/S1M1d_9dT.png) 1-20笔请求细节图 ⬇️ ![image](https://hackmd.io/_uploads/S1cOOdqd6.png)