Solidity里一个超级硬核的主题——Gas优化!在以太坊上跑智能合约,Gas费可不是开玩笑,每一笔操作都要花真金白银,合约写得不好,分分钟钱包就空了!Gas优化就是帮你把合约代码打磨得又快又省,少花Gas还能保持功能稳如老狗。这篇干货会用大白话把Solidity的Gas优化技巧讲得透透的,从变量
Solidity里一个超级硬核的主题——Gas优化!在以太坊上跑智能合约,Gas费可不是开玩笑,每一笔操作都要花真金白银,合约写得不好,分分钟钱包就空了!Gas优化就是帮你把合约代码打磨得又快又省,少花Gas还能保持功能稳如老狗。这篇干货会用大白话把Solidity的Gas优化技巧讲得透透的,从变量存储到循环优化、函数设计,再到汇编魔法,结合OpenZeppelin和Hardhat测试,带你一步步把Gas费省到极致。每种技巧都配上代码和分析,重点是硬核知识点,废话少说,直接上技术细节,帮你把合约Gas费省到飞起!
先来搞清楚几个关键点:
咱们用Solidity 0.8.20,结合OpenZeppelin和Hardhat,逐步实现各种Gas优化技巧,代码和测试都安排得明明白白。
用Hardhat搭建开发环境,写和测试合约。
mkdir gas-optimization-demo
cd gas-optimization-demo
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox @openzeppelin/contracts
npm install ethers
初始化Hardhat:
npx hardhat init
选择TypeScript项目,安装依赖:
npm install --save-dev ts-node typescript @types/node @types/mocha
目录结构:
gas-optimization-demo/
├── contracts/
│ ├── StorageOptimization.sol
│ ├── LoopOptimization.sol
│ ├── FunctionOptimization.sol
│ ├── AssemblyOptimization.sol
├── scripts/
│ ├── deploy.ts
├── test/
│ ├── GasOptimization.test.ts
├── hardhat.config.ts
├── tsconfig.json
├── package.json
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./"
},
"include": ["hardhat.config.ts", "scripts", "test"]
}
hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
hardhat: {
chainId: 1337,
},
},
};
export default config;
runs: 200平衡部署和执行成本。npx hardhat node
存储操作(SSTORE、SLOAD)是Gas大户,优化存储布局能省不少钱。
EVM存储按256位(32字节)槽位分配,多个小变量打包到同一槽位可减少SSTORE。
contracts/StorageOptimization.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// 未优化的存储
contract UnoptimizedStorage {
uint256 public a; // Slot 0
uint8 public b; // Slot 1
uint256 public c; // Slot 2
function setValues(uint256 _a, uint8 _b, uint256 _c) public {
a = _a;
b = _b;
c = _c;
}
}
// 优化后的存储
contract OptimizedStorage {
uint256 public a; // Slot 0
uint8 public b; // Slot 0
uint256 public c; // Slot 1
function setValues(uint256 _a, uint8 _b, uint256 _c) public {
a = _a;
b = _b;
c = _c;
}
}
a(256位)、b(8位)、c(256位)各占一个槽位,3次SSTORE(~60,000 Gas)。a和b打包到槽0(264位),c占槽1,2次SSTORE(~40,000 Gas)。test/GasOptimization.test.ts:
import { ethers } from "hardhat";
import { expect } from "chai";
import { UnoptimizedStorage, OptimizedStorage } from "../typechain-types";
describe("StorageOptimization", function () {
let unoptimized: UnoptimizedStorage;
let optimized: OptimizedStorage;
beforeEach(async function () {
const UnoptimizedFactory = await ethers.getContractFactory("UnoptimizedStorage");
unoptimized = await UnoptimizedFactory.deploy();
await unoptimized.deployed();
const OptimizedFactory = await ethers.getContractFactory("OptimizedStorage");
optimized = await OptimizedFactory.deploy();
await optimized.deployed();
});
it("should compare gas for setting values", async function () {
const txUnoptimized = await unoptimized.setValues(100, 20, 300);
const receiptUnoptimized = await txUnoptimized.wait();
console.log("Unoptimized Gas:", receiptUnoptimized.gasUsed.toString());
const txOptimized = await optimized.setValues(100, 20, 300);
const receiptOptimized = await txOptimized.wait();
console.log("Optimized Gas:", receiptOptimized.gasUsed.toString());
expect(receiptOptimized.gasUsed).to.be.lt(receiptUnoptimized.gasUsed);
});
});
跑测试:
npx hardhat test
uint256是EVM原生类型,但uint128或更小类型在打包时更省空间。
contracts/StorageOptimization.sol(添加):
contract OptimizedStorageSmallTypes {
uint128 public a; // Slot 0
uint8 public b; // Slot 0
uint128 public c; // Slot 0
function setValues(uint128 _a, uint8 _b, uint128 _c) public {
a = _a;
b = _b;
c = _c;
}
}
a(128位)、b(8位)、c(128位)打包到槽0(264位),1次SSTORE(~20,000 Gas)。uint256的2次SSTORE,省50% Gas。数组的动态长度管理(如push操作)耗Gas,mapping更高效。
contracts/StorageOptimization.sol(添加):
contract UnoptimizedArray {
address[] public users;
function addUser(address user) public {
users.push(user);
}
function getUser(uint256 index) public view returns (address) {
return users[index];
}
}
contract OptimizedMapping {
mapping(uint256 => address) public users;
uint256 public userCount;
function addUser(address user) public {
users[userCount] = user;
userCount++;
}
function getUser(uint256 index) public view returns (address) {
return users[index];
}
}
push需要更新长度(SSTORE)和存储元素,2次SSTORE。GasOptimization.test.ts,验证addUser的Gas消耗,mapping更省。循环是Gas杀手,尤其在存储操作多时。
contracts/LoopOptimization.sol:
// 未优化的循环
contract UnoptimizedLoop {
mapping(address => uint256) public balances;
function updateBalances(address[] memory users, uint256[] memory amounts) public {
for (uint256 i = 0; i < users.length; i++) {
balances[users[i]] = amounts[i]; // 每次循环SSTORE
}
}
}
// 优化后的循环
contract OptimizedLoop {
mapping(address => uint256) public balances;
function updateBalances(address[] memory users, uint256[] memory amounts) public {
uint256 length = users.length;
for (uint256 i = 0; i < length; ) {
balances[users[i]] = amounts[i];
unchecked { i++; } // 避免溢出检查
}
}
}
users.length到length,减少MLOAD。unchecked跳过i++的溢出检查(已知i不会溢出)。unchecked需确保安全,避免溢出风险。批量更新比单次循环更省Gas。
contracts/LoopOptimization.sol(添加):
contract OptimizedBatch {
mapping(address => uint256) public balances;
function updateBalancesBatch(address[] memory users, uint256[] memory amounts) public {
uint256 length = users.length;
require(length == amounts.length, "Invalid input");
assembly {
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
let user := mload(add(users, add(32, mul(i, 32))))
let amount := mload(add(amounts, add(32, mul(i, 32))))
sstore(add(keccak256(user, 32), sload(0)), amount)
}
}
}
}
test/GasOptimization.test.ts(添加):
import { ethers } from "hardhat";
import { UnoptimizedLoop, OptimizedLoop, OptimizedBatch } from "../typechain-types";
describe("LoopOptimization", function () {
let unoptimized: UnoptimizedLoop;
let optimized: OptimizedLoop;
let batch: OptimizedBatch;
beforeEach(async function () {
const UnoptimizedFactory = await ethers.getContractFactory("UnoptimizedLoop");
unoptimized = await UnoptimizedFactory.deploy();
await unoptimized.deployed();
const OptimizedFactory = await ethers.getContractFactory("OptimizedLoop");
optimized = await OptimizedFactory.deploy();
await optimized.deployed();
const BatchFactory = await ethers.getContractFactory("OptimizedBatch");
batch = await BatchFactory.deploy();
await batch.deployed();
});
it("should compare gas for loops", async function () {
const users = [ethers.Wallet.createRandom().address, ethers.Wallet.createRandom().address];
const amounts = [100, 200];
const txUnoptimized = await unoptimized.updateBalances(users, amounts);
const receiptUnoptimized = await txUnoptimized.wait();
console.log("Unoptimized Loop Gas:", receiptUnoptimized.gasUsed.toString());
const txOptimized = await optimized.updateBalances(users, amounts);
const receiptOptimized = await txOptimized.wait();
console.log("Optimized Loop Gas:", receiptOptimized.gasUsed.toString());
const txBatch = await batch.updateBalancesBatch(users, amounts);
const receiptBatch = await txBatch.wait();
console.log("Batch Gas:", receiptBatch.gasUsed.toString());
expect(receiptOptimized.gasUsed).to.be.lt(receiptUnoptimized.gasUsed);
expect(receiptBatch.gasUsed).to.be.lt(receiptOptimized.gasUsed);
});
});
OptimizedBatch最省Gas,OptimizedLoop次之,UnoptimizedLoop最高。函数设计直接影响Gas消耗。
view函数避免不必要的SLOAD。
contracts/FunctionOptimization.sol:
// 未优化的函数
contract UnoptimizedFunction {
mapping(address => uint256) public balances;
function getBalance(address user) public returns (uint256) {
return balances[user]; // SLOAD
}
}
// 优化后的函数
contract OptimizedFunction {
mapping(address => uint256) public balances;
function getBalance(address user) public view returns (uint256) {
return balances[user]; // No state change, cheaper
}
}
view函数标记只读,EVM优化执行路径。利用Solidity的短路求值减少计算。
contracts/FunctionOptimization.sol(添加):
contract UnoptimizedCondition {
function check(uint256 a, uint256 b) public pure returns (bool) {
return (a > 0 && b > 0 && a + b > 0); // 全部计算
}
}
contract OptimizedCondition {
function check(uint256 a, uint256 b) public pure returns (bool) {
return (a > 0 && b > 0 && (a + b) > 0); // 短路求值
}
}
a > 0或b > 0为假,跳过后续计算。test/GasOptimization.test.ts(添加):
import { UnoptimizedFunction, OptimizedFunction, UnoptimizedCondition, OptimizedCondition } from "../typechain-types";
describe("FunctionOptimization", function () {
let unoptimizedFunc: UnoptimizedFunction;
let optimizedFunc: OptimizedFunction;
let unoptimizedCond: UnoptimizedCondition;
let optimizedCond: OptimizedCondition;
beforeEach(async function () {
const UnoptimizedFuncFactory = await ethers.getContractFactory("UnoptimizedFunction");
unoptimizedFunc = await UnoptimizedFuncFactory.deploy();
await unoptimizedFunc.deployed();
const OptimizedFuncFactory = await ethers.getContractFactory("OptimizedFunction");
optimizedFunc = await OptimizedFuncFactory.deploy();
await optimizedFunc.deployed();
const UnoptimizedCondFactory = await ethers.getContractFactory("UnoptimizedCondition");
unoptimizedCond = await UnoptimizedCondFactory.deploy();
await unoptimizedCond.deployed();
const OptimizedCondFactory = await ethers.getContractFactory("OptimizedCondition");
optimizedCond = await OptimizedCondFactory.deploy();
await optimizedCond.deployed();
});
it("should compare gas for functions", async function () {
const user = ethers.Wallet.createRandom().address;
const txUnoptimized = await unoptimizedFunc.getBalance(user);
const receiptUnoptimized = await txUnoptimized.wait();
console.log("Unoptimized Function Gas:", receiptUnoptimized.gasUsed.toString());
const txOptimized = await optimizedFunc.getBalance(user);
const receiptOptimized = await txOptimized.wait();
console.log("Optimized Function Gas:", receiptOptimized.gasUsed.toString());
expect(receiptOptimized.gasUsed).to.be.lt(receiptUnoptimized.gasUsed);
});
it("should compare gas for conditions", async function () {
const txUnoptimized = await unoptimizedCond.check(0, 5);
const receiptUnoptimized = await txUnoptimized.wait();
console.log("Unoptimized Condition Gas:", receiptUnoptimized.gasUsed.toString());
const txOptimized = await optimizedCond.check(0, 5);
const receiptOptimized = await txOptimized.wait();
console.log("Optimized Condition Gas:", receiptOptimized.gasUsed.toString());
expect(receiptOptimized.gasUsed).to.be.lt(receiptUnoptimized.gasUsed);
});
});
view函数和短路求值明显降低Gas。用Yul(Solidity的中间语言)直接操作EVM,极致省Gas。
contracts/AssemblyOptimization.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract UnoptimizedMath {
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
}
contract OptimizedMath {
function add(uint256 a, uint256 b) public pure returns (uint256) {
assembly {
let result := add(a, b)
mstore(0x0, result)
return(0x0, 32)
}
}
}
add指令,减少栈操作。test/GasOptimization.test.ts(添加):
import { UnoptimizedMath, OptimizedMath } from "../typechain-types";
describe("AssemblyOptimization", function () {
let unoptimized: UnoptimizedMath;
let optimized: OptimizedMath;
beforeEach(async function () {
const UnoptimizedFactory = await ethers.getContractFactory("UnoptimizedMath");
unoptimized = await UnoptimizedFactory.deploy();
await unoptimized.deployed();
const OptimizedFactory = await ethers.getContractFactory("OptimizedMath");
optimized = await OptimizedFactory.deploy();
await optimized.deployed();
});
it("should compare gas for math", async function () {
const txUnoptimized = await unoptimized.add(100, 200);
const receiptUnoptimized = await txUnoptimized.wait();
console.log("Unoptimized Math Gas:", receiptUnoptimized.gasUsed.toString());
const txOptimized = await optimized.add(100, 200);
const receiptOptimized = await txOptimized.wait();
console.log("Optimized Math Gas:", receiptOptimized.gasUsed.toString());
expect(receiptOptimized.gasUsed).to.be.lt(receiptUnoptimized.gasUsed);
});
});
OpenZeppelin的库经过Gas优化,直接用省心省Gas。
contracts/StorageOptimization.sol(添加):
import "@openzeppelin/contracts/access/Ownable.sol";
contract UnoptimizedOwnable {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function setValue(uint256 _value) public onlyOwner {
// Logic
}
}
contract OptimizedOwnable is Ownable {
constructor() Ownable() {}
function setValue(uint256 _value) public onlyOwner {
// Logic
}
}
onlyOwner,代码冗余。Ownable,逻辑简洁,编译优化更好。scripts/deploy.ts:
import { ethers } from "hardhat";
async function main() {
const factories = [
"UnoptimizedStorage", "OptimizedStorage", "OptimizedStorageSmallTypes",
"UnoptimizedArray", "OptimizedMapping",
"UnoptimizedLoop", "OptimizedLoop", "OptimizedBatch",
"UnoptimizedFunction", "OptimizedFunction",
"UnoptimizedCondition", "OptimizedCondition",
"UnoptimizedMath", "OptimizedMath",
"UnoptimizedOwnable", "OptimizedOwnable"
];
for (const factory of factories) {
const ContractFactory = await ethers.getContractFactory(factory);
const contract = await ContractFactory.deploy();
await contract.deployed();
console.log(`${factory} deployed to: ${contract.address}`);
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
跑部署:
npx hardhat run scripts/deploy.ts --network hardhat
跑代码,体验Solidity Gas优化的省钱魔法吧!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!