Solidity Gas优化:让你的智能合约省钱省到飞起

Solidity里一个超级硬核的主题——Gas优化!在以太坊上跑智能合约,Gas费可不是开玩笑,每一笔操作都要花真金白银,合约写得不好,分分钟钱包就空了!Gas优化就是帮你把合约代码打磨得又快又省,少花Gas还能保持功能稳如老狗。这篇干货会用大白话把Solidity的Gas优化技巧讲得透透的,从变量

Solidity里一个超级硬核的主题——Gas优化!在以太坊上跑智能合约,Gas费可不是开玩笑,每一笔操作都要花真金白银,合约写得不好,分分钟钱包就空了!Gas优化就是帮你把合约代码打磨得又快又省,少花Gas还能保持功能稳如老狗。这篇干货会用大白话把Solidity的Gas优化技巧讲得透透的,从变量存储到循环优化、函数设计,再到汇编魔法,结合OpenZeppelin和Hardhat测试,带你一步步把Gas费省到极致。每种技巧都配上代码和分析,重点是硬核知识点,废话少说,直接上技术细节,帮你把合约Gas费省到飞起!

Gas优化的核心概念

先来搞清楚几个关键点:

  • Gas:以太坊上执行合约的“燃料”,每条指令都有固定Gas成本,单位是wei(1 ETH = 10^18 wei)。
  • 为什么优化Gas:Gas费 = Gas消耗 × Gas价格,优化Gas直接降低用户成本,提升合约竞争力。
  • 优化原则
    • 减少存储操作:存储读写(如SSTORE、SLOAD)成本高。
    • 简化计算:降低复杂运算和循环。
    • 优化数据结构:用更高效的变量类型和存储布局。
    • 利用EVM特性:如短路求值、位操作。
  • 工具
    • Solidity 0.8.x:自带溢出/下溢检查,安全又高效。
    • OpenZeppelin:提供优化好的库,减少重复造轮子。
    • Hardhat:测试和分析Gas消耗,调试优化效果。
  • EVM:以太坊虚拟机,指令成本由EVM定义(如SSTORE ~20,000 Gas,SLOAD ~200 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;
  • 优化器:启用Solidity优化器,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)。
    • 优化ab打包到槽0(264位),c占槽1,2次SSTORE(~40,000 Gas)。
    • 节省:约33% Gas。
  • 规则:按声明顺序打包,优先将小变量(如uint8、uint16)放在一起,总大小不超过256位。

测试

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
  • 结果:优化版Gas消耗明显低于未优化版,验证了变量打包效果。

使用uint128代替uint256

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。
  • 注意:确保数据范围适合小类型,Solidity 0.8.x自带溢出检查。

使用mapping代替数组

数组的动态长度管理(如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。
    • mapping:只存元素和计数器,1次SSTORE加计数器更新。
    • 节省:约20% Gas,mapping还支持快速查找。
  • 测试:修改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++; } // 避免溢出检查
        }
    }
}
  • 解析
    • 未优化:每次循环检查数组长度(MLOAD),且Solidity 0.8.x的溢出检查增加Gas。
    • 优化
      • 缓存users.lengthlength,减少MLOAD。
      • unchecked跳过i++的溢出检查(已知i不会溢出)。
    • 节省:约5-10% Gas,数组越大越明显。
  • 注意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)
            }
        }
    }
}
  • 解析
    • 用汇编直接操作内存和存储,减少Solidity的开销。
    • 批量处理数组,减少函数调用开销。
    • 节省:约20% Gas,适合大数据量场景。
  • 注意:汇编需谨慎,调试难度较高。

测试

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
    }
}
  • 解析
    • 未优化:普通函数可能触发SLOAD(~200 Gas)。
    • 优化view函数标记只读,EVM优化执行路径。
    • 节省:约10% Gas,视调用频率而定。

短路求值

利用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 > 0b > 0为假,跳过后续计算。
    • 节省:约5-10% Gas,视输入而定。

测试

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)
        }
    }
}
  • 解析
    • 未优化:Solidity编译器生成冗余指令。
    • 优化:汇编直接用EVM的add指令,减少栈操作。
    • 节省:约5% Gas,小函数更明显。
  • 注意:汇编需熟悉EVM,错误风险高。

测试

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);
  });
});
  • 结果:汇编版Gas消耗更低。

使用OpenZeppelin库

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,代码冗余。
    • 优化:用OpenZeppelin的Ownable,逻辑简洁,编译优化更好。
    • 节省:约10% Gas,视函数复杂度而定。

部署脚本

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优化的省钱魔法吧!

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!