이더리움 메인 네트워크의 가스 요금은 항상 지속적인 문제였으며, 특히 네트워크가 혼잡할 때 더욱 그렇습니다. 피크 기간 동안 사용자는 종종 매우 높은 거래 수수료를 지불해야 합니다. 따라서 스마트 계약 개발 단계에서 가스 요금을 최적화하는 것이 특히 중요합니다. 가스 소비를 최적화하면 거래 비용을 효과적으로 줄일 수 있을 뿐만 아니라 거래 효율성을 향상시켜 사용자에게 보다 경제적이고 효율적인 블록체인 경험을 제공할 수 있습니다.

이 글에서는 EVM(Ethereum Virtual Machine)의 가스 수수료 메커니즘, 가스 수수료 최적화와 관련된 핵심 개념, 스마트 계약 개발 시 가스 수수료 최적화 모범 사례에 대해 간략하게 설명합니다. 우리는 이러한 콘텐츠를 통해 개발자에게 영감과 실질적인 도움을 제공하고 일반 사용자가 EVM의 가스 요금이 어떻게 작동하는지 더 잘 이해하고 블록체인 생태계의 과제에 공동으로 대처할 수 있도록 도울 수 있기를 바랍니다.

EVM의 가스 요금 메커니즘 소개

EVM 호환 네트워크에서 "가스"는 특정 작업을 수행하는 데 필요한 컴퓨팅 성능을 측정하는 데 사용되는 단위를 나타냅니다.

다음 그림은 EVM의 구조적 레이아웃을 보여줍니다. 그림에서 가스 소비는 작업 실행, 외부 메시지 호출, 메모리 및 저장소 읽기 및 쓰기의 세 부분으로 나뉩니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

 출처: 이더리움 공식 홈페이지[1]

각 트랜잭션을 실행하려면 컴퓨팅 리소스가 필요하므로 무한 루프 및 서비스 거부(DoS) 공격을 방지하기 위해 수수료가 부과됩니다. 거래를 완료하는 데 필요한 수수료를 "가스 수수료"라고 합니다.

EIP-1559(런던 하드 포크)가 발효된 이후 가스 요금은 다음 공식으로 계산됩니다.

가스 요금 = 사용된 가스 단위 * (기본 요금 + 우선 요금)

기본 수수료는 소각되고 우선 수수료는 검증인이 블록체인에 거래를 추가하도록 하는 인센티브 역할을 합니다. 거래를 보낼 때 더 높은 우선순위 수수료를 설정하면 해당 거래가 다음 블록에 포함될 가능성이 높아집니다. 이는 사용자가 검증자에게 지불하는 "팁"과 유사합니다.

1. EVM의 가스 최적화 이해

Solidity로 스마트 계약을 컴파일할 때 계약은 일련의 "opcode" 또는 opcode로 변환됩니다.

모든 작업 코드(예: 계약 생성, 메시지 호출, 계정 저장소 액세스, 가상 머신에서 작업 수행)에는 가스 소비 비용이 인정되며 이는 Ethereum Yellow Paper [2]에 기록되어 있습니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

많은 EIP 수정 후에 일부 opcode의 가스 비용이 조정되었으며 Yellow Book의 가스 비용과 다를 수 있습니다. opcode의 최신 비용에 대한 자세한 내용은 여기에서 확인할 수 있습니다[3].

2.가스 최적화의 기본 개념

가스 최적화의 핵심 개념은 EVM 블록체인에서 비용 효율적인 운영을 우선시하고 값비싼 가스 비용 운영을 피하는 것입니다.

EVM에서는 다음 작업이 비용이 더 저렴합니다.

  • 메모리 변수 읽기 및 쓰기
  • 상수 및 불변 변수 읽기
  • 지역 변수 읽기 및 쓰기
  • calldata 배열 및 구조와 같은 calldata 변수 읽기
  • 내부 함수 호출

비용이 많이 드는 작업에는 다음이 포함됩니다.

  • 계약 저장소에 저장된 상태 변수 읽기 및 쓰기
  • 외부 함수 호출
  • 루프 작동

EVM 가스 비용 최적화 모범 사례

위의 기본 개념을 바탕으로 우리는 개발자 커뮤니티를 위한 가스 요금 최적화 모범 사례 목록을 작성했습니다. 이러한 관행을 따르면 개발자는 스마트 계약 가스 소비를 줄이고 거래 비용을 낮추며 보다 효율적이고 사용자 친화적인 애플리케이션을 만들 수 있습니다.

1. 스토리지 사용량 최소화

Solidity에서 스토리지는 제한된 리소스이며 가스 소비량은 메모리보다 훨씬 높습니다. 스마트 계약이 스토리지에서 데이터를 읽거나 쓸 때마다 높은 가스 비용이 발생합니다.

Ethereum Yellow Paper의 정의에 따르면 스토리지 작업 비용은 메모리 작업 비용보다 100배 이상 높습니다. 예를 들어, OPcodesmload 및 mstore 명령어는 3개의 가스 단위만 소비하는 반면, sload 및 sstore와 같은 저장 작업에는 최상의 경우에도 최소 100단위의 비용이 듭니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

저장소 사용량을 제한하는 방법은 다음과 같습니다.

  • 비영구적 데이터를 메모리에 저장
  • 저장소 수정 횟수를 줄입니다. 중간 결과를 메모리에 저장한 다음 모든 계산이 완료된 후 결과를 저장소 변수에 할당합니다.

2. 가변 포장

스마트 계약에 사용되는 저장 슬롯 수와 개발자가 데이터를 표현하는 방식은 가스 요금 소비에 큰 영향을 미칩니다.

Solidity 컴파일러는 컴파일 프로세스 중에 연속적인 저장 변수를 패킹하고 32바이트 저장 슬롯을 변수 저장의 기본 단위로 사용합니다. 변수 패키징이란 여러 변수가 하나의 저장 슬롯에 들어갈 수 있도록 변수를 적절하게 배열하는 것을 말합니다.

왼쪽은 3개의 스토리지 슬롯을 사용하는 덜 효율적인 구현이고, 오른쪽은 더 효율적인 구현입니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

이 세부 사항을 조정함으로써 개발자는 20,000개의 가스 단위를 절약할 수 있지만(사용하지 않는 저장소 슬롯을 저장하는 데 20,000개의 가스가 필요함) 이제는 2개의 저장소 슬롯만 필요합니다.

각 저장 슬롯은 가스를 소비하므로 가변 패킹은 필요한 저장 슬롯 수를 줄여 가스 사용량을 최적화합니다.

3. 데이터 유형 최적화

변수는 여러 데이터 유형으로 표시될 수 있지만 데이터 유형에 따라 운영 비용이 다릅니다. 적절한 데이터 유형을 선택하면 가스 사용량을 최적화하는 데 도움이 됩니다.

예를 들어 Solidity에서는 정수를 uint8, uint16, uint32 등 다양한 크기로 세분화할 수 있습니다. EVM은 256비트 단위로 작동하므로 uint8을 사용한다는 것은 EVM이 먼저 이를 uint256으로 변환해야 함을 의미하며 이 변환에는 추가 Gas가 소비됩니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

그림의 코드를 통해 uint8과 uint256의 Gas 비용을 비교할 수 있습니다. UseUint() 함수는 120,382 가스 단위를 소비하는 반면 UseUInt8() 함수는 166,111 가스 단위를 소비합니다.

개별적으로 살펴보면 uint256을 사용하는 것이 uint8보다 저렴합니다. 그러나 앞서 제안한 변수 패킹 최적화를 사용하면 다릅니다. 개발자가 4개의 uint8 변수를 단일 스토리지 슬롯에 넣을 수 있는 경우 이를 반복하는 데 드는 총 비용은 4개의 uint256 변수보다 낮습니다. 이런 방식으로 스마트 계약은 저장소 슬롯을 한 번 읽고 쓸 수 있으며 한 번의 작업으로 4개의 uint8 변수를 메모리/저장소에 넣을 수 있습니다.

4. 동적 변수 대신 고정 크기 변수를 사용하세요.

32바이트 이내에서 데이터를 제어할 수 있는 경우에는 바이트나 문자열 대신 bytes32 데이터 형식을 사용하는 것이 좋습니다. 일반적으로 고정 크기 변수는 가변 크기 변수보다 가스를 덜 소비합니다. 바이트 길이를 제한할 수 있는 경우 bytes1에서 bytes32까지 최소 길이를 선택해 보십시오.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

5. 매핑과 배열

Solidity의 데이터 목록은 배열(Arrays)과 매핑(Mappings)의 두 가지 데이터 유형으로 표현할 수 있지만 구문과 구조는 완전히 다릅니다.

대부분의 경우 맵은 더 효율적이고 저렴하지만 배열은 반복 가능하고 데이터 유형 패킹을 지원합니다. 따라서 반복이 필요하거나 데이터 유형 패키징을 통해 가스 소비를 최적화할 수 있는 경우가 아니면 데이터 목록을 관리할 때 먼저 매핑을 사용하는 것이 좋습니다.

6. 메모리 대신 호출 데이터를 사용하세요

함수 매개변수에 선언된 변수는 calldata나 메모리에 저장될 수 있습니다. 둘 사이의 주요 차이점은 메모리는 함수로 수정할 수 있지만 호출 데이터는 변경할 수 없다는 것입니다.

이 원칙을 기억하십시오. 함수 매개변수가 읽기 전용인 경우 메모리보다 calldata를 우선적으로 사용해야 합니다. 이렇게 하면 함수 호출 데이터에서 메모리로 불필요한 복사를 방지할 수 있습니다.

예시 1: 메모리 사용

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

memory 키워드를 사용하면 ABI 디코딩 중에 배열 값이 인코딩된 호출 데이터에서 메모리로 복사됩니다. 이 코드 블록의 실행 비용은 3,694 가스 단위입니다.

예시 2: 통화 데이터 사용

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

calldata에서 직접 값을 읽는 경우 중간 메모리 작업을 건너뜁니다. 이 최적화 방법은 실행 비용을 2,413 Gas 단위로 줄이고 Gas 효율성을 35% 높입니다.

7. 가능하면 상수/불변 키워드를 사용하세요.

상수/불변 변수는 계약 저장소에 저장되지 않습니다. 이러한 변수는 컴파일 타임에 계산되어 계약의 바이트코드에 저장됩니다. 따라서 스토리지보다 액세스 비용이 훨씬 저렴하며 가능하면 Constant 또는 Immutable 키워드를 사용하는 것이 좋습니다.

8. 오버플로/언더플로가 발생하지 않도록 하려면 선택하지 않음을 사용하세요.

개발자가 산술 연산으로 인해 오버플로나 언더플로가 발생하지 않는다고 확신하는 경우 Solidity v0.8.0에 도입된 unchecked 키워드를 사용하여 중복된 오버플로 또는 언더플로 검사를 방지함으로써 가스 비용을 절약할 수 있습니다.

아래 그림에서 i 조건에 따라

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

또한 컴파일러 버전 0.8.0 이상에서는 컴파일러 자체에 오버플로 및 언더플로 보호 기능이 내장되어 있으므로 더 이상 SafeMath 라이브러리를 사용할 필요가 없습니다.

9. 수정자 최적화

수정자의 코드는 수정된 함수에 내장되어 있으며 수정자를 사용할 때마다 해당 코드가 복사됩니다. 이는 바이트코드 크기와 가스 소비를 증가시킵니다. 수정자의 가스 비용을 최적화하는 한 가지 방법은 다음과 같습니다.

최적화 전:

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

최적화 후:

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

이 경우 로직을 내부 함수 _checkOwner()로 리팩터링하여 수정자에서 내부 함수를 재사용할 수 있도록 함으로써 바이트코드 크기가 줄어들고 가스 비용이 절감됩니다.

10. 단락 최적화

|| 및 && 연산자의 경우 논리 연산은 단락 평가를 거칩니다. 즉, 첫 번째 조건이 이미 논리 표현식의 결과를 결정할 수 있는 경우 두 번째 조건은 평가되지 않습니다.

Gas 소비를 최적화하기 위해서는 계산 비용이 낮은 조건을 먼저 배치해야 비용이 많이 드는 계산을 건너뛸 수 있습니다.

추가 일반 조언

1. 쓸모없는 코드 삭제

계약서에 사용되지 않는 함수나 변수가 있는 경우 삭제하는 것이 좋습니다. 이는 계약 배포 비용을 줄이고 계약 규모를 작게 유지하는 가장 직접적인 방법입니다.

다음은 몇 가지 실용적인 제안입니다.

계산에 가장 효율적인 알고리즘을 사용하십시오. 특정 계산의 결과가 계약에 직접 사용되는 경우 이러한 중복 계산 프로세스를 제거해야 합니다. 기본적으로 사용하지 않는 계산은 모두 삭제해야 합니다.

이더리움에서 개발자는 저장 공간을 공개하여 가스 보상을 얻을 수 있습니다. 변수가 더 이상 필요하지 않으면 delete 키워드를 사용하여 삭제하거나 기본값으로 설정해야 합니다.

루프 최적화: 비용이 많이 드는 루프 작업을 피하고, 루프를 최대한 병합하고, 반복되는 계산을 루프 본문 밖으로 이동합니다.

2. 미리 컴파일된 계약 사용

사전 컴파일된 계약은 암호화 및 해싱 작업과 같은 복잡한 라이브러리 기능을 제공합니다. 코드는 EVM에서 실행되지 않고 클라이언트 노드에서 로컬로 실행되므로 가스가 덜 필요합니다. 사전 컴파일된 계약을 사용하면 스마트 계약을 실행하는 데 필요한 계산 노력을 줄여 가스를 절약할 수 있습니다.

사전 컴파일된 계약의 예로는 ECDSA(Elliptic Curve Digital Signature Algorithm) 및 SHA2-256 해싱 알고리즘이 있습니다. 스마트 계약에서 이러한 사전 컴파일된 계약을 사용함으로써 개발자는 가스 비용을 줄이고 애플리케이션 운영 효율성을 향상시킬 수 있습니다.

Ethereum 네트워크에서 지원하는 사전 컴파일된 계약의 전체 목록은 여기를 참조하세요[4].

3. 인라인 어셈블리 코드 사용

인라인 어셈블리를 사용하면 개발자는 값비싼 Solidity 연산 코드를 사용하지 않고도 EVM에서 직접 실행할 수 있는 저수준이지만 효율적인 코드를 작성할 수 있습니다. 또한 인라인 조립을 통해 메모리 및 스토리지 사용량을 더욱 정밀하게 제어할 수 있어 가스 요금이 더욱 절감됩니다. 또한 인라인 어셈블리는 Solidity만으로는 달성하기 어려운 일부 복잡한 작업을 수행할 수 있으므로 가스 소비 최적화를 위한 더 많은 유연성을 제공합니다.

다음은 인라인 어셈블리를 사용하여 가스를 절약하는 코드 예제입니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

위 그림에서 볼 수 있듯이 인라인 조립 기술을 사용한 두 번째 사용 사례는 표준 사용 사례에 비해 가스 효율이 더 높습니다.

그러나 인라인 어셈블리를 사용하는 것도 위험하고 오류가 발생하기 쉽습니다. 따라서 숙련된 개발자만 주의해서 사용해야 합니다.

4. 레이어 2 솔루션 사용

레이어 2 솔루션을 사용하면 이더리움 메인넷에 저장하고 계산해야 하는 데이터의 양이 줄어듭니다.

롤업, 사이드체인, 상태 채널과 같은 레이어 2 솔루션은 메인 이더리움 체인에서 트랜잭션 처리를 오프로드하여 더 빠르고 저렴한 트랜잭션을 가능하게 합니다.

이러한 솔루션은 많은 수의 트랜잭션을 하나로 묶음으로써 온체인 트랜잭션 수를 줄여 가스 요금을 낮춥니다. 레이어 2 솔루션을 사용하면 Ethereum의 확장성이 향상되어 네트워크에 과부하가 걸리거나 정체가 발생하지 않고 더 많은 사용자와 애플리케이션이 네트워크에 참여할 수 있습니다.

5. 최적화 도구 및 라이브러리 사용

solc 최적화 프로그램, Truffle의 빌드 최적화 프로그램, Remix의 Solidity 컴파일러와 같은 여러 최적화 도구를 사용할 수 있습니다.

이더리움 스마트 계약의 가스 최적화를 위한 상위 10가지 모범 사례

이러한 도구는 바이트코드 크기를 최소화하고 데드 코드를 제거하며 스마트 계약을 실행하는 데 필요한 작업 수를 줄이는 데 도움이 될 수 있습니다. "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