Solidity 0.8.0+中calldata与memory转换的Gas消耗不一致问题
Solidity 0.8+中Calldata转Memory的Gas消耗差异分析
问题现象
在Solidity 0.8.0及以上版本中,外部调用传入memory数组再调用内部memory参数函数,与外部调用传入calldata数组再转memory调用内部函数的Gas消耗存在显著差异;而在0.7.6版本中,两者消耗一致。
复现代码
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract Dummy { function iacceptmem(uint256[] memory amounts) internal { } function test1(uint256[] memory amounts) external { iacceptmem(amounts); } function test2(uint256[] calldata amounts) external { iacceptmem(amounts); } } contract Tester { Dummy d = new Dummy(); function call_test1() external { uint256[] memory data = new uint256[](1000); d.test1(data); } function call_test2() external { uint256[] memory data = new uint256[](1000); d.test2(data); } }
部署Tester合约后调用call_test1和call_test2,可观察到:
call_test1执行成本约517391 Gascall_test2执行成本约295126 Gas
差异原因
1. Solidity 0.8+的参数处理逻辑
test1路径:Tester.call_test1创建memory数组,调用Dummy.test1时,需将数组从Tester的memory复制到Dummy的calldata(外部调用的参数传递规则)。Dummy.test1的参数为memory类型,Solidity 0.8+会强制将calldata中的数组复制到Dummy的memory中,再传递给内部函数iacceptmem。- 整个流程包含两次数组复制:
Tester memory → Dummy calldata、Dummy calldata → Dummy memory。
test2路径:Tester.call_test2创建memory数组,调用Dummy.test2时,同样将数组从Tester的memory复制到Dummy的calldata。Dummy.test2的参数为calldata类型,调用iacceptmem(memory参数)时,仅需将Dummy的calldata数组复制到自身memory一次。- 整个流程仅包含一次数组复制:
Dummy calldata → Dummy memory(外部调用的复制与test1路径一致)。
两次复制的Gas成本差异(单个数组复制的成本包含字节复制费+内存扩展费),是导致总Gas消耗差距的核心原因。
2. 0.7.6版本的一致性原因
在Solidity 0.7.x版本中,外部函数的memory参数存在一个未严格实现的逻辑:编译器并未将calldata实际复制到memory,而是直接将calldata作为memory参数的底层存储使用(尽管memory类型理论上可修改,但实际场景中若未修改参数则不会触发错误)。因此:
test1路径中,Dummy.test1的memory参数直接复用calldata,无需额外复制,仅需外部调用时的一次复制。test2路径的复制逻辑与0.8+一致,因此两者总Gas消耗相近。
Solidity 0.8.0修复了这一逻辑漏洞,严格区分memory与calldata的存储区域,强制外部函数的memory参数必须复制到内存,从而引入了额外的Gas消耗差异。
内容的提问来源于stack exchange,提问作者Shihao Xia
相关产品推荐
相关产品推荐

