这篇文章详细探讨了如何在以太坊智能合约中,通过使用内联汇编(Assembly)代码来显著优化Gas消耗。文章通过对比Solidity代码与对应汇编实现的Gas成本,展示了在哈希、循环、数学运算、存储操作及地址零地址检查等多个场景下的具体节省效果。
在以太坊区块链上编写高效且经济实惠的智能合约时,哪怕是节省一点 Gas 也很重要。优化 Gas 使用量的一种方法是在合约的某些部分使用汇编代码而不是 Solidity。
在本文中,我们将探讨在各种场景中,从哈希和数学运算到写入存储和检查零地址,使用汇编代码如何帮助你节省 Gas。我们将提供 Solidity 代码示例及其对应的汇编实现,以及 Gas 使用量比较,以便你亲眼看到潜在的 Gas 节省。
1function solidityHash(uint256 a, uint256 b) public view {
2 //unoptimized
3 keccak256(abi.encodePacked(a, b));
4}
Gas: 313
1function assemblyHash(uint256 a, uint256 b) public view {
2 //optimized
3 assembly {
4 mstore(0x00, a)
5 mstore(0x20, b)
6 let hashedVal := keccak256(0x00, 0x40)
7 }
8}
Gas: 231
让我们看看所使用的 Yul 指令及其解释。

你也可以使用 unchecked{++i;} 来节省更多 Gas,但这不会检查 i 是否溢出。为了获得最佳 Gas 节省,请使用内联汇编,但这会限制你可以实现的功能。
1//loop with unchecked{++i}
2function uncheckedPlusPlusI() public pure {
3 uint256 j = 0;
4 for (uint256 i; i < 10; ) {
5 j++;
6 unchecked {
7 ++i;
8 }
9 }
10}
Gas: 1329
1//loop with inline assembly
2function inlineAssemblyLoop() public pure {
3 assembly {
4 let j := 0
5 for {
let i := 0
} lt(i, 10) {
i := add(i, 0x01)
} {
j := add(j, 0x01)
}
}
}
Gas: 709
现在,解释一下 lt Yul 指令以及类似的比较操作码。

使用汇编进行数学运算而不是 Solidity。你可以在汇编中检查溢出/下溢以确保安全。
1//addition in Solidity
2function addTest(uint256 a, uint256 b) public pure {
3 uint256 c = a + b;
4}
Gas: 303
1//addition in assembly
2function addAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := add(a, b)
5 if lt(c, a) {
6 mstore(0x00, "overflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 263
1//subtraction in Solidity
2function subTest(uint256 a, uint256 b) public pure {
3 uint256 c = a - b;
4}
Gas: 300
1//subtraction in assembly
2function subAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := sub(a, b)
5 if gt(c, a) {
6 mstore(0x00, "underflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 263
1//multiplication in Solidity
2function mulTest(uint256 a, uint256 b) public pure {
3 uint256 c = a * b;
4}
Gas: 325
1//multiplication in assembly
2function mulAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := mul(a, b)
5 if lt(c, a) {
6 mstore(0x00, "overflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 265
1//division in Solidity
2function divTest(uint256 a, uint256 b) public pure {
3 uint256 c = a * b;
4}
Gas: 325
1//division in assembly
2function divAssemblyTest(uint256 a, uint256 b) public pure {
3 assembly {
4 let c := div(a, b)
5 if gt(c, a) {
6 mstore(0x00, "underflow")
7 revert(0x00, 0x20)
8 }
9 }
10}
Gas: 265
所以,现在让我们看看这些 Yul 指令的解释。

1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;
2
3function updateOwner(address newOwner) public {
4 owner = newOwner;
5}
Gas: 5302
1address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84;
2
3function assemblyUpdateOwner(address newOwner) public {
4 assembly {
5 sstore(owner.slot, newOwner)
6 }
7}
Gas: 5236
我们之前已经看到了一些这些 Yul 指令,但让我们回顾一下它们的解释。

1function ownerNotZero(address _addr) public pure {
2 require(_addr != address(0), "zero address)");
3}
Gas: 258
1function assemblyOwnerNotZero(address _addr) public pure {
2 assembly {
3 if iszero(_addr) {
4 mstore(0x00, "zero address")
5 revert(0x00, 0x20)
6 }
7 }
8}
Gas: 252
iszero 和 mstore 我们已经见过,现在让我们检查一下 revert 的解释。

当获取合约的 ETH 余额时,你可以使用 selfbalance() 而不是 address(this).balance 来节省 Gas。
1function addressInternalBalance() public returns (uint256) {
2 return address(this).balance;
3}
Gas: 148
1function assemblyInternalBalance() public returns (uint256) {
2 assembly {
3 let c := selfbalance()
4 mstore(0x00, c)
5 return(0x00, 0x20)
6 }
7}
Gas: 133
return 我们刚刚见过,现在让我们看看 selfbalance。

在 Zealynx,我们专注于智能合约安全审计——包括超越表面检查的 Gas 优化审查。无论你需要全面的协议审计还是有针对性的 Gas 效率审查,我们的团队都随时准备帮助你交付安全、经济高效的代码。联系我们以开始对话。
想要通过更多此类深入分析保持领先吗?订阅我们的通讯,确保你不会错过未来的见解。
- 原文链接: zealynx.io/blogs/solidit...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!