EIP-3855 PUSH0 명령어 도입 (PUSH0 instruction)
최종 확정 · EVM·실행 · 원문 상태 Final · 2021-02-19 작성
스택에 상수 0을 올리는 1바이트 명령어 PUSH0(0x5f)을 EVM에 추가합니다. 기존 방식인 PUSH1 0보다 가스가 적게 들고 코드도 짧아집니다.
왜 필요한가
EVM에서 많은 명령어가 입력으로 오프셋 값을 받는데, 이 값이 0인 경우가 많습니다. 예를 들어 CALL 계열 명령어의 반환 데이터 매개변수는 RETURNDATA 계열 명령어를 쓰는 컨트랙트라면 0으로 둡니다. 지금은 0을 스택에 올리려면 PUSH1 0을 써야 하는데, 실행할 때 3가스가 들고 코드에서 2바이트를 차지해 배포할 때도 2 * 200가스가 듭니다. 이 비용을 아끼려고 PC, MSIZE, CALLDATASIZE, RETURNDATASIZE, CODESIZE, CALLVALUE, SELFBALANCE 같은 다른 명령어를 0 대용으로 쓰는 사례가 흔한데, 이 값들은 상황에 따라 달라질 수 있어 위험합니다. 메인넷 분석 결과 실행된 PUSH 계열 명령어의 약 11.5%가 0을 올렸고, 기존 계정 전체로 보면 340,557,331바이트가 PUSH1 00에 쓰여 배포에만 68,111,466,200가스가 들었습니다. 0 전용 명령어를 만들면 코드가 짧아지고 이런 편법도 줄일 수 있습니다.
무엇이 바뀌나
지금
- 0을 스택에 올리려면 PUSH1 0을 써서 실행에 3가스, 코드 2바이트가 듭니다.
- 비용을 아끼려고 CALLDATASIZE, RETURNDATASIZE 같은 명령어를 0 대용으로 쓰는 편법이 퍼져 있습니다.
- 실행되는 PUSH 계열 명령어의 약 11.5%가 값 0을 올리는 데 쓰입니다.
EIP-3855 이후
- PUSH0 하나(1바이트)로 0을 올리고 실행 비용은 2가스입니다.
- 0 전용 명령어가 생겨 다른 명령어를 0 대용으로 쓸 이유가 줄고 그 명령어들의 가격이나 동작을 나중에 바꾸기도 덜 위험해집니다.
- 같은 일을 하는 코드가 짧아져 배포 비용이 줄어듭니다.
누구에게 영향이 있나
- 스마트컨트랙트 개발자: 컴파일러가 PUSH0을 쓰면 컨트랙트 코드 크기와 배포·실행 가스가 줄어듭니다.
- 클라이언트 팀: 옵코드 0x5f를 새로 구현해야 합니다. 기존 PUSH 구현과 연속된 공간이라 구현을 공유할 수 있습니다.
- 사용자: 컨트랙트 배포와 실행 비용이 줄어드는 혜택을 간접적으로 받습니다.
- 기존 배포 컨트랙트: 이전에 정의되지 않았던 0x5f를 이미 코드에 담고 있던 컨트랙트는 이 EIP 이후 동작이 바뀔 수 있습니다.
심층 설명
명령어 명세: 0x5f, 즉시 데이터 없음, 2가스
PUSH0 명령어는 옵코드 0x5f에 배치됩니다. 뒤따르는 즉시 데이터(immediate data)가 없고 스택에서 아무것도 꺼내지 않으며 값이 0인 항목 하나를 스택에 올립니다. 가스 비용은 2가스로, base 등급에 해당합니다. 같은 일을 하던 PUSH1 0은 실행에 3가스가 들고 코드에서 2바이트를 차지했으므로, PUSH0을 쓰면 실행 가스 1가스와 코드 1바이트를 아끼고 코드가 1바이트 줄어든 만큼 배포 비용도 바이트당 200가스씩 줄어듭니다.
동기: 메인넷 측정치와 세 가지 목표
원문은 메인넷 블록 구간 8,567,259…8,582,058과 12,205,970…12,817,405를 분석해, 실행된 PUSH 계열 명령어의 약 11.5%가 값 0을 올린다는 사실을 제시합니다. 기존 계정 전체로는 340,557,331바이트가 PUSH1 00에 쓰였고 이를 배포하는 데 68,111,466,200가스가 들었습니다. 이를 근거로 세 가지 목표를 듭니다. 첫째, 컨트랙트 코드 크기를 줄입니다. 둘째, PC나 RETURNDATASIZE 같은 명령어를 0 대용으로 오남용하는 위험을 줄입니다. 이런 명령어의 가격이나 동작을 바꾸는 일이 더 위험해지기 때문입니다. 원문은 그 예로, RETURNDATASIZE의 동작이 바뀌어 호출 프레임 시작 시점에 0임이 보장되지 않게 되는 경우를 듭니다. 셋째, 0을 복제하려고 DUP 명령어를 쓸 필요를 줄입니다.
가스 비용과 옵코드 선택의 근거
2가스(base)는 ADDRESS, ORIGIN처럼 상수 값을 스택에 올리는 명령어들에 적용되는 비용 등급입니다. PUSH0도 항상 같은 값 0을 올리므로 같은 등급을 씁니다. 즉시 데이터를 읽어 올 필요가 없다는 점에서, 3가스가 드는 PUSH1 0보다 싼 등급이 자연스럽습니다. 옵코드를 0x5f로 고른 이유는 나머지 PUSH 명령어들과 연속된(contiguous) 공간에 놓이기 때문입니다. 원문은 이 덕분에 클라이언트가 PUSH 계열과 구현을 잠재적으로 공유할 수 있다고 설명합니다.
테스트 케이스
원문은 세 가지 테스트를 제시합니다. 첫째, 코드 5F 하나를 실행하면 성공하고 스택에는 0 하나만 남습니다. 둘째, 5F를 1024번 반복한 코드를 실행하면 성공하고 스택에는 0이 1024개 쌓입니다. 셋째, 5F를 1025번 반복한 코드는 스택 초과(out of stack)로 실행이 중단됩니다. 즉 PUSH0도 다른 명령어와 똑같이 스택 깊이 한도의 적용을 받는다는 점을 확인하는 구성입니다.
하위 호환성, 보안, 현재 상태
이 EIP는 이전에 존재하지 않던 옵코드를 새로 정의합니다. 따라서 0x5f를 이미 코드에 담고 있던 기존 배포 컨트랙트는 이 EIP 이후 동작이 바뀔 수 있습니다. 보안 측면에서 저자들은 알려진 영향이 없다고 밝힙니다. 특히 PUSH0은 즉시 데이터 바이트가 없어서 점프 목적지 분석(jumpdest-analysis)에 영향을 주지 않습니다. 원문 상태는 Final로, 명세가 확정되어 더는 바뀌지 않습니다.