Skip to main content

gas-optimization

Gas optimization patterns for Solidity smart contracts. Use when optimizing contract deployment cost, runtime gas usage, or reviewing gas-critical paths. Covers calldata vs memory, immutable/constant, unchecked blocks, storage packing, batch operations, and custom errors.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
ccashwell/evm-cortex
آخر نشاط في المصدر
١٠ أبريل ٢٠٢٦ في ١٦:٣١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٣١
التفرعات
١٨

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
gas-optimization
description
Gas optimization patterns for Solidity smart contracts. Use when optimizing contract deployment cost, runtime gas usage, or reviewing gas-critical paths. Covers calldata vs memory, immutable/constant, unchecked blocks, storage packing, batch operations, and custom errors.
# Gas Optimization ## Measure First Never optimize blindly. Use forge snapshots and gas reports. ```bash # Gas report for all tests forge test --gas-report # Snapshot to compare before/after forge snapshot # ...make changes... forge snapshot --diff ``` ## Custom Errors Over Require Strings Custom errors save ~50 gas on deployment and ~200 gas per revert vs `require("string")`. ```solidity // BAD: ~24,000 gas on revert (stores string in bytecode + memory) require(amount > 0, "Amount must be greater than zero"); // GOOD: ~24 gas for error selector encoding error ZeroAmount(); if (amount == 0) revert ZeroAmount(); ``` ## Calldata vs Memory Use `calldata` for read-only external function parameters. Avoids copying to memory. ```solidity // BAD: copies entire array to memory (~3 gas per byte + allocation) function processOrders(Order[] memory orders) external { ... } // GOOD: reads directly from calldata function processOrders(Order[] calldata orders) external { ... } // Gas savings scale with input size: // 100 bytes: ~600 gas saved // 1KB: ~6,000 gas saved // 10KB: ~60,000 gas saved ``` ## Immutable and Constant ```solidity // constant: compile-time value, inlined everywhere. 0 gas for SLOAD. uint256 public constant MAX_SUPPLY = 10_000; bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name)"); // immutable: set once in constructor, stored in bytecode. 0 gas for SLOAD. address public immutable FACTORY; uint256 public immutable DEPLOYMENT_TIMESTAMP; constructor(address factory) { FACTORY = factory; DEPLOYMENT_TIMESTAMP = block.timestamp; } // Regular storage variable: 2,100 gas (cold) or 100 gas (warm) per SLOAD address public admin; // avoid if value never changes after deployment ``` ## Unchecked Arithmetic When overflow is impossible (e.g., loop counters bounded by array length), use `unchecked` to skip overflow checks (~40 gas per operation). ```solidity // BAD: overflow checks on every increment for (uint256 i = 0; i < arr.length; i++) { ... } // GOOD: i cannot overflow (bounded by array length, which fits in uint256) for (uint256 i; i < arr.length;) { // process arr[i] unchecked { ++i; } } ``` ## Cache Storage Reads Every SLOAD costs 2,100 gas (cold) or 100 gas (warm). Cache values read more than once. ```solidity // BAD: 3 SLOADs for totalSupply function distribute() external { if (totalSupply == 0) revert NoSupply(); // SLOAD 1 uint256 perToken = rewards / totalSupply; // SLOAD 2 emit Distributed(perToken, totalSupply); // SLOAD 3 } // GOOD: 1 SLOAD, cached in memory function distribute() external { uint256 supply = totalSupply; // SLOAD 1 (cached) if (supply == 0) revert NoSupply(); uint256 perToken = rewards / supply; emit Distributed(perToken, supply); } ``` ## Storage Packing Pack related variables into single 32-byte slots. See `storage-layout` skill for details. ```solidity // BAD: 3 slots uint256 amount; // slot 0 address user; // slot 1 bool active; // slot 2 // GOOD: 2 slots uint256 amount; // slot 0 address user; // slot 1 (20 bytes) bool active; // slot 1 (packed, +1 byte) ``` Writing to packed slots together in one transaction saves ~20,000 gas (avoids separate SSTORE). ## Short-Circuit Evaluation Put cheap checks first in `&&` / `||` chains. ```solidity // BAD: expensive SLOAD first if (balances[msg.sender] > minBalance && isWhitelisted) { ... } // GOOD: cheap check first, SLOAD only if needed if (isWhitelisted && balances[msg.sender] > minBalance) { ... } ``` ## Batch Operations Amortize fixed overhead (21,000 base gas, cold SLOADs) across multiple operations. ```solidity function batchTransfer( IERC20 token, address[] calldata recipients, uint256[] calldata amounts ) external { uint256 len = recipients.length; if (len != amounts.length) revert ArrayLengthMismatch(); for (uint256 i; i < len;) { token.safeTransfer(recipients[i], amounts[i]); unchecked { ++i; } } } ``` ## Avoid Redundant Zero Initialization The EVM initializes all values to zero. Explicit zero assignment wastes gas. ```solidity // BAD: redundant zero init uint256 counter = 0; bool flag = false; // GOOD: default is already zero/false uint256 counter; bool flag; ``` ## Use Bytes32 Over String for Short Constants ```solidity // BAD: dynamic string requires more gas string public constant NAME = "MyToken"; // GOOD: fixed-size, cheaper to read bytes32 public constant NAME = "MyToken"; ``` ## Precompute Keccak256 Hashes ```solidity // BAD: computed at runtime every call function hasRole(string memory role) external view { bytes32 hash = keccak256(abi.encodePacked(role)); ... } // GOOD: precomputed constant bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); ``` ## Function Ordering by Selector The Solidity dispatcher checks function selectors in order. Frequently called functions with lower selectors dispatch faster. Not worth manual optimization unless the contract has 50+ external functions. ## Gas Optimization Checklist | Optimization | Typical Savings | |-------------|----------------| | Custom errors vs require strings | ~200 gas/revert | | calldata vs memory | ~600+ gas/call | | immutable vs storage | ~2,100 gas/read | | Unchecked loop increment | ~40 gas/iteration | | Cache storage reads | ~100-2,100 gas/read | | Storage packing | ~20,000 gas/write | | ++i vs i++ | ~5 gas/iteration | | Zero-init removal | ~3 gas/variable | ## When NOT to Optimize - Readability loss outweighs savings for admin-only / rare-path functions - One-time setup functions (constructors, initializers) - Gas savings under 100 gas in non-hot paths - Assembly tricks without measured benchmarks
عرض على GitHub