EIP-5920 코드 실행 없이 이더만 보내는 PAY 명령 (PAY opcode)

Hegota 포함 검토 (CFI) · EVM·실행 · 원문 상태 Draft · 2022-03-14 작성

EVM에 새 명령어 PAY(0xfc)를 추가해 받는 쪽 코드를 실행하지 않고 이더만 보낼 수 있게 합니다. 송금할 때 생기는 재진입 공격과 불필요한 가스 비용을 피하는 것이 목적입니다.

왜 필요한가

지금 스마트 컨트랙트가 다른 주소에 이더를 보내려면 그 주소를 호출(call)해야 합니다. 호출은 실행 흐름을 상대에게 넘기는 일이라, 돈만 보내려 해도 상대 코드가 함께 돌아갑니다. 여기서 여러 문제가 생깁니다. 받는 쪽이 보낸 쪽을 다시 호출하는 재진입 공격이 가능하고 받는 쪽이 가스를 다 써 버리거나 실행을 되돌려 송금 자체를 막는 서비스 거부 공격도 가능합니다. 호출마다 기본으로 딸려 가는 2300 가스로는 일반 저장소를 바꿀 수 없지만 임시 저장소(transient storage, EIP-1153)는 바꿀 수 있어 의도치 않은 상태 변경이 일어날 여지가 있습니다. 코드 실행에도 가스가 들기 때문에 송금만 원해도 비용을 더 냅니다. 앞으로 나올 호출 계열 명령에는 넘기는 가스 양을 제한하는 수단이 없을 수도 있습니다. 또 EIP-7702로 일반 계정(EOA)에 코드를 위임할 수 있게 되면서 평범한 지갑 주소로 보내는 송금도 코드를 실행하고 가스를 쓰게 됐습니다. 이더만 보내는 전용 명령이 있으면 이 문제를 한 번에 피할 수 있습니다.

무엇이 바뀌나

지금

EIP-5920 이후

누구에게 영향이 있나

심층 설명

명령어 정의와 스택 인자

PAY는 옵코드 0xfc를 씁니다. 스택에서 addr(받는 주소)과 val(보낼 금액, wei 단위)을 이 순서로 꺼내고 현재 실행 중인 주소에서 addr로 val wei를 옮깁니다. 상대 주소의 코드는 전혀 실행하지 않습니다. 이체는 현재 주소 잔액이 val 이상일 때만 일어납니다. 성공하면 스택에 1을, 실패하면 0을 올립니다. 현재 명세에서 0이 나오는 경우는 잔액 부족 하나뿐입니다. 실행 뒤 addr은 accessed_addresses에 추가되어 warm 상태가 됩니다. 인자는 addr과 val 두 개뿐이고 CALL처럼 넘길 가스 양을 따로 지정하는 인자는 없습니다.

정적 프레임과 잘못된 주소에서의 예외 중단

두 경우에 예외로 중단합니다. 첫째, 현재 프레임이 EIP-214가 정의한 정적(static) 프레임이면 중단합니다. 둘째, addr의 상위 12바이트 가운데 0이 아닌 값이 하나라도 있으면, 즉 20바이트 주소가 아니면 중단합니다. CALL은 이런 경우 상위 바이트를 잘라 버리지만 PAY는 일부러 중단합니다. 주소 공간 확장(Address Space Extension)을 염두에 둔 설계입니다. 잘라내는 동작을 두면 컨트랙트가 그 동작에 의존하게 됩니다. 나중에 주소가 20바이트를 넘게 확장되면 PAY가 그 계정을 겨냥하지 못하거나 잘못된 주소로 이더가 갈 수 있습니다. 원문은 이 중단 동작 자체도 바뀔 수 있으니 컨트랙트가 여기에 의존하지 말고 의도적으로 멈추려면 INVALID(EIP-141) 같은 전용 수단을 쓰라고 적습니다.

가스 비용 계산

가스는 세 항목의 합입니다. 첫째, addr이 accessed_addresses에 있으면 WARM_STORAGE_READ_COST, 없으면 COLD_ACCOUNT_ACCESS_COST를 냅니다. 두 상수는 EIP-2929 정의를 따릅니다. 둘째, addr 계정이 이미 존재하거나 val이 0이면 0, 둘 다 아니면 GAS_NEW_ACCOUNT를 냅니다. 셋째, val이 0이면 0, 아니면 GAS_CALL_VALUE를 냅니다. GAS_NEW_ACCOUNT와 GAS_CALL_VALUE는 실행 계층 명세(EELS)의 frontier 가스 정의를 그대로 가져옵니다. 원문은 빈 계정(empty account)이 남아 있는 네트워크에서는 PAY를 구현할 수 없다고 명시합니다. 그래서 EIP-7523을 선행 조건(requires)으로 둡니다.

인자 순서를 CALL과 맞춘 이유

PAY는 CALL과 같은 순서로 addr을 먼저, val을 다음에 꺼냅니다. 원문은 CALL과의 일관성 말고도 한 가지 이유를 더 듭니다. 이 순서를 쓰면 PAY가 항상 COINBASE 바로 뒤에 나타나므로, 검증자가 MEV 기회를 패턴 매칭으로 찾는 데 도움이 된다는 설명입니다. 원문의 설계 근거(Rationale)는 인자 순서와 잘못된 주소 처리 두 가지만 다루고 그 밖의 선택에 대한 설명은 따로 적혀 있지 않습니다.

보안 고려와 하위 호환성

이 변경은 하드포크가 필요합니다. 보안 측면에서 원문은 기존 컨트랙트가 자기 잔액이 자기 통제 아래에만 있다고 가정해서는 안 된다고 짚습니다. 이미 SELFDESTRUCT(EIP-6780으로 일부 제한)로 호출 없이 이더를 넣을 수 있고 block.coinbase로 가는 우선 수수료(priority fee)로도 본인 뜻과 상관없이 계정에 이더가 들어올 수 있기 때문입니다. PAY는 이 과정을 더 싸고 쉽게 만들 뿐, 기존 불변식(invariant)을 깨지는 않는다고 정리합니다.

현재 상태와 포크 논의

원문 상태는 Draft라서 명세가 아직 바뀔 수 있습니다. 원문도 잘못된 주소에서 중단하는 동작은 바뀔 수 있다고 적습니다. 코어 개발자 콜에서는 08.13 ACDE #243에서 EIP-5920 PAY 옵코드가 헤고타(Hegota) 후보(PFI)로 접수됐고 10.08 ACDE #247에서 헤고타 검토 대상(CFI)으로 올라갔습니다. 복잡도는 중간으로 평가됐고 대부분 클라이언트가 지지해 반대가 없었습니다. 검토 대상에 오른 단계이며 최종 포함이 확정된 것은 아닙니다.

EIP 원문 · ← EIPs