EIP-3529 가스 환불 축소 (Reduction in refunds)

최종 확정 · 수수료·가스 · 원문 상태 Final · 2021-04-22 작성

컨트랙트를 지울 때(SELFDESTRUCT) 주던 가스 환불을 없애고 저장 값을 0으로 지울 때 주던 환불을 15,000에서 4,800가스로 줄입니다. 한 트랜잭션이 돌려받는 환불 상한도 사용 가스의 1/2에서 1/5로 낮춥니다.

왜 필요한가

이더리움은 쓰지 않는 저장 공간과 컨트랙트를 지우도록 유도하려고 가스 환불 제도를 뒀습니다. 그런데 기대한 정리 효과는 작았고 부작용이 더 컸습니다. 대표 사례가 가스토큰(GasToken)입니다. 수수료가 쌀 때 저장 슬롯에 값을 채워 두었다가 수수료가 비쌀 때 지워서 환불을 받는 방식인데, 저장 슬롯을 가스를 모아 두는 배터리처럼 쓰기 때문에 네트워크가 보관해야 하는 상태(state)를 오히려 키우고 블록 공간을 비효율적으로 채웁니다. 또 환불은 블록 크기 변동을 키웁니다. 환불받은 가스만큼 같은 블록의 다음 트랜잭션이 더 실행될 수 있어서 블록이 실제로 처리하는 양이 명목 가스 한도의 거의 2배까지 늘어날 수 있습니다. 이런 사용량 급증은 EIP-1559가 눌러 주는 것보다 훨씬 오래 유지될 수 있습니다. 이 EIP는 환불을 완전히 없애는 대신, 이런 악용이 성립하지 않는 수준까지만 줄입니다.

무엇이 바뀌나

지금

EIP-3529 이후

누구에게 영향이 있나

심층 설명

SELFDESTRUCT 환불 제거

FORK_BLOCK 이후 블록부터 SELFDESTRUCT의 가스 환불을 완전히 제거합니다. 컨트랙트를 스스로 파괴해도 더 이상 가스를 돌려받지 못합니다. 원문 명세의 세 가지 변경 중 첫 번째 항목입니다. 환불은 트랜잭션 실행이 끝난 뒤에만 적용되므로, 실행 중 어떤 콜 프레임이 쓸 수 있는 가스량에는 영향이 없고 코드 실행 자체를 깨뜨리지 않습니다. 다만 환불에 의존하던 일부 애플리케이션은 경제적으로 성립하지 않게 됩니다.

SSTORE_CLEARS_SCHEDULE을 15,000에서 4,800으로

EIP-2200에서 정의한 SSTORE_CLEARS_SCHEDULE(원래 값이 0이 아닌 슬롯을 0으로 지울 때의 환불, 현재 15,000가스)을 SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST로 바꿉니다. EIP-2929와 EIP-2930 기준으로 이 합은 4,800가스입니다. EIP-2200의 나머지 두 환불 경우, 즉 원래 0이던 슬롯을 0이 아닌 값으로 바꿨다가 다시 0으로 되돌릴 때의 19,900가스(SSTORE_SET_GAS - SLOAD_GAS)와 원래 값으로 되돌릴 때의 4,900가스(SSTORE_RESET_GAS - SLOAD_GAS)는 그대로 둡니다. 이 둘은 같은 슬롯에 먼저 쓴 비용을 되돌려받는 구조라서 가스토큰에 쓸 수 없기 때문입니다.

환불 상한을 gas_used // 5로

지금까지 트랜잭션이 돌려받을 수 있는 최대 환불은 gas_used // 2였습니다. 이 EIP는 이 나눗수에 MAX_REFUND_QUOTIENT라는 이름을 붙이고 값을 2에서 5로 바꿉니다. 즉 환불 상한이 사용 가스의 1/2에서 1/5로 줄어듭니다. 환불로 늘릴 수 있는 블록당 저장소 쓰기 작업량은 최대 25%로 제한되어, 저장소 쓰기를 노린 서비스 거부 공격에 이 메커니즘을 쓰기 어려워집니다.

왜 하필 4,800인가: 페어링 논증

가스토큰이 성립하지 않으려면 슬롯에 넣은 가스보다 많은 가스를 꺼낼 수 없어야 합니다. 원문은 각 환불을 같은 트랜잭션에서 같은 슬롯에 앞서 쓴 지출과 짝짓는 방식으로 조건을 끌어냅니다. 원래 값이 0이 아닌 슬롯을 0으로 지우는 사건이 그 트랜잭션에서 처음이라면, 그 슬롯을 처음 읽고 고치는 최소 비용인 SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST와 짝지을 수 있습니다. 두 번째 이후라면 직전에 값을 0에서 벗어나게 했을 때 환불 카운터에서 SSTORE_CLEARS_SCHEDULE만큼 빠졌으므로 서로 상쇄됩니다. 따라서 SSTORE_CLEARS_SCHEDULE <= SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST이면 슬롯당 총 가스 지출이 항상 양수가 되고 이 EIP는 SSTORE_CLEARS_SCHEDULE을 정확히 그 합으로 내립니다. 직관적으로는, 아직 읽히지 않은(대개 쓸모없는) 데이터를 지울 때는 순환불이 없고 읽힌(쓸모 있을 가능성이 높은) 데이터를 지울 때는 순환불이 남는 설계입니다.

유지되는 패턴: 재진입 잠금과 ERC20 approve

0에서 0이 아닌 값으로 갔다가 다시 0으로 돌아오는 저장 패턴은 계속 약 100가스만 듭니다. 원문이 드는 두 가지 중요한 예는 재진입 방지 잠금(자식 호출 직전에 0에서 1로 바꾸고 호출이 끝나면 다시 0으로 되돌리는 패턴)과 ERC20 approve 후 전송(승인 값이 0에서 0이 아닌 값이 되었다가 전송이 처리되면 다시 0이 되는 패턴)입니다. 이런 사례는 유리한 가스 비용 대우를 받을 자격이 있고 이 EIP에서도 그 대우가 유지됩니다.

저장소 정리 유인은 얼마나 남는가

환불을 전부 없애는 EIP-3298, EIP-3403에는 슬롯을 0으로 지울 유인이 사라진다는 비판이 있었습니다. 예를 들어 ERC20 토큰 1단위를 전부 넘기는 대신 0.999999단위만 넘기고 잔액을 남겨 두면, 나중에 같은 토큰을 다시 받을 때 SSTORE 비용이 22,100가스(0에서 0이 아닌 값으로 쓰기 20,000 포함) 대신 5,000가스(읽기 2,100 + 0이 아닌 값끼리 쓰기 2,900)만 듭니다. 지금은 15,000가스 환불이 이를 상쇄해서 슬롯을 다시 쓸 확률이 15000/17100 = 87.7%를 넘을 때만 남겨 두는 게 이득입니다. 환불을 전부 없애면 그 확률이 0%보다 조금만 커도 남겨 두는 쪽이 이득이 됩니다. 이 EIP는 4,800가스 환불을 남겨서 다시 쓸 확률이 4800/17100 = 28.1%를 넘을 때만 남겨 둘 유인이 생기게 합니다. 완벽하지는 않지만 잔액을 전부 비운 토큰을 같은 주소로 다시 취득할 평균적인 기대치보다는 높을 것이라는 게 원문의 판단입니다.

클라이언트 구현 참고와 보안 고려

환불은 트랜잭션 실행 중에는 보이지 않으므로 실행 로직에는 영향이 없습니다. 0에서 0이 아닌 값으로 썼다가 나중에 다시 0으로 되돌린 SSTORE를 빼면, 블록에서 실행에 쓸 수 있는 최대 가스는 가스 한도로 제한됩니다. 되돌려진 쓰기는 저장소가 실제로 늘어나지 않고 클라이언트가 머클 트리를 조정할 필요도 없어서 환불된 가스만큼 처리 부담도 함께 사라지기 때문에 빼고 세어도 됩니다. 원문은 새 값이 원래 값과 같으면(new_value = original_value) 저장소 쓰기를 하지 말라고 강조합니다. 이는 이더리움 초기부터 현명한 최적화였지만 이 EIP 이후 더 중요해집니다. 이 EIP의 상태는 Final이며 명세가 확정되어 있습니다.

EIP 원문 · ← EIPs