EIP-7973 웜 계정 쓰기 과금 (Warm Account Write Metering)
포함 제외 (DFI) · 수수료·가스 · 원문 상태 Draft · 2025-06-14 작성
한 트랜잭션 안에서 같은 계정을 두 번 이상 수정하면 두 번째부터는 쓰기 비용을 깎아 주는 가스 재조정입니다. 상태 루트 갱신은 어차피 한 번만 일어나기 때문입니다.
왜 필요한가
블록을 만들 때 가장 비싼 작업 중 하나가 상태 루트(state root, 네트워크 전체 상태를 요약한 값) 갱신입니다. 지금 스토리지(컨트랙트가 쓰는 저장 공간) 쓰기는 같은 트랜잭션에서 반복하면 두 번째부터 싸지는 넷 미터링(net gas metering)이 적용되지만 계정 자체(잔액, 논스, 코드해시)를 고치는 비용은 몇 번을 반복해도 매번 같은 값을 냅니다. 그래서 한 트랜잭션에서 WETH 같은 토큰을 여러 번 보내면 갈수록 싸지는데, 네이티브 ETH를 여러 번 보내면 할인이 없습니다. 이 차이 때문에 네이티브 ETH 전송은 쓰이지 않게 되고 PAY나 GAS2ETH처럼 값 전송이 들어가는 미래의 명령어도 불리해집니다. 이 EIP는 한 트랜잭션 안의 여러 쓰기를 묶어 처리할 수 있다는 점을 가스 비용에 반영해 요금을 실제 실행 비용에 가깝게 맞춥니다.
무엇이 바뀌나
지금
- 계정(잔액·논스·코드해시) 쓰기는 한 트랜잭션에서 반복해도 매번 같은 비용을 냅니다
- 스토리지 쓰기만 넷 미터링으로 반복 시 할인됩니다
- 토큰 반복 전송은 싸지는데 네이티브 ETH 반복 전송은 할인이 없습니다
EIP-7973 이후
- 트랜잭션에서 처음 고치는 계정은 콜드(cold) 비용, 이미 고친 계정은 더 싼 웜(warm) 비용을 냅니다
- SSTORE 비용도 계정 갱신 몫과 스토리지 트라이 갱신 몫으로 나뉩니다
- 네이티브 ETH 반복 전송도 토큰 전송처럼 갈수록 싸집니다
누구에게 영향이 있나
- 사용자: 지갑과 RPC가 업데이트를 처리하므로 평소 사용 방식은 바꿀 필요가 없습니다.
- 지갑 개발자: eth_estimateGas를 쓰는 지갑은 새 가스 파라미터를 반영해야 합니다. 안 하면 가스를 과대 추정해 공격 경로가 될 수 있습니다.
- 노드·클라이언트 팀: eth_estimateGas 같은 RPC 메서드에 새 비용 값을 반영한 계산식을 넣어야 하는 하위 호환 깨는 재조정입니다.
- 컨트랙트·앱 개발자: 계정 쓰기 비용이 내려가면서 새 공격 경로가 생길 수 있어 dApp별 영향 분석이 더 필요하다고 원문이 밝힙니다.
심층 설명
가스 파라미터 재편
기존 파라미터 GAS_CALL_VALUE와 GAS_STORAGE_UPDATE를 없애고 세 개를 새로 둡니다. GAS_COLD_STORAGE_WRITE는 스토리지 트라이를 한 번 갱신하는 비용, GAS_COLD_ACCOUNT_WRITE는 계정 트라이를 한 번 갱신하는 비용, GAS_WARM_WRITE는 새 상태 루트 계산을 일으키지 않는 웜 갱신 비용입니다. 세 값 모두 아직 TBD(미정)입니다. 스토리지 상태 루트 갱신 비용과 계정 상태 루트 갱신 비용을 분리한 덕분에 계정 갱신에도 웜 과금을 적용할 수 있게 됩니다.
CREATE·CALL 계열의 과금 규칙
계정을 갱신하는 명령어 CREATE, CREATE2, *CALL에서 GAS_CALL_VALUE 대신 다음을 부과합니다. 계정 필드(nonce, value, codehash)가 트랜잭션 시작 값과 같으면, 즉 이 트랜잭션에서 아직 갱신된 적이 없으면 GAS_COLD_ACCOUNT_WRITE를 부과합니다. 시작 값과 다르면, 즉 이미 갱신된 적이 있으면 GAS_WARM_WRITE를 부과합니다.
SSTORE 비용의 2단 분리
SSTORE 비용을 계정 튜플 갱신 몫과 상태 트라이 갱신 몫으로 나눕니다. 스토리지가 한 번이라도 갱신됐으면 계정 튜플은 이미 더티(dirty) 상태라서 다시 갱신하는 비용을 묶어 처리할 수 있습니다. 계정이 원래 값과 다르면 GAS_WARM_WRITE, 같으면 GAS_COLD_ACCOUNT_WRITE를 먼저 부과합니다. 슬롯이 처음 접근되면 GAS_COLD_SLOAD를 더합니다. 원래 값과 현재 값이 같은데 새 값으로 바뀌면 원래 값이 0일 때 GAS_STORAGE_SET, 아니면 GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD를 더합니다. 그 외에는 GAS_WARM_ACCESS를 더합니다.
환불 로직 갱신
환불 카운터 계산도 새 파라미터에 맞춰 바뀝니다. 트랜잭션에서 처음으로 슬롯이 0으로 지워지면 GAS_STORAGE_CLEAR_REFUND를 더하고 앞서 준 환불을 되돌릴 조건이면 같은 값을 뺍니다. 슬롯이 원래 값으로 복원되면 원래 값이 0이었을 때 GAS_STORAGE_SET - GAS_WARM_ACCESS, 0이 아니었을 때 GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD - GAS_WARM_ACCESS를 환불합니다. 복원 시 계정이 아직 콜드 상태면 GAS_COLD_ACCOUNT_WRITE도 함께 환불합니다. 트랜잭션 끝 값이 시작 값과 같으면 환불하는 완전한 넷 미터링도 검토됐지만 단순함을 위해 넣지 않았습니다.
EIP-7928 BAL과 병렬 실행 호환
EIP-7928의 블록 수준 접근 목록(Block-Level Access List, BAL)과 병렬 실행을 고려한 규칙이 있습니다. 접근한 계정이 현재 트랜잭션보다 앞 순번의 트랜잭션에서 갱신된 것으로 BAL에 나타나면, 콜드·웜 판정의 비교 기준 값을 계정 트라이가 아니라 그 BAL 항목에서 가져옵니다.
왜 계정 갱신을 웜 처리할 수 있나
이더리움에서 계정은 (nonce, balance, storage_root, codehash) 튜플로 표현되고, 머클 패트리샤 트리(MPT)의 리프입니다. storage_root는 그 계정의 키-값 저장소 MPT의 루트입니다. 스토리지를 고치면 MPT 두 개(계정의 storage_root와 전역 상태 루트)를 갱신해야 하지만 계정의 다른 필드를 고치는 것은 MPT 하나만 갱신하면 됩니다. 같은 트랜잭션에서 같은 계정을 여러 번 고치면 상태 루트 계산을 묶어 한 번에 처리할 수 있으므로, 두 번째 이후 갱신의 실제 비용은 첫 갱신과 같지 않습니다.
수치 미정과 벤치마킹
이 제안은 아직 최종 가스 수치가 없습니다. 수치를 정하려면 상태를 포함한 벤치마크(stateful benchmark)가 필요한데 현재 개발 중이며 데이터를 모은 뒤 최종 값을 정하겠다고 원문이 밝힙니다. 보안 측면에서는 계정 접근 비용이 내려가면 새 공격 경로가 생길 여지가 있어 dApp과 사용자 행동에 미치는 영향 분석이 더 필요합니다.
상태와 포크 논의
원문 상태는 Draft라서 명세가 아직 바뀔 수 있습니다. 08.13 ACDE #243 콜에서 EIP 7971, 8058, 7609, 7973, 8360은 후보로 접수되지 않았습니다(DFI). 챔피언(champion, 제안을 책임지고 밀어붙이는 담당자)이 콜에 없어 nixo가 제안자에게 연락해 보고 닿지 않으면 PR을 닫기로 했습니다.