오리지널

자세한 기술 설명 | 이더 스마트 계약의 가스 최적화: 상위 10가지 모범 사례

avatar
CertiK
12-27
이 기사는 기계로 번역되었습니다
원문 표시

이더리움 메인넷의 가스 비용은 오랫동안 해결되지 않은 문제였습니다. 특히 네트워크 혼잡 시 더욱 두드러집니다. 피크 시간에 사용자들은 종종 매우 높은 거래 수수료를 지불해야 합니다. 따라서 스마트 계약 개발 단계에서 가스 비용 최적화가 매우 중요합니다. 가스 소비를 최적화하면 거래 비용을 효과적으로 낮출 수 있을 뿐만 아니라 거래 효율성을 높여 사용자에게 더 경제적이고 효율적인 블록체인 사용 경험을 제공할 수 있습니다.

이 글에서는 이더리움 가상 머신(EVM)의 가스 비용 메커니즘, 가스 비용 최적화 관련 핵심 개념, 그리고 스마트 계약 개발 시 가스 비용 최적화를 위한 모범 사례를 개괄할 것입니다. 이를 통해 개발자에게 통찰력과 실용적인 도움을 제공하고, 일반 사용자들도 EVM의 가스 비용 운영 방식을 더 잘 이해하여 블록체인 생태계의 과제에 함께 대응할 수 있기를 희망합니다.

EVM의 가스 비용 메커니즘 소개

EVM 호환 네트워크에서 "가스"는 특정 작업을 실행하는 데 필요한 계산 능력을 측정하는 단위입니다.

아래 그림은 EVM의 구조 배치를 보여줍니다. 여기서 가스 소비는 세 부분으로 나뉩니다: 작업 실행, 외부 메시지 호출, 그리고 메모리 및 저장소의 읽기/쓰기.

출처: 이더리움 공식 웹사이트[1]

각 거래 실행에는 계산 자원이 필요하므로 무한 루프와 서비스 거부(DoS) 공격을 방지하기 위해 일정 수수료가 부과됩니다. 거래 완료에 필요한 수수료를 "가스 비용"이라고 합니다.

EIP-1559(런던 하드 포크) 이후 가스 비용은 다음 공식으로 계산됩니다:

가스 비용 = 사용된 가스 단위 * (기본 수수료 + 우선 수수료)

기본 수수료는 소각되고, 우선 수수료는 검증자에 대한 인센티브로 작용하여 거래를 다음 블록에 포함시키도록 장려합니다. 거래 전송 시 더 높은 우선 수수료를 설정하면 다음 블록에 포함될 가능성이 높아집니다. 이는 사용자가 검증자에게 지불하는 일종의 "팁"과 같습니다.

1. EVM의 가스 최적화 이해하기

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

모든 opcode(예: 계약 생성, 메시지 호출 수행, 계정 저장소 액세스, 가상 머신에서 작업 실행 등)에는 공인된 가스 소비 비용이 있으며, 이는 이더리움 노란색 문서[2]에 기록되어 있습니다.

여러 EIP 수정을 거치면서 일부 opcode의 가스 비용이 조정되어 노란색 문서와 다를 수 있습니다. 최신 opcode 비용 정보는 여기[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 또는 strings 대신 bytes32 데이터 유형을 사용하는 것이 좋습니다. 일반적으로 고정 크기 변수가 가변 크기 변수보다 가스 소비가 적습니다. bytes1에서 bytes32 중 가장 작은 길이를 선택하세요.

5. 매핑과 배열

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

직접 calldata에서 값을 읽을 때 중간 memory 작업을 건너뛰는 것입니다. 이러한 최적화 방식을 통해 실행 비용이 2,413 Gas 단위로 낮아졌으며, Gas 효율이 35% 향상되었습니다.

7. 가능한 한 Constant/Immutable 키워드 사용하기

Constant/Immutable 변수는 계약의 저장소에 저장되지 않습니다. 이러한 변수는 컴파일 시 계산되어 계약의 바이트코드에 저장됩니다. 따라서 저장소에 비해 액세스 비용이 훨씬 낮으므로, 가능한 한 Constant 또는 Immutable 키워드를 사용하는 것이 좋습니다.

8. 오버플로/언더플로가 발생하지 않는 경우 Unchecked 사용하기

개발자가 산술 연산이 오버플로 또는 언더플로를 일으키지 않을 것을 확신할 때, Solidity v0.8.0에서 도입된 unchecked 키워드를 사용하여 불필요한 오버플로 또는 언더플로 검사를 피할 수 있어 Gas 비용을 절감할 수 있습니다.

아래 그림에서 iunchecked 코드 블록에서 i를 증가시키는 것이 안전하고 Gas를 절감할 수 있습니다.

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

9. 수정자 최적화하기

수정자의 코드는 수정된 함수에 삽입됩니다. 수정자를 사용할 때마다 해당 코드가 복사됩니다. 이로 인해 바이트코드 크기가 증가하고 Gas 소비가 증가합니다. 다음은 수정자 Gas 비용을 최적화하는 방법입니다:

최적화 전:

최적화 후:

이 예에서는 논리를 내부 함수 _checkOwner()로 리팩토링하여 수정자에서 해당 내부 함수를 반복 사용할 수 있게 함으로써 바이트코드 크기를 줄이고 Gas 비용을 절감할 수 있습니다.

10. 단락 최적화

||와 && 연산자의 경우 논리 연산에서 단락 평가가 발생합니다. 즉, 첫 번째 조건만으로도 논리 표현식의 결과를 확인할 수 있다면 두 번째 조건은 평가되지 않습니다.

Gas 소비를 최적화하려면 계산 비용이 낮은 조건을 먼저 배치해야 합니다. 그러면 비용이 높은 계산을 건너뛸 수 있습니다.

추가 일반적인 제안

1. 사용되지 않는 코드 삭제하기

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

다음은 유용한 제안 사항입니다:

  • 계산에 가장 효율적인 알고리즘을 사용합니다. 계약에서 특정 계산 결과를 직접 사용하는 경우 해당 중복 계산 과정을 제거해야 합니다. 본질적으로 사용되지 않는 모든 계산은 삭제되어야 합니다.
  • 이더리움에서는 개발자가 저장 공간을 해제하면 Gas 보상을 받을 수 있습니다. 더 이상 필요하지 않은 변수가 있다면 delete 키워드를 사용하여 삭제하거나 기본값으로 설정해야 합니다.
  • 루프 최적화: 비용이 많이 드는 루프 작업을 피하고, 가능한 한 루프를 통합하며 반복 계산을 루프 외부로 이동시키십시오.

2. 사전 컴파일된 계약 사용하기

사전 컴파일된 계약은 암호화 및 해싱 작업과 같은 복잡한 라이브러리 함수를 제공합니다. 코드가 EVM에서 실행되는 것이 아니라 클라이언트 노드에서 로컬로 실행되므로 더 적은 Gas가 필요합니다. 사전 컴파일된 계약을 사용하면 스마트 계약 실행에 필요한 계산 작업량을 줄일 수 있어 Gas 비용을 절감할 수 있습니다.

사전 컴파일된 계약의 예로는 타원 곡선 디지털 서명 알고리즘(ECDSA)과 SHA2-256 해시 알고리즘이 있습니다. 이러한 사전 컴파일된 계약을 스마트 계약에서 사용하면 Gas 비용을 낮추고 애플리케이션 성능을 높일 수 있습니다.

이더리움 네트워크에서 지원되는 사전 컴파일된 계약의 전체 목록은 [4]를 참조하십시오.

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

인라인 어셈블리를 사용하면 개발자가 EVM에서 직접 실행할 수 있는 저수준이지만 효율적인 코드를 작성할 수 있습니다. 이를 통해 비싼 Solidity 작업 코드를 사용하지 않아도 됩니다. 인라인 어셈블리를 사용하면 메모리와 저장소 사용을 보다 정확하게 제어할 수 있어 Gas 비용을 추가로 절감할 수 있습니다. 또한 인라인 어셈블리를 통해 Solidity로는 구현하기 어려운 복잡한 작업을 수행할 수 있어 Gas 최적화를 위한 더 많은 유연성을 제공합니다.

다음은 인라인 어셈블리를 사용하여 Gas를 절감하는 코드 예시입니다:

위 그림에서 볼 수 있듯이, 표준 사용 사례와 비교할 때 인라인 어셈블리 기술을 사용한 두 번째 사용 사례가 더 높은 Gas 효율을 가지고 있습니다.

그러나 인라인 어셈블리 사용에는 위험이 따르고 실수하기 쉬우므로 경험이 풍부한 개발자만 주의 깊게 사용해야 합니다.

4. Layer 2 솔루션 사용하기

Layer 2 솔루션을 사용하면 이더리움 메인넷에 저장하고 계산해야 하는 데이터 양을 줄일 수 있습니다.

롤업, 사이드체인, 상태 채널 등의 Layer 2 솔루션은 주 이더리움 체인에서 거래 처리를 오프로드하여 더 빠르고 저렴한 거래를 가능하게 합니다. 이러한 솔루션은 많은 거래를 묶어서 체인에 올리는 횟수를 줄임으로써 Gas 비용을 낮출 수 있습니다. Layer 2 솔루션을 사용하면 이더리움의 확장성도 높아져 더 많은 사용자와 애플리케이션이 네트워크에 참여할 수 있게 되어 병목 현상을 방지할 수 있습니다.

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

solc 최적화 도구, Truffle의 빌드 최적화 도구, Remix의 Solidity 컴파일러 등 다양한 최적화 도구를 사용할 수 있습니다.

이러한 도구는 바이트코드 크기를 최소화하고, 사용되지 않는 코드를 제거하며, 스마트 계약 실행에 필요한 작업 수를 줄일 수 있습니다. "solmate"와 같은 Gas 최적화 라이브러리와 결합하면 개발자가 효과적으로 Gas 비용을 낮추고 스마트 계약 효율을 높일 수 있습니다.

결론

Gas 소비 최적화는 개발자에게 중요한 단계입니다. 이를 통해 거래 비용을 최소화하고 EVM 호환 네트워크의 스마트 계약 효율을 높일 수 있습니다. 비용 절감 작업 우선 실행, 저장소 사용 감소, 인라인 어셈블리 활용 등 본 문서에서 논의한 모범 사례를 따르면 계약의 Gas 소비를 효과적으로 줄일 수 있습니다.

그러나 최적화 과정에서 개발자는 안전성 문제를 유발하지 않도록 주의해야 합니다. 코드 최적화와 Gas 소비 감소 과정에서 스마트 계약의 고유 안전성은 절대 희생되어서는 안 됩니다.

[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

면책조항: 상기 내용은 작자의 개인적인 의견입니다. 따라서 이는 Followin의 입장과 무관하며 Followin과 관련된 어떠한 투자 제안도 구성하지 않습니다.
라이크
즐겨찾기에 추가
코멘트