이더리움 스마트 컨트랙트에서 가스(Gas)는 곧 비용
같은 기능이라도 코드 작성 방식에 따라 가스 소비량이 수 배 이상 차이
배포 이후에는 수정 불가이므로 사전 최적화가 유일한 기회


Gas와 EVM 연산 비용

EVM(이더리움 가상머신)은 각 연산(Opcode)마다 고정된 가스 비용 부과
실제 수수료는 사용된 가스량 × Gas Price(Gwei)로 계산

storage 블록체인 영구 저장 SLOAD: 2,100 gas SSTORE: 20,000+ gas 트랜잭션 간 데이터 유지 memory 함수 실행 중 임시 저장 MLOAD/MSTORE: 3 gas 크기 따라 소폭 증가 트랜잭션 종료 시 소멸 stack EVM 연산 처리 공간 PUSH/POP: 2~3 gas 최대 depth 1,024 immutable/constant 참조 비용 높음 ◀──────────────────────────────── 비용 낮음

EVM 데이터 저장 위치별 비용 구조 — storage(영구), memory(임시), stack(연산용)
출처: ethereum.org — Ethereum Virtual Machine

위치별 접근 비용 차이가 최적화의 핵심 근거

데이터 위치Opcode가스 비용특징
storage 읽기 (cold)SLOAD2,100블록체인 영구 저장, EIP-2929 이후
storage 쓰기 (신규)SSTORE20,000+가장 비싼 연산
memory 읽기/쓰기MLOAD/MSTORE3트랜잭션 종료 시 소멸
바이트코드 인라인 값PUSH3immutable / constant 적용 시
산술 연산ADD, MUL3~5매우 저렴

2021년 Berlin 하드포크(EIP-2929)로 SLOAD 비용이 800 → 2,100으로 인상
스토리지 접근 횟수를 줄이는 패턴의 절감 효과가 이때부터 더욱 크게 작용


패턴 1. Custom Error — revert 비용을 4바이트로

require의 문자열은 배포 시 바이트코드에 그대로 포함
문자열 길이에 비례해 배포 비용 증가, revert 시마다 전체 문자열 ABI 인코딩 반환
Custom Error는 함수 시그니처 selector 4바이트만 반환 — 길이 고정

방식revert 반환 크기바이트코드 영향
require("Only owner can call this.")문자열 길이(~30바이트) 비례문자열 전체 포함
revert NotOwner()selector 4바이트 고정시그니처 해시만 포함
// Custom Error 선언 및 사용
error NotOwner();
error InsufficientBalance(uint256 available, uint256 required);

modifier onlyOwner() {
    if (msg.sender != owner) revert NotOwner();
    _;
}

function withdraw(uint256 amount) public {
    if (balance < amount)
        revert InsufficientBalance(balance, amount);
}

Solidity 0.8.4부터 도입, 파라미터를 추가해도 string 방식보다 항상 유리


패턴 2. immutable / constant — SLOAD를 PUSH로 교체

배포 후 변경되지 않는 값을 일반 상태 변수로 선언하면 읽을 때마다 SLOAD 발생
immutableconstant는 값을 바이트코드에 직접 인라인 삽입
읽기 연산이 SLOAD(2,100 gas) → PUSH(3 gas)로 교체됨

키워드값 결정 시점읽기 비용대표 사용 예
constant컴파일 시점~3 gas (PUSH)고정 상수 (uint256 MAX = 1000)
immutableconstructor 실행 시~3 gas (PUSH)배포자 주소 (msg.sender)
일반 상태 변수런타임 언제든2,100 gas (SLOAD)런타임 변경이 필요한 값
contract Counter {
    uint256 public constant MAX_VALUE = 10000; // 컴파일 타임 고정
    address public immutable owner;            // 배포 시 결정, 이후 불변

    constructor() {
        owner = msg.sender;
    }
}

패턴 3. storage 캐싱 — SLOAD 횟수 최소화

같은 스토리지 변수를 함수 내에서 반복 참조하면 그 횟수만큼 2,100 gas가 누적
로컬 변수에 한 번 캐싱하면 이후 접근은 memory 비용(~3 gas)으로 처리
루프와 결합 시 절감폭이 배열 크기에 비례해 선형으로 증가

// Before — SLOAD 3회 (6,300 gas)
function report() public {
    emit Log(counter);
    uint256 x = counter * 2;
    uint256 y = counter + 1;
}

// After — SLOAD 1회 (2,100 gas) + MLOAD 2회 (6 gas)
function report() public {
    uint256 c = counter;   // 단 한 번의 SLOAD
    emit Log(c);
    uint256 x = c * 2;
    uint256 y = c + 1;
}

배열을 순회하는 경우 arr.length도 스토리지 읽기 대상
루프 전에 uint256 len = arr.length로 캐싱하는 것만으로 반복 SLOAD 제거 가능


패턴 4. mapping으로 O(n) → O(1)

배열 순회는 데이터 크기에 비례해 가스가 선형 증가
데이터가 충분히 커지면 블록 가스 한도 초과로 트랜잭션 자체가 실패하는 상황 발생
mapping은 해시 테이블 구조로, 크기와 무관하게 항상 일정한 가스 소비

조회 방식시간 복잡도가스 특성
배열 순회 (for)O(n)데이터 크기에 비례해 선형 증가
mapping 조회O(1)크기 무관, 항상 고정
// 역매핑 패턴 — O(1) 조회 + 전체 목록 순회를 모두 지원
address[] public userList;
mapping(address => uint256) public userIndex;

function register(address user) internal {
    userIndex[user] = userList.length; // mapping으로 인덱스 저장
    userList.push(user);               // 배열로 전체 목록 유지
}

function getIndex(address user) public view returns (uint256) {
    return userIndex[user]; // O(1) 조회
}

정리

패턴BeforeAfter절감 핵심
Custom Error require("string") revert Error() revert 비용 고정 4바이트
immutable 일반 상태 변수 immutable 선언 SLOAD(2,100) → PUSH(3)
constant 일반 상태 변수 constant 선언 스토리지 슬롯 미사용
storage 캐싱 반복 SLOAD 로컬 변수 1회 캐싱 SLOAD 횟수 최소화
mapping 활용 배열 순회 O(n) mapping 조회 O(1) 가스 선형 증가 차단

이더리움 컨트랙트는 배포 후 수정 불가
가스 최적화는 비용 절감인 동시에 복잡한 스토리지 로직을 단순화해
reentrancy 등 보안 취약점 노출 면적 축소의 의미도 존재


참고 자료

[1] Ethereum Foundation — Ethereum Virtual Machine (EVM)
    https://ethereum.org/en/developers/docs/evm/

[2] Vitalik Buterin, Martin Swende — EIP-2929: Gas cost increases for state access opcodes (2020)
    https://eips.ethereum.org/EIPS/eip-2929

[3] Solidity Documentation — Custom Errors
    https://docs.soliditylang.org/en/latest/control-structures.html#custom-errors

[4] Solidity Documentation — The Optimizer
    https://docs.soliditylang.org/en/latest/internals/optimizer.html

[5] ethereum.org — Gas and Fees
    https://ethereum.org/en/developers/docs/gas/

+ Recent posts