저자: 이더리움 스마트 컨트랙트 개발자 기메르 세르베라 번역: 굿오바, 골든파이낸스
소개
이 글에서는 스마트 콘트랙트 최적화 및 보안을 위한 이더리움 가상머신(EVM)과 솔리디티 어셈블리에 대해 자세히 설명합니다.
이더넷 가상 머신(EVM)은 이더넷 네트워크의 핵심 구성 요소로, 솔리디티와 같은 고급 언어로 작성된 스마트 컨트랙트를 배포하고 실행할 수 있는 소프트웨어입니다. 계약이 작성되면 바이트코드로 컴파일되어 이더넷 네트워크의 모든 노드에서 실행되는 EVM에 배포됩니다.
솔리디티 어셈블리는 개발자가 EVM 자체에 더 가까운 수준에서 코드를 작성할 수 있는 저수준 프로그래밍 언어입니다. 스마트 컨트랙트 실행을 세밀하게 제어할 수 있어 상위 수준의 솔리디티 코드만으로는 불가능한 최적화와 커스터마이징이 가능합니다.
솔리디티에서 인라인 어셈블리에 사용되는 언어는 Yul이라고 하며, 이 프로그래밍 언어는 EVM 바이트코드로 컴파일하기 위한 매개체 역할을 합니다. 개발자가 스마트 컨트랙트의 실행을 더 세밀하게 제어할 수 있는 저수준 언어로 설계되었습니다. 솔리디티에서 독립형 모드로 사용하거나 인라인 어셈블리로 사용할 수 있으며, 율은 개발자가 보다 최적화되고 효율적인 코드를 작성할 수 있는 로우레벨 스택 기반 언어로 설계되었습니다. 솔리디티 어셈블리를 설명하기 전에 EVM의 컴포넌트가 어떻게 작동하는지 이해해야 합니다.
EVM은 준-튜링 완전 상태 머신입니다. 이 경우 "준"이라는 용어는 특정 스마트 컨트랙트 실행에 사용할 수 있는 가스 양에 따라 프로세스 실행이 유한한 수의 계산 단계로 제한된다는 것을 의미합니다. 이것이 이더리움이 실행이 (악의적으로 또는 실수로) 영원히 실행될 수 있는 상황과 중단 문제를 처리하는 방식입니다. 이를 통해 이더 플랫폼이 완전히 마비되는 것을 방지할 수 있습니다.
가스량은 이더리움에서 트랜잭션을 완료하는 데 필요한 계산량을 측정하는 개념입니다. 거래 비용은 이더로 지불되며 가스 및 가스 가격과 관련이 있습니다. 이 과정에서 우리의 목표는 보안을 손상시키지 않으면서 소비되는 총 가스 양을 최소화하는 방법을 배우는 것입니다.
코드 최적화 문제
인라인 어셈블리는 낮은 수준에서 EVM에 액세스하는 방법입니다. 인라인 어셈블리는 몇 가지 중요한 보안 기능을 우회하고 솔리디티에서 검사합니다. 인라인 어셈블리를 적절히 사용하면 실행 비용을 크게 줄일 수 있습니다. 하지만 인라인 어셈블리가 필요한 작업에만 사용해야 하며, 작업 내용을 잘 알고 있는 경우에만 사용해야 합니다. 인라인 어셈블리를 사용하여 코드를 최적화하면 코드에 새로운 보안 문제가 발생할 수 있습니다. 인라인 어셈블리를 마스터하려면 EVM과 그 구성 요소가 어떻게 작동하는지 이해해야 합니다.
EVM에서는 저장된 변수에 처음 액세스할 때마다 비용을 지불해야 하는데, 이를 "콜드"액세스라고 하며 2,100가스의 비용이 듭니다. 두 번째 또는 연속으로 액세스하는 것을 "핫"액세스라고 합니다. "hot" 액세스로, 100가스의 비용이 듭니다.
다음 코드는 Yul을 사용하여 코드를 최적화하는 방법의 예시입니다. SetData1 함수는 솔리디티를 사용하여 기존 방식으로 전역 변수 value에 새 값을 설정합니다. 이 새 값을 처음 할당할 때는 22514가스가 필요하지만, 두 번째 할당에서는 이보다 훨씬 적은 5414가스가 필요합니다.
소개
이 문서에서는 다음과 같은 일련의 기사를 소개합니다. 스마트 컨트랙트 최적화 및 보안을 위한 이더리움 가상 머신(EVM)과 솔리디티 어셈블리에 대해 자세히 살펴봅니다.
이더넷 가상 머신(EVM)은 이더넷 네트워크의 핵심 구성 요소로, 솔리디티와 같은 고급 언어로 작성된 스마트 컨트랙트를 배포하고 실행할 수 있는 소프트웨어입니다. 계약이 작성되면 바이트코드로 컴파일되어 이더넷 네트워크의 모든 노드에서 실행되는 EVM에 배포됩니다.
솔리디티 어셈블리는 개발자가 EVM 자체에 더 가까운 수준에서 코드를 작성할 수 있는 저수준 프로그래밍 언어입니다. 스마트 컨트랙트 실행을 세밀하게 제어할 수 있어 상위 수준의 솔리디티 코드만으로는 불가능한 최적화와 커스터마이징이 가능합니다.
솔리디티에서 인라인 어셈블리에 사용되는 언어는 Yul이라고 하며, 이 프로그래밍 언어는 EVM 바이트코드로 컴파일하기 위한 중개자 역할을 합니다. 개발자가 스마트 컨트랙트의 실행을 더 세밀하게 제어할 수 있는 저수준 언어로 설계되었습니다. 솔리디티에서 독립형 모드로 사용하거나 인라인 어셈블리로 사용할 수 있으며, 율은 개발자가 보다 최적화되고 효율적인 코드를 작성할 수 있는 로우레벨 스택 기반 언어로 설계되었습니다. 솔리디티 어셈블리를 설명하기 전에 EVM의 컴포넌트가 어떻게 작동하는지 이해해야 합니다.
EVM은 준-튜링 완전 상태 머신입니다. 이 경우 "준"이라는 용어는 특정 스마트 컨트랙트 실행에 사용할 수 있는 가스 양에 따라 프로세스 실행이 유한한 수의 계산 단계로 제한된다는 것을 의미합니다. 이것이 이더리움이 실행이 (악의적으로 또는 실수로) 영원히 실행될 수 있는 상황과 중단 문제를 처리하는 방식입니다. 이를 통해 이더 플랫폼이 완전히 마비되는 것을 방지할 수 있습니다.
가스량은 이더리움에서 트랜잭션을 완료하는 데 필요한 계산량을 측정하는 개념입니다. 거래 비용은 이더로 지불되며 가스 및 가스 가격과 관련이 있습니다. 이 과정에서 우리의 목표는 보안을 손상시키지 않으면서 소비되는 총 가스 양을 최소화하는 방법을 배우는 것입니다.
코드 최적화 문제
인라인 어셈블리는 낮은 수준에서 EVM에 액세스하는 방법입니다. 인라인 어셈블리는 몇 가지 중요한 보안 기능을 우회하고 솔리디티에서 검사합니다. 인라인 어셈블리를 적절히 사용하면 실행 비용을 크게 줄일 수 있습니다. 하지만 인라인 어셈블리가 필요한 작업에만 사용해야 하며, 작업 내용을 잘 알고 있는 경우에만 사용해야 합니다. 인라인 어셈블리를 사용하여 코드를 최적화하면 코드에 새로운 보안 문제가 발생할 수 있습니다. 인라인 어셈블리를 마스터하려면 EVM과 그 구성 요소가 어떻게 작동하는지 이해해야 합니다.
EVM에서는 저장된 변수에 처음 액세스할 때마다 비용을 지불해야 하는데, 이를 "콜드"액세스라고 하며 2,100가스의 비용이 듭니다. 두 번째 또는 연속으로 액세스하는 것을 "핫"액세스라고 합니다. "핫" 액세스는 100가스의 비용이 듭니다.
다음 코드는 Yul을 사용하여 코드를 최적화하는 방법의 예시입니다. SetData1 함수는 솔리디티를 사용하여 기존 방식으로 전역 변수 value에 새 값을 설정합니다. 이 새 값을 처음 할당할 때는 22514가스가 필요하지만, 두 번째 할당할 때는 훨씬 적은 5414가스가 필요합니다.

함수 setData2는 인라인 어셈블리를 구현합니다. 인라인 어셈블리 블록은 assembly { ... }로 표시되며, 여기서 중괄호 안의 코드는 Yul 언어의 코드입니다. 이 시점에서 소스 코드를 알 필요는 없으며, 소프트웨어가 하위 수준에서 스토리지에 액세스하고 있다는 점만 기억하면 됩니다. 결과적으로 구현 비용이 절감됩니다.
이 예제에서는 값을 처음 수정할 때 22484가스의 비용이 들지만, 여러 번 연속으로 수정하면 5384가스의 비용이 듭니다. 차이가 크지 않아 보일 수 있지만 이 코드가 수천 번 실행될 수 있다는 점을 고려해야 합니다.

스토리지가 왜 그렇게 비싼가요? 우리는 데이터가 한 곳이 아닌 수만 개의 노드에 저장되는 분산형 세상에 살고 있다는 점을 기억하세요. 또한 향후 트랜잭션에서 데이터에 액세스하거나 변경해야 하는 경우 네트워크의 모든 노드에서 쉽게 사용할 수 있어야 합니다. 해당 데이터의 전체 비용은 해당 데이터가 소비하는 저장 공간과 네트워크 전체에서 해당 데이터를 생성하는 데 들어가는 계산량을 합한 것과 같습니다.
EVM 스택, 스토리지, 메모리
EVM은 값을 저장하고 연산을 수행하는 스택이라는 데이터 구조에서 실행되는 스택 기반 머신입니다. 명령어(옵코드라고 함)가 있으며, 이는 저장소 읽기 및 쓰기, 다른 컨트랙트 호출, 수학적 연산 수행과 같은 작업을 수행하는 데 사용됩니다. 스택은 그림 1을 참조하면 가장 최근에 삽입된 항목이 스택의 맨 위에 저장되고 가장 먼저 삭제되는 항목이 되는 LIFO(Last In First Out) 방식으로 작동합니다.

스마트 컨트랙트를 실행할 때, EVM은 다양한 데이터 구조와 상태 변수가 포함된 실행 컨텍스트를 생성합니다. 실행이 완료되면 실행 컨텍스트는 삭제되어 다음 컨트랙트를 위해 준비됩니다. 실행하는 동안 EVM은 트랜잭션 간에 지속되지 않는 임시 메모리를 유지하며, 1024개 항목으로 구성된 스택 머신을 실행합니다. 각 항목은 256비트 단어이며, 이 크기는 256비트 해싱과 타원 곡선 암호화를 사용하기 쉽도록 선택되었습니다.
EVM의 구성 요소는 다음과 같습니다(그림 2 참조).
스택: EVM의 스택은 데이터 온더플라이(LIFO) 머신입니다. 스마트 컨트랙트 실행 중 임시 값을 저장하는 데 사용되는 데이터 구조입니다.
저장소: 이더리움 상태의 일부인 영구 저장소로, 처음 초기화할 때만 0이 됩니다.
메모리: 컨트랙트 실행 중 중간 데이터를 저장하는 데 사용되는 휘발성, 동적 크기의 바이트 배열입니다. 메모리는 새 실행 컨텍스트가 생성될 때마다 0으로 초기화됩니다.
콜데이터: 이 역시 메모리와 유사한 휘발성 데이터 저장 영역입니다. 하지만 불변 데이터를 저장합니다. 스마트 컨트랙트 트랜잭션의 일부로 전송된 데이터를 보관하도록 설계되었습니다.
프로그램 카운터: 프로그램 카운터(PC)는 EVM이 실행할 다음 명령어를 가리킵니다. 일반적으로 명령어가 실행된 후 PC는 1바이트씩 증가합니다.
가상 ROM: 스마트 콘트랙트는 이 영역에 바이트 코드로 저장됩니다. 가상 ROM은 읽기 전용입니다.

EVM 스택
이 아키텍처에서 프로그램의 명령어와 데이터는 는 메모리에 보관되며, 프로그램 실행은 스택의 맨 위를 가리키는 스택 포인터에 의해 제어됩니다. 스택 포인터는 스택에서 다음 값이나 명령어가 저장되거나 검색될 위치를 추적합니다. 프로그램이 실행되면 스택에 값을 추가하고 이미 존재하는 값에 대한 연산을 수행합니다. 코드에서 두 개의 숫자를 더하려는 경우 스택에 숫자를 누른 다음 상위 두 값에 대해 더하기 연산을 수행합니다. 그런 다음 결과가 스택으로 반환됩니다.

스택 기반 아키텍처의 가장 중요한 특징 중 하나는 매우 간단하고 효율적인 연산 실행이 가능하다는 점입니다. 스택은 LIFO 데이터 구조이므로 데이터와 명령어를 쉽고 빠르게 처리할 수 있습니다.
EVM에는 옵코드라는 자체 명령어 집합이 있습니다. 옵코드는 스토리지 읽기 및 쓰기, 다른 컨트랙트 호출, 수학적 연산 수행과 같은 작업을 수행하는 데 사용되며, EVM 명령어 세트는 다음과 같은 대부분의 연산을 제공합니다.
스택 연산: POP, PUSH, DUP, SWAP
산술/비교/위치별 연산: ADD, SUB, GT, LT, AND, OR
환경: 호출자, 호출 값, 숫자
메모리 연산: MLOAD, MSTORE, MSTORE8, MSIZE
저장 연산: SLOAD, SSTORE
프로그램 카운터 관련 연산 코드: JUMP, JUMPI, PC, JUMPDEST
프로그램 카운터 관련 연산 코드: MLOAD, MSTORE8, MSIZE
정지 코드: STOP, RETURN, REVERT, INVALID, SELFDESTRUCT
EVM 스토리지
EVM 스토리지는 256비트 -> 256비트 키-값 쌍을 저장하는 비휘발성 공간입니다. 컨트랙트의 총 저장소 슬롯 수는 2²⁵⁶로, 매우 많은 수의 슬롯이 있습니다. 블록체인의 각 스마트 콘트랙트에는 고유한 저장 공간이 있습니다.
저장소는 함수 호출 사이에 기억해야 하는 데이터를 위해 함수 호출 중에 사용됩니다. 스마트 컨트랙트 실행이 종료된 후에도 사용할 수 있어야 하는 변수와 데이터 구조를 저장하는 데 사용됩니다.

스토리지에 대한 액세스는 SLOAD 및 SSTORE 연산자로
제공됩니다. 이 계정의 저장소는 스마트 컨트랙트에서만 사용하는 영구 데이터 저장소입니다. 외부 소유 계정(EOA)은 항상 코드가 없고 저장소가 비어 있습니다.
EVM 메모리
메모리는 아키텍처에서 휘발성 메모리이며, 해당 데이터는 블록체인에서 지속적이지 않습니다. 인메모리는 스마트 컨트랙트 실행 중에 임시 데이터를 저장하는 랜덤 액세스 데이터 구조입니다.

메모리는 스테이징 공간을 위한 2개의 슬롯, 여유 메모리 포인터를 위한 1개의 슬롯, 사용 가능한 여유 메모리를 가리키는 0 및 1개의 슬롯 등 4개의 섹션으로 나뉩니다. 처음 64바이트의 공간은 최종 출력을 반환할 때까지 중간 출력을 저장할 임시 공간이 필요한 해시 메서드에서 사용됩니다.
유휴 메모리 포인터는 단순히 사용 가능한 메모리의 시작을 가리키는 포인터입니다. 이는 스마트 컨트랙트가 어떤 메모리 위치에 기록되었고 어떤 메모리가 여전히 사용 가능한지 추적할 수 있도록 합니다. 이는 컨트랙트가 다른 변수에 할당된 일부 메모리를 덮어쓰는 것을 방지합니다. 그림 6은 메모리가 어떻게 분할되는지 보여줍니다.

메모리는 메모리에 보관할 필요가 없는 변수와 데이터 구조를 저장하는 데 사용됩니다. 스마트 컨트랙트 실행 중에 메모리 크기를 조정할 수 있지만, 스택에 비해 접근 속도가 느리고 비용이 많이 듭니다.
메모리가 0으로 초기화되고 메모리에 액세스하는 데 사용되는 옵코드는 MLOAD, MSTORE, MSTORE8
전체
이 문서에서는 이더넷 가상 머신(EVM)과 관련된 몇 가지 기본 개념을 검토했습니다. 인라인 어셈블리 코드를 구현하려면 EVM에 대한 심층적인 이해가 필요합니다. 이는 우리가 EVM의 일부 구성 요소와 상호 작용하기 때문입니다. 이후 강의에서는 스토리지, 메모리, 콜데이터와 같은 다른 EVM 요소를 더 자세히 분석하고 바이트코드, 가스, 애플리케이션 바이너리 인터페이스(ABI)와 같은 중요한 개념을 검토할 것입니다. 마지막으로 스마트 컨트랙트 실행을 안전하게 최적화하기 위한 옵코드 작동 방식과 더 많은 인라인 어셈블리 예제에 대해 설명합니다.