
이더리움 스마트 계약의 가스 최적화를 위한 10가지 모범 사례
글: Certik
이더리움 메인넷의 가스 비용은 네트워크 혼잡 시 특히 두드러지게 나타나는 오랜 문제이다. 피크 시간대에는 사용자가 거래 수수료를 매우 높게 지불해야 하는 경우가 흔하다. 따라서 스마트 계약 개발 단계에서 가스 비용 최적화가 매우 중요하다. 가스 소비를 최적화하면 거래 비용을 효과적으로 절감할 수 있을 뿐 아니라, 거래 효율성도 향상시켜 사용자에게 보다 경제적이고 효율적인 블록체인 경험을 제공할 수 있다.
본문에서는 이더리움 가상 머신(EVM)의 가스 요금 체계, 가스 최적화 관련 핵심 개념 및 스마트 계약 개발 시 가스 최적화를 위한 모범 사례들을 개괄한다. 이러한 내용을 통해 개발자들에게 실질적인 통찰과 도움을 제공하고, 일반 사용자들이 EVM의 가스 요금 작동 방식을 더 잘 이해하도록 돕고자 한다. 또한 우리는 블록체인 생태계 내 도전 과제에 공동으로 대응하고자 한다.
EVM 가스 요금 체계 소개
EVM 호환 네트워크에서 '가스(Gas)'란 특정 작업 수행에 필요한 컴퓨팅 능력을 측정하는 단위이다.
아래 그림은 EVM 구조 구성도를 보여주며, 가스 소비는 세 가지 부분으로 나뉜다: 연산 실행, 외부 메시지 호출, 그리고 메모리 및 스토리지 읽기/쓰기.

출처: 이더리움 공식 웹사이트[1]
모든 트랜잭션 실행은 컴퓨팅 자원을 필요로 하므로 무한 루프 및 서비스 거부(DoS) 공격을 방지하기 위해 일정한 수수료가 부과된다. 트랜잭션 완료에 드는 비용을 '가스 요금'이라고 부른다.
EIP-1559(런던 하드포크) 시행 이후, 가스 요금은 다음 공식으로 계산된다:
가스 요금 = 사용된 가스량 × (기본 요금 + 우선 순위 요금)
기본 요금은 소각되며, 우선 순위 요금은 검증자가 트랜잭션을 블록에 포함시키도록 유도하는 인센티브 역할을 한다. 트랜잭션 전송 시 더 높은 우선 순위 요금을 설정하면 다음 블록에 포함될 가능성이 높아진다. 이는 마치 검증자에게 지불하는 '팁(tip)'과 같다.
1. EVM 내 가스 최적화 이해
Solidity로 작성된 스마트 계약은 컴파일 시 일련의 '오퍼코드(opcodes)'로 변환된다.
어떤 오퍼코드라도(예: 계약 생성, 메시지 호출, 계정 스토리지 접근, 가상 머신 상 연산 실행 등) 공인된 가스 소비 비용을 가지며, 이 정보는 이더리움 옐로우페이퍼[2]에 기록되어 있다.

여러 EIP 수정을 거쳐 일부 오퍼코드의 가스 비용이 조정되었으며, 이는 옐로우페이퍼와 다를 수 있다. 최신 오퍼코드 비용에 대한 자세한 내용은 여기를 참조하라[3].
2. 가스 최적화 기본 개념
가스 최적화의 핵심 아이디어는 EVM 블록체인에서 비용 효율이 높은 연산을 우선 선택하고, 가스 비용이 높은 연산은 피하는 것이다.
EVM에서 낮은 비용이 드는 연산은 다음과 같다:
-
메모리 변수 읽기/쓰기
-
상수 및 불변 변수 읽기
-
로컬 변수 읽기/쓰기
-
calldata 변수 읽기 (예: calldata 배열 및 구조체)
-
내부 함수 호출
비용이 높은 연산은 다음과 같다:
-
계약 스토리지 내 상태 변수 읽기/쓰기
-
외부 함수 호출
-
반복문 연산
EVM 가스 요금 최적화 모범 사례
위의 기본 개념을 바탕으로, 개발자 커뮤니티를 위해 가스 요금 최적화를 위한 모범 사례 목록을 정리하였다. 이러한 사례들을 따르면 개발자는 스마트 계약의 가스 소비를 줄이고 거래 비용을 낮추며, 보다 효율적이고 사용자 친화적인 애플리케이션을 만들 수 있다.
1. 스토리지 사용 최소화
Solidity에서 스토리지(Storage)는 제한된 자원이며, 메모리(Memory)보다 훨씬 많은 가스를 소비한다. 스마트 계약이 스토리지에서 데이터를 읽거나 쓸 때마다 높은 가스 비용이 발생한다.
이더리움 옐로우페이퍼에 따르면, 스토리지 연산은 메모리 연산보다 100배 이상 비용이 많이 든다. 예를 들어, OPcodes mload와 mstore 명령어는 각각 3가스만 소비하지만, sload와 sstore 같은 스토리지 연산은 가장 이상적인 경우에도 최소 100가스가 필요하다.

스토리지 사용을 제한하는 방법은 다음과 같다:
-
영구적이지 않은 데이터는 메모리에 저장
-
스토리지 수정 횟수 감소: 중간 결과를 메모리에 저장해두고 모든 계산 후에 한 번만 스토리지 변수에 할당
2. 변수 패킹
스마트 계약에서 사용되는 스토리지 슬롯(Storage slot) 수와 데이터 표현 방식은 가스 요금 소비에 큰 영향을 미친다.
Solidity 컴파일러는 연속된 스토리지 변수를 컴파일 시 패킹하며, 32바이트 크기의 스토리지 슬롯을 변수 저장의 기본 단위로 사용한다. 변수 패킹이란 여러 변수를 하나의 스토리지 슬롯에 적절히 배치하여 공간을 절약하는 기법이다.
왼쪽은 효율이 낮은 구현 방식으로 3개의 스토리지 슬롯을 사용하며, 오른쪽은 더 효율적인 방식이다.

이러한 세부 조정을 통해 개발자는 20,000가스를 절약할 수 있다(사용되지 않은 스토리지 슬롯 저장 시 20,000가스 소모). 이제는 두 개의 스토리지 슬롯만 필요하게 되었다.
모든 스토리지 슬롯이 가스를 소모하므로, 변수 패킹은 필요한 슬롯 수를 줄임으로써 가스 사용을 최적화한다.
3. 데이터 타입 최적화
하나의 변수는 여러 데이터 타입으로 표현할 수 있지만, 각 타입의 연산 비용은 다르다. 적절한 데이터 타입 선택은 가스 사용 최적화에 도움이 된다.
예를 들어 Solidity에서 정수형은 uint8, uint16, uint32 등 다양한 크기로 나뉜다. EVM은 256비트 단위로 연산을 수행하므로, uint8 사용 시 EVM은 이를 먼저 uint256으로 변환해야 하며, 이 과정에서 추가 가스가 소모된다.

위 코드를 통해 uint8과 uint256의 가스 비용을 비교할 수 있다. UseUint() 함수는 120,382가스를 소비하고, UseUInt8() 함수는 166,111가스를 소비한다.
따라서 단독으로 보면 uint256이 uint8보다 저렴하다. 그러나 앞서 제안한 변수 패킹 최적화를 적용하면 이야기가 달라진다. 개발자가 네 개의 uint8 변수를 하나의 스토리지 슬롯에 패킹할 수 있다면, 이들을 반복 처리하는 총 비용은 네 개의 uint256 변수보다 낮아진다. 이렇게 하면 스마트 계약은 스토리지 슬롯을 한 번만 읽고 쓰며, 네 개의 uint8 변수를 메모리/스토리지에 한 번의 연산으로 옮길 수 있다.
4. 동적 변수 대신 고정 크기 변수 사용
데이터 길이가 32바이트 이내로 제한될 수 있다면, bytes 또는 string 대신 bytes32 데이터 타입 사용을 권장한다. 일반적으로 고정 크기 변수는 가변 크기 변수보다 가스 소비가 적다. 바이트 길이를 제한할 수 있다면, bytes1부터 bytes32까지 가능한 최소 길이를 선택하는 것이 좋다.


5. 맵핑과 배열
Solidity에서 데이터 리스트는 배열(Arrays)과 맵핑(Mappings) 두 가지 데이터 타입으로 표현할 수 있으나, 문법과 구조는 크게 다르다.
대부분의 경우 맵핑이 더 효율적이며 비용이 낮지만, 배열은 반복 가능하며 데이터 타입 패킹을 지원한다. 따라서 데이터 리스트 관리 시 맵핑 사용을 우선 추천하며, 반복이 필요하거나 데이터 타입 패킹을 통해 가스 소비를 최적화할 수 있는 경우에만 배열을 사용하는 것이 좋다.
6. memory 대신 calldata 사용
함수 매개변수로 선언된 변수는 memory 또는 calldata에 저장할 수 있다. 주요 차이점은 memory는 함수 내에서 수정 가능하지만 calldata는 불변이라는 점이다.
원칙을 기억하라: 함수 매개변수가 읽기 전용이라면 memory보다 calldata를 우선 사용하라. 이를 통해 함수의 calldata에서 memory로의 불필요한 복사 연산을 피할 수 있다.
예제 1: memory 사용

memory 키워드 사용 시, 배열 값은 ABI 디코딩 과정에서 인코딩된 calldata로부터 memory로 복사된다. 이 코드 블록의 실행 비용은 3,694가스이다.
예제 2: calldata 사용

calldata에서 직접 값을 읽을 경우, 중간 memory 작업을 건너뛸 수 있다. 이 최적화 방식으로 실행 비용이 2,413가스로 감소하며, 가스 효율성이 35% 향상되었다.
7. 가능하면 Constant/Immutable 키워드 사용
Constant/Immutable 변수는 계약의 스토리지에 저장되지 않는다. 이러한 변수는 컴파일 시 계산되어 계약의 바이트코드에 저장된다. 따라서 스토리지 접근보다 훨씬 낮은 비용이 들며, 가능하면 Constant 또는 Immutable 키워드 사용을 권장한다.
8. 오버플로우/언더플로우가 발생하지 않을 때 Unchecked 사용
개발자가 산술 연산에서 오버플로우 또는 언더플로우가 절대 발생하지 않음을 확신할 수 있다면, Solidity v0.8.0에서 도입된 unchecked 키워드를 사용하여 불필요한 오버플로우/언더플로우 검사를 피하고 가스 비용을 절감할 수 있다.
아래 그림에서 i<length 조건 때문에 변수 i는 절대 오버플로우하지 않는다. 여기서 length는 uint256으로 정의되어 있으므로 i의 최댓값은 max(uint)-1이다. 따라서 unchecked 코드 블록 내에서 i를 증가시키는 것은 안전하며, 가스도 절약된다.

또한 0.8.0 이상 버전의 컴파일러는 SafeMath 라이브러리를 더 이상 필요로 하지 않으며, 컴파일러 자체에 오버플로우 및 언더플로우 보호 기능이 내장되어 있다.
9. 수정자 최적화
수정자의 코드는 수정된 함수 내에 삽입되며, 수정자를 사용할 때마다 코드가 복사된다. 이로 인해 바이트코드 크기가 증가하고 가스 소비도 늘어난다. 아래는 수정자의 가스 비용을 최적화하는 방법이다:
최적화 전:

최적화 후:

이 예제에서 로직을 내부 함수 _checkOwner()로 재구성함으로써 수정자 내에서 해당 함수를 재사용할 수 있게 되었고, 이로 인해 바이트코드 크기와 가스 비용이 모두 감소하였다.
10. 단락 평가 최적화
|| 및 && 연산자에 대해 논리 연산은 단락 평가(short-circuit evaluation)를 수행한다. 즉, 첫 번째 조건만으로도 논리식의 결과가 결정되면 두 번째 조건은 평가되지 않는다.
가스 소비를 최적화하기 위해 계산 비용이 낮은 조건을 앞에 배치하면, 높은 비용의 계산을 건너뛸 가능성이 생긴다.
추가 일반 권장 사항
1. 사용하지 않는 코드 삭제
계약 내 사용하지 않는 함수나 변수가 있다면 삭제하는 것이 좋다. 이는 계약 배포 비용을 줄이고 계약 크기를 작게 유지하는 가장 직접적인 방법이다.
실용적인 몇 가지 제안:
계산에 가장 효율적인 알고리즘 사용. 계약 내 어떤 계산의 결과를 직접 사용한다면, 이러한 중복 계산 과정은 제거해야 한다. 본질적으로, 사용되지 않는 모든 계산은 삭제되어야 한다.
이더리움에서는 스토리지 공간을 해제할 때 가스 보상을 받을 수 있다. 더 이상 필요 없는 변수는 delete 키워드로 삭제하거나 기본값으로 설정해야 한다.
반복문 최적화: 고비용의 반복 연산을 피하고, 가능한 경우 반복문을 병합하며, 반복문 내부에서 반복 계산을 제거하라.
2. 프리컴파일된 계약 사용
프리컴파일된 계약은 암호화 및 해시 연산과 같은 복잡한 라이브러리 함수를 제공한다. 코드가 EVM 상에서 실행되는 것이 아니라 클라이언트 노드에서 로컬로 실행되기 때문에 더 적은 가스가 필요하다. 프리컴파일된 계약을 사용하면 스마트 계약 실행에 필요한 계산 작업량을 줄여 가스를 절약할 수 있다.
프리컴파일된 계약의 예로는 타원 곡선 디지털 서명 알고리즘(ECDSA)과 SHA2-256 해시 알고리즘이 있다. 스마트 계약에서 이러한 프리컴파일된 계약을 사용함으로써 개발자는 가스 비용을 줄이고 애플리케이션의 실행 효율성을 높일 수 있다.
이더리움 네트워크가 지원하는 프리컴파일된 계약의 전체 목록은 여기를 참조하라[4].
3. 인라인 어셈블리 코드 사용
인라인 어셈블리(inline assembly)는 개발자가 EVM에서 직접 실행 가능한 저수준이면서도 효율적인 코드를 작성할 수 있도록 하며, 비싼 Solidity 오퍼코드를 사용하지 않아도 된다. 또한 메모리 및 스토리지 사용에 대해 더욱 정밀한 제어가 가능하여 가스 요금을 추가로 줄일 수 있다. 더불어 인라인 어셈블리는 Solidity만으로는 어렵게 구현되는 복잡한 연산도 수행할 수 있어, 가스 소비 최적화에 더 많은 유연성을 제공한다.
다음은 인라인 어셈블리를 사용해 가스를 절약하는 코드 예제이다:

위 그림에서 알 수 있듯이, 표준 사용 사례와 비교했을 때 인라인 어셈블리 기술을 사용한 두 번째 사용 사례가 훨씬 높은 가스 효율성을 보인다.
그러나 인라인 어셈블리 사용은 위험을 수반하며 오류가 발생하기 쉽다. 따라서 신중하게 사용해야 하며, 숙련된 개발자만이 사용하는 것이 좋다.
4. 레이어 2 솔루션 사용
레이어 2 솔루션을 사용하면 이더리움 메인넷에 저장하고 계산해야 하는 데이터 양을 줄일 수 있다.
롤업(rollups), 사이드체인(sidechain), 상태 채널(state channel) 등의 레이어 2 솔루션은 트랜잭션 처리를 메인 이더리움 체인에서 분리함으로써 더 빠르고 저렴한 트랜잭션을 가능하게 한다.
대량의 트랜잭션을 묶어 처리함으로써, 이러한 솔루션은 체인 상 트랜잭션 수를 줄여 가스 요금을 낮춘다. 레이어 2 솔루션 사용은 또한 이더리움의 확장성을 향상시켜, 네트워크 과부하로 인한 혼잡 없이 더 많은 사용자와 애플리케이션이 네트워크에 참여할 수 있도록 한다.
5. 최적화 도구 및 라이브러리 사용
solc 최적화기, Truffle 빌드 최적화기, Remix의 Solidity 컴파일러 등 여러 최적화 도구를 사용할 수 있다.

이러한 도구들은 바이트코드 크기를 최소화하고, 사용하지 않는 코드를 제거하며, 스마트 계약 실행에 필요한 연산 횟수를 줄이는 데 도움을 준다. 「solmate」와 같은 기타 가스 최적화 라이브러리와 함께 사용하면 개발자는 효과적으로 가스 비용을 줄이고 스마트 계약의 효율성을 높일 수 있다.
결론
가스 소비 최적화는 거래 비용을 최소화하고 EVM 호환 네트워크 상의 스마트 계약 효율을 높이기 위한 개발자의 중요한 과정이다. 비용 절감 연산 우선 실행, 스토리지 사용 최소화, 인라인 어셈블리 활용 및 본문에서 논의한 기타 모범 사례들을 따르면 개발자는 계약의 가스 소비를 효과적으로 줄일 수 있다.
다만, 최적화 과정에서 개발자는 보안 취약점을 유발하지 않도록 주의 깊게 행동해야 한다. 코드 최적화 및 가스 소비 감소 과정에서 스마트 계약 본연의 보안성을 결코 희생해서는 안 된다.
[1] : https://ethereum.org/en/developers/docs/gas/
[2] : https://ethereum.github.io/yellowpaper/paper.pdf
[3] : https://www.evm.codes/
[4] : https://www.evm.codes/precompiled
TechFlow 공식 커뮤니티에 오신 것을 환영합니다
Telegram 구독 그룹:https://t.me/TechFlowDaily
트위터 공식 계정:https://x.com/TechFlowPost
트위터 영어 계정:https://x.com/BlockFlow_News














