EIP-2200 순 가스 계량의 구조적 정의 (Structured Definitions for Net Gas Metering)
최종 확정 · 수수료·가스 · 원문 상태 Final · 2019-07-18 작성
컨트랙트 저장 명령 SSTORE의 가스를 트랜잭션이 끝날 때 실제로 남는 변화 기준으로 계산합니다. 같은 저장 칸을 반복해 쓰는 비용이 크게 줄고 재진입 공격을 막는 안전장치가 함께 들어갑니다.
왜 필요한가
이더리움에서 컨트랙트가 데이터를 저장하는 SSTORE 명령은 값을 쓸 때마다 비싼 요금을 받습니다. 그런데 실제로 디스크에 기록이 남는 것은 트랜잭션이 끝난 뒤의 최종 값뿐입니다. 같은 트랜잭션 안에서 한 칸을 여러 번 고쳐 쓰거나 처음 값으로 되돌려 놓아도 매번 큰 비용을 내야 하니, 실제 작업량과 요금이 맞지 않았습니다. 이를 고치려던 EIP-1283은 쓰기 전후의 값을 비교해 순 변화만큼만 받는 '순 가스 계량'을 도입했지만 솔리디티가 단순 송금 호출에 기본으로 주는 2300가스 안에서도 저장이 가능해지면서 기존 컨트랙트에 새로운 종류의 재진입 공격 위험을 만들었습니다. 이 EIP는 EIP-1283에 그 위험을 막는 EIP-1706의 안전장치를 합치고 가스 수치를 이름 있는 파라미터로 정리해 EIP-1884 같은 다른 가스 변경과도 맞물리게 만든 최종본입니다.
무엇이 바뀌나
지금
- 같은 트랜잭션에서 한 저장 칸을 여러 번 고쳐 써도 매번 전액에 가까운 가스를 냅니다
- 값을 원래대로 되돌려 실제로는 아무 기록도 남지 않아도 비용이 거의 줄지 않습니다
- EIP-1283 원안은 2300가스 송금 호출 안에서 저장을 허용해 재진입 공격 위험을 만들었습니다
EIP-2200 이후
- 트랜잭션이 끝날 때 남는 순 변화 기준으로 가스를 매기고 반복 쓰기는 SLOAD_GAS 수준의 싼 요금만 냅니다
- 값을 원래대로 되돌리면 차액이 환불 카운터에 쌓여 돌려받습니다
- 남은 가스가 스티펜드 이하면 SSTORE가 실패해 재진입 보호가 유지됩니다
누구에게 영향이 있나
- 컨트랙트 개발자: 재진입 잠금, 같은 컨트랙트 다중 송금, 하위 호출과의 임시 정보 교환처럼 한 트랜잭션 안에서 저장을 반복하는 패턴의 가스가 크게 줄어듭니다.
- 사용자: 가스 인상은 예상되지 않고 많은 컨트랙트에서 가스가 감소합니다.
- 클라이언트 팀: 하드포크로 적용해야 하며 SSTORE 가스 계산과 환불 카운터 처리를 새 규칙에 맞게 바꿔야 합니다.
- 지갑·도구 개발자: 가스 추정이 스티펜드 이하에서 SSTORE가 실패하는 요건을 반영해야 합니다.
심층 설명
네 개의 가스 파라미터
EIP-1283은 가스 수치를 숫자 그대로 박아 두었지만 이 EIP는 네 개의 이름 있는 변수로 바꿉니다. SLOAD_GAS는 200에서 800으로 바뀌고 SSTORE_SET_GAS는 20000, SSTORE_RESET_GAS는 5000, SSTORE_CLEARS_SCHEDULE은 15000으로 유지됩니다. 수치를 변수로 두면 EIP-1884처럼 가스 가격을 바꾸는 다른 제안과 상호 운용할 수 있고 앞으로 있을 변경도 정의하기 쉬워집니다.
세 가지 값으로 계산하는 SSTORE 가스
계산에는 원래 값(original value), 현재 값(current value), 새 값(new value)만 씁니다. 용어 정의는 EIP-1283을 따릅니다. 현재 값과 새 값이 같으면 아무것도 바꾸지 않는 no-op이므로 SLOAD_GAS만 뗍니다. 값이 바뀌는데 원래 값과 현재 값이 같으면, 즉 이번 실행 맥락에서 처음 건드리는 칸이면 원래 값이 0일 때 SSTORE_SET_GAS를, 아니면 SSTORE_RESET_GAS를 떼고 새 값이 0이면 SSTORE_CLEARS_SCHEDULE을 환불 카운터에 더합니다. 원래 값과 현재 값이 다르면, 즉 이미 건드린 더러운(dirty) 칸이면 SLOAD_GAS만 떼고 별도의 환불 조정 규칙을 적용합니다.
더러운 칸의 환불 카운터 조정
더러운 칸에 쓸 때는 두 묶음의 조정을 모두 적용합니다. 첫째, 원래 값이 0이 아닐 때 현재 값이 0이면(새 값은 0이 아니라는 뜻) 환불 카운터에서 SSTORE_CLEARS_SCHEDULE을 빼고 새 값이 0이면(현재 값은 0이 아니라는 뜻) 더합니다. 둘째, 원래 값과 새 값이 같으면(칸이 원상 복구됨) 원래 값이 0일 때 SSTORE_SET_GAS - SLOAD_GAS를, 아니면 SSTORE_RESET_GAS - SLOAD_GAS를 환불 카운터에 더합니다. 원문은 이 정의에서 콜 프레임 단위 환불 카운터를 쓰는 구현은 카운터가 음수가 될 수 있고 트랜잭션 단위 카운터를 쓰면 항상 양수를 유지한다고 명시합니다.
스티펜드 이하에서는 SSTORE 실패
남은 가스(gasleft)가 가스 스티펜드 이하면 'out of gas' 예외로 현재 콜 프레임을 실패시킵니다. 솔리디티는 단순 송금 호출에 2300가스 스티펜드를 기본으로 주는데, EIP-1283 원안대로면 이 안에서도 저장이 가능해져 기존 컨트랙트에 새로운 종류의 재진입 공격이 생길 수 있었습니다. 이 검사가 EIP-1706이 제안한 안전장치이고 하위 호환성과 EIP-1283의 원래 의도를 깨지 않으면서 위험을 막습니다. Rationale은 EIP-1283 폐기, 상태 변경 없이 LOG만 허용하는 새 콜 컨텍스트 추가, 더러운 칸 쓰기 비용을 2300가스 이상으로 인상, 스티펜드 축소 같은 다른 대안도 검토했지만 각각의 단점 때문에 채택하지 않았다고 설명합니다.
트랜지언트 스토리지를 대신하는 효과
이 EIP는 트랜지언트 스토리지 제안(EIP-1087, EIP-1153)이 하려는 일 대부분을 'dirty map' 같은 별도 자료구조 없이 달성합니다. EIP-1087은 저장 변경 내역 지도를 유지해야 하는데, EIP-658 이후 효율적인 클라이언트는 블록이 끝날 때만 변경을 커밋하는 구조라 모든 변경을 순회하려면 추가 메모리나 처리 비용이 듭니다. 이 방식은 칸의 원래 값과 현재 값만 알면 되므로 실행 시 추가 비용이 없습니다. 기존 방식보다 가스가 더 드는 경우는 없고 사용 가스에서 환불을 뺀 절대 가스 기준으로는 모든 경우에 EIP-1087과 같습니다. 칸을 바꿨다가 원래대로 되돌린 뒤 다시 바꾸는 한 가지 경우만 EIP-1283 방식이 EIP-1087보다 환불을 더 많이 쌓습니다. 부록은 원래 값이 0인 경우와 아닌 경우를 나눠 귀납법으로 이 성질들을 증명합니다.
하위 호환성과 테스트 케이스
적용에는 하드포크가 필요합니다. 가스 인상은 예상되지 않고 많은 컨트랙트에서 가스가 감소합니다. SSTORE는 원래 5000가스 미만으로는 실행할 수 없었으므로 스티펜드 검사가 메인넷 기존 컨트랙트와 비호환을 만들지 않지만 가스 추정은 이 요건을 반영해야 합니다. 원문은 코드, 사용 가스, 환불, 원래 값과 차례로 쓰는 값을 짝지은 17개 테스트 케이스 표를 제공합니다. 예를 들어 원래 값이 0인 칸에 0을 두 번 쓰는 0x60006000556000600055는 1612가스에 환불 0입니다. 1을 썼다 0으로 되돌리는 0x60016000556000600055는 20812가스에 환불 19200입니다. 이 EIP의 상태는 Final로 명세가 확정되었습니다.