ERC-7751 중첩 리버트 오류 감싸기 (Wrapping of bubbled up reverts)

Final · 기타 · 2024-08-06 작성

컨트랙트가 다른 컨트랙트를 호출하다 실패했을 때, 실패 이유를 그대로 던지는 대신 어디에서 왜 실패했는지 맥락을 붙인 표준 오류로 감싸서 전달하게 하는 표준입니다.

왜 필요한가

지금은 컨트랙트 A가 컨트랙트 B를 호출했다가 B가 실패(리버트, revert)하면 그 실패 이유가 보통 그대로 위로 전달되어 던져집니다. 그런데 하나의 거래 안에서 여러 컨트랙트가 연쇄적으로 호출되는 경우, 전달받은 오류만 봐서는 그 오류가 어느 컨트랙트의 어떤 맥락에서 나온 것인지 구분하기 어렵습니다. 개발자는 어디서 문제가 생겼는지 추적하기 힘들고 이더스캔(Etherscan) 같은 서비스도 정확한 호출 경로와 실패 지점을 보여 주기 어렵습니다. 이 표준은 오류를 감싸서 전달하는 방식을 하나의 커스텀 에러(custom error) 형식으로 통일해 실패한 컨트랙트 주소와 함수, 원래 오류 내용, 추가 설명까지 함께 담아 올려 보내도록 합니다. 그 결과 오류 메시지가 더 의미 있어지고 디버깅 경험이 좋아집니다.

구현해야 하는 것

심층 설명

WrappedError 하나로 감싸기 방식 통일

오류를 감싸서 올리려는 컨트랙트는 반드시(MUST) error WrappedError(address target, bytes4 selector, bytes reason, bytes details) 형태의 커스텀 에러로 리버트해야 합니다. 이 에러의 시그니처는 0x90bfb865입니다. 형식을 하나로 고정했기 때문에 어떤 컨트랙트가 던진 오류든 도구가 같은 방식으로 파싱할 수 있습니다.

네 필드의 의미와 규칙

target은 리버트한 피호출 컨트랙트의 주소입니다. selector는 리버트한 피호출 함수의 셀렉터이며, 데이터 없는 ETH 전송 호출이었다면 반드시 bytes4(0)이어야 합니다. reason은 전달받은 리버트 이유의 원시 바이트를 그대로 담습니다. details는 선택적인 추가 맥락으로, 필요 없으면 빈 바이트로 둘 수 있습니다. 추가 맥락을 담는 경우에는 WrappedError를 던지는 컨트랙트에 선언된 커스텀 에러를 ABI 인코딩한 값이어야 합니다.

중첩 리버트를 재귀적으로 추적

감싸는 방식이 표준화되면 오류 안에 오류를 담는 중첩이 가능해집니다. 여러 컨트랙트가 각자 던진 리버트를 바깥쪽 WrappedError부터 재귀적으로 풀어 가며 따라갈 수 있습니다. 호출된 컨트랙트와 함수, 원시 리버트 바이트, 추가 맥락이 함께 담기므로 실패에 대한 더 상세한 정보를 제공할 수 있고 이더스캔이나 파운드리 같은 도구가 이를 파싱해 가독성과 디버깅 편의를 높일 수 있습니다.

테스트 케이스가 보여 주는 3단 중첩

테스트 케이스는 Router가 Vault를, Vault가 Token을 호출하는 구조입니다. Token의 transfer가 "insufficient balance"로 리버트하면 Vault는 Token 주소, transfer 셀렉터, 원래 오류, 그리고 abi.encodeWithSelector(ERC20TransferFailed.selector, to)로 인코딩한 맥락을 담아 WrappedError를 던집니다. Router는 이를 다시 Vault 주소, withdraw 셀렉터, Vault의 오류 전체, 빈 details로 감쌉니다. 최종 오류를 풀면 세 단계의 실패 경로가 모두 복원됩니다.

하위 호환성과 표준 상태

이 표준은 하위 호환성 문제를 일으키지 않으며 기존 컨트랙트도 점진적으로 채택할 수 있습니다. 참조 구현처럼 피호출 컨트랙트의 리버트를 catch한 뒤 위 규칙을 따르는 WrappedError로 다시 리버트하면 됩니다. 원문 상태는 Final로, 내용이 확정된 표준입니다.

쓸 때 주의할 점

ERC 원문 · ← ERCs