본문으로 건너뛰기

황서의 EVM 사양 이해하기

evm
중급
qbzzt
2022년 5월 15일
34 분 소요

황서 (opens in a new tab)는 이더리움의 공식 사양입니다. EIP 프로세스에 의해 수정된 경우를 제외하고, 모든 것이 어떻게 작동하는지에 대한 정확한 설명이 포함되어 있습니다. 이 문서는 수학 논문 형식으로 작성되어 프로그래머에게 익숙하지 않은 용어가 포함되어 있을 수 있습니다. 이 문서에서는 황서를 읽는 방법을 배우고, 더 나아가 다른 관련 수학 논문을 읽는 방법도 알아봅니다.

어떤 황서인가요?

이더리움의 거의 모든 것과 마찬가지로 황서도 시간이 지남에 따라 발전합니다. 특정 버전을 참조할 수 있도록 작성 당시의 현재 버전을 업로드했습니다. 제가 사용하는 섹션, 페이지 및 수식 번호는 해당 버전을 참조합니다. 이 문서를 읽는 동안 다른 창에 띄워두는 것이 좋습니다.

왜 EVM인가요?

원본 황서는 이더리움 개발 초기에 작성되었습니다. 이 문서는 원래 네트워크를 보호하는 데 사용되었던 초기 작업증명(PoW) 기반 합의 메커니즘을 설명합니다. 하지만 이더리움은 2022년 9월에 작업증명(PoW)을 종료하고 지분 증명(PoS) 기반 합의를 사용하기 시작했습니다. 이 튜토리얼은 이더리움 가상 머신(EVM)을 정의하는 황서의 부분에 초점을 맞출 것입니다. EVM은 지분 증명(PoS)으로의 전환에 의해 변경되지 않았습니다(DIFFICULTY 연산 코드의 반환 값 제외).

9 실행 모델

이 섹션(12-14페이지)에는 EVM 정의의 대부분이 포함되어 있습니다.

시스템 상태(system state)라는 용어는 시스템을 실행하기 위해 알아야 할 모든 것을 포함합니다. 일반적인 컴퓨터에서는 메모리, 레지스터의 내용 등을 의미합니다.

튜링 머신(Turing machine) (opens in a new tab)은 계산 모델입니다. 본질적으로 이것은 일반 컴퓨터가 계산을 실행할 수 있는 것과 동일한 능력을 가진 것으로 증명된 컴퓨터의 단순화된 버전입니다(컴퓨터가 계산할 수 있는 모든 것은 튜링 머신도 계산할 수 있으며 그 반대도 마찬가지입니다). 이 모델은 무엇이 계산 가능하고 무엇이 계산 불가능한지에 대한 다양한 정리를 증명하기 쉽게 해줍니다.

튜링 완전(Turing-complete) (opens in a new tab)이라는 용어는 튜링 머신과 동일한 계산을 실행할 수 있는 컴퓨터를 의미합니다. 튜링 머신은 무한 루프에 빠질 수 있지만, EVM은 가스가 고갈되기 때문에 그럴 수 없으므로 준 튜링 완전(quasi-Turing-complete)에 불과합니다.

9.1 기본 사항

이 섹션에서는 EVM의 기본 사항과 다른 계산 모델과 어떻게 비교되는지 설명합니다.

스택 머신(stack machine) (opens in a new tab)은 중간 데이터를 레지스터가 아닌 스택 (opens in a new tab)에 저장하는 컴퓨터입니다. 이것은 구현하기 쉽기 때문에 버그와 보안 취약성이 발생할 가능성이 훨씬 적다는 것을 의미하므로 가상 머신에서 선호되는 아키텍처입니다. 스택의 메모리는 256비트 워드로 나뉩니다. 이것은 케착-256 해싱 및 타원 곡선 계산과 같은 이더리움의 핵심 암호화 작업에 편리하기 때문에 선택되었습니다. 스택의 최대 크기는 1024개 항목(1024 x 256비트)입니다. 연산 코드가 실행될 때 일반적으로 스택에서 매개변수를 가져옵니다. POP(스택 맨 위에서 항목 제거), DUP_N(스택에서 N번째 항목 복제) 등과 같이 스택의 요소를 재구성하기 위한 전용 연산 코드가 있습니다.

EVM에는 실행 중에 데이터를 저장하는 데 사용되는 메모리라는 휘발성 공간도 있습니다. 이 메모리는 32바이트 워드로 구성됩니다. 모든 메모리 위치는 0으로 초기화됩니다. 메모리에 워드를 추가하기 위해 이 Yul (opens in a new tab) 코드를 실행하면 워드의 빈 공간을 0으로 채워 32바이트의 메모리를 채웁니다. 즉, 위치 0-29에는 0, 30에는 0x60, 31에는 0xA7이 있는 하나의 워드를 생성합니다.

mstore(0, 0x60A7)

mstore은 EVM이 메모리와 상호 작용하기 위해 제공하는 세 가지 연산 코드 중 하나이며, 메모리에 워드를 로드합니다. 나머지 두 개는 단일 바이트를 메모리에 로드하는 mstore8와 메모리에서 스택으로 워드를 이동하는 mload입니다.

EVM에는 시스템 상태의 일부로 유지되는 별도의 비휘발성 스토리지(storage) 모델도 있습니다. 이 메모리는 (스택의 워드 주소 지정 가능 바이트 배열과 달리) 워드 배열로 구성됩니다. 이 스토리지는 컨트랙트가 영구 데이터를 보관하는 곳입니다. 컨트랙트는 자체 스토리지와만 상호 작용할 수 있습니다. 스토리지는 키-값 매핑으로 구성됩니다.

황서의 이 섹션에는 언급되어 있지 않지만, 네 번째 유형의 메모리가 있다는 것을 아는 것도 유용합니다. 콜 데이터(Calldata)는 트랜잭션의 data 매개변수와 함께 전달된 값을 저장하는 데 사용되는 바이트 주소 지정 가능 읽기 전용 메모리입니다. EVM에는 calldata를 관리하기 위한 특정 연산 코드가 있습니다. calldatasize는 데이터의 크기를 반환합니다. calldataload는 데이터를 스택에 로드합니다. calldatacopy는 데이터를 메모리에 복사합니다.

표준 폰 노이만 아키텍처(Von Neumann architecture) (opens in a new tab)는 코드와 데이터를 동일한 메모리에 저장합니다. EVM은 보안상의 이유로 이 표준을 따르지 않습니다. 휘발성 메모리를 공유하면 프로그램 코드를 변경할 수 있기 때문입니다. 대신 코드는 스토리지에 저장됩니다.

코드가 메모리에서 실행되는 경우는 두 가지뿐입니다.

  • 컨트랙트가 다른 컨트랙트를 생성할 때(CREATE (opens in a new tab) 또는 CREATE2 (opens in a new tab) 사용), 컨트랙트 생성자의 코드는 메모리에서 가져옵니다.
  • 모든 컨트랙트 생성 중에 생성자 코드가 실행된 다음 실제 컨트랙트의 코드와 함께 반환되며, 이 역시 메모리에서 가져옵니다.

예외적 실행(exceptional execution)이라는 용어는 현재 컨트랙트의 실행을 중단시키는 예외를 의미합니다.

9.2 수수료 개요

이 섹션에서는 가스 수수료가 어떻게 계산되는지 설명합니다. 세 가지 비용이 있습니다.

연산 코드 비용

특정 연산 코드의 고유 비용입니다. 이 값을 얻으려면 부록 H(28페이지, 수식 (327) 아래)에서 연산 코드의 비용 그룹을 찾고, 수식 (324)에서 비용 그룹을 찾습니다. 이렇게 하면 비용 함수를 얻을 수 있으며, 대부분의 경우 부록 G(27페이지)의 매개변수를 사용합니다.

예를 들어, 연산 코드 CALLDATACOPY (opens in a new tab)Wcopy 그룹의 멤버입니다. 해당 그룹의 연산 코드 비용은 Gverylow+Gcopy×⌈μs[2]÷32⌉입니다. 부록 G를 보면 두 상수 모두 3임을 알 수 있으며, 이는 3+3×⌈μs[2]÷32⌉이 됩니다.

우리는 여전히 ⌈μs[2]÷32⌉라는 표현을 해독해야 합니다. 가장 바깥쪽 부분인 ⌈ <value> ⌉는 올림 함수(ceiling function)로, 값이 주어지면 해당 값보다 작지 않은 가장 작은 정수를 반환하는 함수입니다. 예를 들어, ⌈2.5⌉ = ⌈3⌉ = 3입니다. 안쪽 부분은 μs[2]÷32입니다. 3페이지의 섹션 3(규칙)을 보면 μ는 머신 상태입니다. 머신 상태는 13페이지의 섹션 9.4.1에 정의되어 있습니다. 해당 섹션에 따르면 머신 상태 매개변 중 하나는 스택을 나타내는 s입니다. 모두 종합해 보면 μs[2]는 스택의 위치 #2인 것 같습니다. 연산 코드 (opens in a new tab)를 보면 스택의 위치 #2는 바이트 단위의 데이터 크기입니다. Wcopy 그룹의 다른 연산 코드인 CODECOPY (opens in a new tab)RETURNDATACOPY (opens in a new tab)를 보면 동일한 위치에 데이터 크기가 있습니다. 따라서 ⌈μs[2]÷32⌉는 복사되는 데이터를 저장하는 데 필요한 32바이트 워드의 수입니다. 모든 것을 종합하면 CALLDATACOPY (opens in a new tab)의 고유 비용은 3가스에 복사되는 데이터의 워드당 3가스를 더한 값입니다.

실행 비용

우리가 호출하는 코드를 실행하는 비용입니다.

메모리 확장 비용

메모리를 확장하는 비용입니다(필요한 경우).

수식 324에서 이 값은 Cmemi')-Cmemi)로 작성됩니다. 섹션 9.4.1을 다시 보면 μi가 메모리의 워드 수임을 알 수 있습니다. 따라서 μi는 연산 코드 이전의 메모리 워드 수이고 μi'는 연산 코드 이후의 메모리 워드 수입니다.

함수 Cmem은 수식 326에 정의되어 있습니다: Cmem(a) = Gmemory × a + ⌊a2 ÷ 512⌋. ⌊x⌋는 내림 함수(floor function)로, 값이 주어지면 해당 값보다 크지 않은 가장 큰 정수를 반환하는 함수입니다. 예를 들어, ⌊2.5⌋ = ⌊2⌋ = 2입니다. a < √512일 때 a2 < 512이며 내림 함수의 결과는 0입니다. 따라서 처음 22개 워드(704바이트)의 경우 비용은 필요한 메모리 워드 수에 따라 선형적으로 증가합니다. 그 지점을 넘어서면 ⌊a2 ÷ 512⌋는 양수가 됩니다. 필요한 메모리가 충분히 높으면 가스 비용은 메모리 양의 제곱에 비례합니다.

참고로 이러한 요소는 고유한 가스 비용에만 영향을 미칩니다. 최종 사용자가 지불해야 하는 금액을 결정하는 수수료 시장이나 검증자에 대한 팁은 고려하지 않습니다. 이것은 EVM에서 특정 작업을 실행하는 데 드는 순수 비용일 뿐입니다.

가스에 대해 자세히 알아보기.

9.3 실행 환경

실행 환경은 블록체인 상태나 EVM의 일부가 아닌 정보를 포함하는 튜플 I입니다.

매개변수데이터에 액세스하기 위한 연산 코드데이터에 액세스하기 위한 Solidity 코드
IaADDRESS (opens in a new tab)address(this)
IoORIGIN (opens in a new tab)tx.origin
IpGASPRICE (opens in a new tab)tx.gasprice
IdCALLDATALOAD (opens in a new tab)msg.data
IsCALLER (opens in a new tab)msg.sender
IvCALLVALUE (opens in a new tab)msg.value
IbCODECOPY (opens in a new tab)address(this).code
IHNUMBER (opens in a new tab)DIFFICULTY (opens in a new tab)와 같은 블록 헤더 필드block.number, block.difficulty
Ie컨트랙트 간 호출(컨트랙트 생성 포함)을 위한 호출 스택의 깊이
IwEVM이 상태를 변경하도록 허용되는지, 아니면 정적으로 실행되는지 여부

섹션 9의 나머지 부분을 이해하려면 몇 가지 다른 매개변수가 필요합니다.

매개변수정의된 섹션의미
σ2(2페이지, 수식 1)블록체인의 상태
g9.3(13페이지)남은 가스
A6.1(8페이지)누적된 하위 상태(트랜잭션이 끝날 때 예정된 변경 사항)
o9.3(13페이지)출력 - 내부 트랜잭션(한 컨트랙트가 다른 컨트랙트를 호출할 때) 및 뷰 함수 호출(단지 정보를 요청하는 것이므로 트랜잭션을 기다릴 필요가 없을 때)의 경우 반환된 결과

9.4 실행 개요

이제 모든 준비가 끝났으므로 마침내 EVM이 어떻게 작동하는지 살펴볼 수 있습니다.

수식 137-142는 EVM을 실행하기 위한 초기 조건을 제공합니다.

기호초기값의미
μgg남은 가스
μpc0프로그램 카운터, 실행할 다음 명령어의 주소
μm(0, 0, ...)메모리, 모두 0으로 초기화됨
μi0사용된 가장 높은 메모리 위치
μs()스택, 처음에는 비어 있음
μo출력, 반환 데이터와 함께 중지하거나(RETURN (opens in a new tab) 또는 REVERT (opens in a new tab)) 반환 데이터 없이 중지할 때까지(STOP (opens in a new tab) 또는 SELFDESTRUCT (opens in a new tab)) 빈 집합입니다.

수식 143은 실행 중 각 시점에 네 가지 가능한 조건이 있으며, 이를 어떻게 처리해야 하는지 알려줍니다.

  1. Z(σ,μ,A,I). Z는 작업이 잘못된 상태 전환을 생성하는지 테스트하는 함수를 나타냅니다(예외적 중단 참조). True로 평가되면 변경 사항이 구현되지 않았기 때문에 새 상태는 이전 상태와 동일합니다(가스가 소모되는 것 제외).
  2. 실행 중인 연산 코드가 REVERT (opens in a new tab)인 경우 새 상태는 이전 상태와 동일하며 일부 가스가 손실됩니다.
  3. RETURN (opens in a new tab)로 표시된 대로 작업 시퀀스가 완료되면 상태가 새 상태로 업데이트됩니다.
  4. 종료 조건 1-3 중 하나에 해당하지 않으면 계속 실행합니다.

9.4.1 머신 상태

이 섹션에서는 머신 상태를 더 자세히 설명합니다. w가 현재 연산 코드임을 지정합니다. μpc가 코드의 길이인 ||Ib||보다 작으면 해당 바이트(Ibpc])가 연산 코드입니다. 그렇지 않으면 연산 코드는 STOP (opens in a new tab)로 정의됩니다.

이것은 스택 머신 (opens in a new tab)이므로 각 연산 코드에 의해 팝(pop)된 항목 수(δ)와 푸시(push)된 항목 수(α)를 추적해야 합니다.

9.4.2 예외적 중단

이 섹션에서는 비정상적인 종료가 발생하는 시점을 지정하는 Z 함수를 정의합니다. 이것은 부울(Boolean) (opens in a new tab) 함수이므로 논리합(OR)에  (opens in a new tab)를 사용하고 논리곱(AND)에  (opens in a new tab)를 사용합니다.

다음 조건 중 하나라도 참이면 예외적 중단이 발생합니다.

  • μg < C(σ,μ,A,I) 섹션 9.2에서 보았듯이 C는 가스 비용을 지정하는 함수입니다. 다음 연산 코드를 처리할 만큼 가스가 충분히 남아 있지 않습니다.

  • δw=∅ 연산 코드에 대해 팝된 항목 수가 정의되지 않은 경우 연산 코드 자체도 정의되지 않습니다.

  • || μs || < δw 스택 언더플로, 현재 연산 코드에 대해 스택에 항목이 충분하지 않습니다.

  • w = JUMP ∧ μs[0]∉D(Ib) 연산 코드는 JUMP (opens in a new tab)이고 주소는 JUMPDEST (opens in a new tab)가 아닙니다. 점프는 목적지가 JUMPDEST (opens in a new tab)일 때_만_ 유효합니다.

  • w = JUMPI ∧ μs[1]≠0 ∧ μs[0] ∉ D(Ib) 연산 코드는 JUMPI (opens in a new tab)이고, 조건이 참(0이 아님)이므로 점프가 발생해야 하며, 주소는 JUMPDEST (opens in a new tab)가 아닙니다. 점프는 목적지가 JUMPDEST (opens in a new tab)일 때_만_ 유효합니다.

  • w = RETURNDATACOPY ∧ μs[1]+μs[2]>|| μo || 연산 코드는 RETURNDATACOPY (opens in a new tab)입니다. 이 연산 코드에서 스택 요소 μs[1]은 반환 데이터 버퍼에서 읽을 오프셋이고, 스택 요소 μs[2]는 데이터의 길이입니다. 이 조건은 반환 데이터 버퍼의 끝을 넘어 읽으려고 할 때 발생합니다. 콜 데이터나 코드 자체에는 이와 유사한 조건이 없다는 점에 유의하세요. 해당 버퍼의 끝을 넘어 읽으려고 하면 0만 반환됩니다.

  • || μs || - δw + αw > 1024

    스택 오버플로. 연산 코드를 실행한 결과 스택 항목이 1024개를 초과하게 되면 중단합니다.

  • ¬Iw ∧ W(w,μ) 정적으로 실행 중인가요?(¬는 부정 (opens in a new tab)이며 블록체인 상태를 변경할 수 있는 경우 Iw는 참입니다). 그렇다면 상태 변경 작업을 시도하더라도 발생할 수 없습니다.

    함수 W(w,μ)는 나중에 수식 150에 정의됩니다. 다음 조건 중 하나가 참이면 W(w,μ)는 참입니다.

    • w ∈ {CREATE, CREATE2, SSTORE, SELFDESTRUCT} 이러한 연산 코드는 새 컨트랙트를 생성하거나, 값을 저장하거나, 현재 컨트랙트를 자가 파기하여 상태를 변경합니다.

    • LOG0≤w ∧ w≤LOG4 정적으로 호출된 경우 로그 항목을 내보낼 수 없습니다. 로그 연산 코드는 모두 LOG0(A0) (opens in a new tab)에서 LOG4(A4) (opens in a new tab) 사이의 범위에 있습니다. 로그 연산 코드 뒤의 숫자는 로그 항목에 포함된 주제(topic)의 수를 지정합니다.

    • w=CALL ∧ μs[2]≠0 정적일 때 다른 컨트랙트를 호출할 수 있지만, 그렇게 할 경우 해당 컨트랙트로 ETH를 전송할 수 없습니다.

  • w = SSTORE ∧ μg ≤ Gcallstipend Gcallstipend(부록 G에 2300으로 정의됨) 가스보다 많이 가지고 있지 않으면 SSTORE (opens in a new tab)를 실행할 수 없습니다.

9.4.3 점프 목적지 유효성

여기서는 JUMPDEST (opens in a new tab) 연산 코드가 무엇인지 공식적으로 정의합니다. 바이트 값 0x5B만 찾을 수는 없습니다. PUSH 내부에 있을 수 있기 때문입니다(따라서 연산 코드가 아니라 데이터일 수 있음).

수식 (153)에서 함수 N(i,w)를 정의합니다. 첫 번째 매개변수 i는 연산 코드의 위치입니다. 두 번째 w는 연산 코드 자체입니다. w∈[PUSH1, PUSH32]이면 연산 코드가 PUSH임을 의미합니다(대괄호는 끝점을 포함하는 범위를 정의함). 이 경우 다음 연산 코드는 i+2+(w−PUSH1)에 있습니다. PUSH1 (opens in a new tab)의 경우 2바이트(PUSH 자체와 1바이트 값)를 전진해야 하고, PUSH2 (opens in a new tab)의 경우 2바이트 값이므로 3바이트를 전진해야 하는 식입니다. 다른 모든 EVM 연산 코드는 길이가 1바이트이므로 다른 모든 경우에는 N(i,w)=i+1입니다.

이 함수는 수식 (152)에서 연산 코드 위치 i로 시작하는 코드 c의 모든 유효한 점프 목적지의 집합(set) (opens in a new tab)DJ(c,i)를 정의하는 데 사용됩니다. 이 함수는 재귀적으로 정의됩니다. i≥||c||이면 코드의 끝이거나 그 이후임을 의미합니다. 더 이상 점프 목적지를 찾을 수 없으므로 빈 집합을 반환합니다.

다른 모든 경우에는 다음 연산 코드로 이동하여 거기서부터 시작하는 집합을 가져와 코드의 나머지 부분을 살펴봅니다. c[i]는 현재 연산 코드이므로 N(i,c[i])는 다음 연산 코드의 위치입니다. 따라서 DJ(c,N(i,c[i]))는 다음 연산 코드에서 시작하는 유효한 점프 목적지의 집합입니다. 현재 연산 코드가 JUMPDEST가 아니면 해당 집합을 반환합니다. JUMPDEST인 경우 결과 집합에 포함하여 반환합니다.

9.4.4 정상 중단

중단 함수 H는 세 가지 유형의 값을 반환할 수 있습니다.

  • 중단 연산 코드가 아닌 경우 빈 집합인 를 반환합니다. 관례상 이 값은 부울 거짓(false)으로 해석됩니다.
  • 출력을 생성하지 않는 중단 연산 코드(STOP (opens in a new tab) 또는 SELFDESTRUCT (opens in a new tab))가 있는 경우 크기가 0바이트인 시퀀스를 반환 값으로 반환합니다. 이것은 빈 집합과는 매우 다릅니다. 이 값은 EVM이 실제로 중단되었지만 읽을 반환 데이터가 없음을 의미합니다.
  • 출력을 생성하는 중단 연산 코드(RETURN (opens in a new tab) 또는 REVERT (opens in a new tab))가 있는 경우 해당 연산 코드에 지정된 바이트 시퀀스를 반환합니다. 이 시퀀스는 메모리에서 가져오며, 스택 맨 위 값(μs[0])이 첫 번째 바이트이고 그 다음 값(μs[1])이 길이입니다.

H.2 명령어 세트

EVM의 마지막 하위 섹션인 9.5로 넘어가기 전에 명령어 자체를 살펴보겠습니다. 명령어는 29페이지에서 시작하는 부록 H.2에 정의되어 있습니다. 특정 연산 코드와 함께 변경되는 것으로 지정되지 않은 모든 것은 동일하게 유지될 것으로 예상됩니다. 변경되는 변수는 <something>′로 지정됩니다.

예를 들어 ADD (opens in a new tab) 연산 코드를 살펴보겠습니다.

니모닉δα설명
0x01ADD21덧셈 연산.
μ′s[0] ≡ μs[0] + μs[1]

δ는 스택에서 팝하는 값의 수입니다. 이 경우 상위 두 값을 더하기 때문에 2입니다.

α는 다시 푸시하는 값의 수입니다. 이 경우 합계인 1입니다.

따라서 새로운 스택 맨 위(μ′s[0])는 이전 스택 맨 위(μs[0])와 그 아래의 이전 값(μs[1])의 합입니다.

지루한 목록으로 모든 연산 코드를 살펴보는 대신, 이 문서에서는 새로운 것을 도입하는 연산 코드만 설명합니다.

니모닉δα설명
0x20KECCAK25621케착-256 해시를 계산합니다.
μ′s[0] ≡ KEC(μms[0] . . . (μs[0] + μs[1] − 1)])
μ′i ≡ M(μis[0],μs[1])

이것은 메모리에 액세스하는 첫 번째 연산 코드입니다(이 경우 읽기 전용). 그러나 메모리의 현재 한계를 넘어 확장될 수 있으므로 μi를 업데이트해야 합니다. 29페이지의 수식 328에 정의된 M 함수를 사용하여 이 작업을 수행합니다.

니모닉δα설명
0x31BALANCE11주어진 계정의 잔액을 가져옵니다.
...

잔액을 찾아야 하는 주소는 μs[0] mod 2160입니다. 스택의 맨 위는 주소이지만 주소는 160비트이므로 2160에 대한 모듈로(modulo) (opens in a new tab) 값을 계산합니다.

σ[μs[0] mod 2160] ≠ ∅이면 이 주소에 대한 정보가 있음을 의미합니다. 이 경우 σ[μs[0] mod 2160]b는 해당 주소의 잔액입니다. σ[μs[0] mod 2160] = ∅이면 이 주소가 초기화되지 않았으며 잔액이 0임을 의미합니다. 4페이지의 섹션 4.1에서 계정 정보 필드 목록을 볼 수 있습니다.

두 번째 수식인 A'a ≡ Aa ∪ {μs[0] mod 2160}는 웜 스토리지(최근에 액세스되어 캐시되었을 가능성이 있는 스토리지)와 콜드 스토리지(액세스되지 않아 검색 비용이 더 많이 드는 느린 스토리지에 있을 가능성이 있는 스토리지)에 대한 액세스 비용 차이와 관련이 있습니다. Aa는 트랜잭션이 이전에 액세스한 주소 목록이므로 8페이지의 섹션 6.1에 정의된 대로 액세스 비용이 더 저렴해야 합니다. 이 주제에 대한 자세한 내용은 EIP-2929 (opens in a new tab)에서 읽을 수 있습니다.

니모닉δα설명
0x8FDUP16161716번째 스택 항목을 복제합니다.
μ′s[0] ≡ μs[15]

스택 항목을 사용하려면 팝해야 하며, 이는 그 위에 있는 모든 스택 항목도 팝해야 함을 의미합니다. DUP<n> (opens in a new tab)SWAP<n> (opens in a new tab)의 경우 최대 16개의 값을 팝한 다음 푸시해야 함을 의미합니다.

9.5 실행 주기

이제 모든 부분을 갖추었으므로 마침내 EVM의 실행 주기가 어떻게 문서화되어 있는지 이해할 수 있습니다.

수식 (155)는 다음과 같은 상태가 주어졌을 때:

  • σ (글로벌 블록체인 상태)
  • μ (EVM 상태)
  • A (하위 상태, 트랜잭션이 끝날 때 발생할 변경 사항)
  • I (실행 환경)

새로운 상태는 (σ', μ', A', I')라고 말합니다.

수식 (156)-(158)은 스택과 연산 코드(μs)로 인한 스택의 변경 사항을 정의합니다. 수식 (159)는 가스의 변화(μg)입니다. 수식 (160)은 프로그램 카운터의 변화(μpc)입니다. 마지막으로 수식 (161)-(164)는 연산 코드에 의해 명시적으로 변경되지 않는 한 다른 매개변수가 동일하게 유지됨을 지정합니다.

이로써 EVM이 완전히 정의되었습니다.

결론

수학적 표기법은 정확하며 황서가 이더리움의 모든 세부 사항을 지정할 수 있게 해주었습니다. 그러나 몇 가지 단점이 있습니다.

아마도 이러한 이유로 최신 합의 레이어 사양 (opens in a new tab)은 Python으로 작성되었습니다. Python으로 작성된 실행 계층 사양 (opens in a new tab)이 있지만 완전하지는 않습니다. 전체 황서가 Python이나 유사한 언어로 번역되지 않는 한 황서는 계속 사용될 것이며, 이를 읽을 수 있는 것은 도움이 됩니다.