ERC-6093 토큰 공통 커스텀 에러 표준 (Custom errors for commonly-used tokens)
Final · 토큰 · 2022-12-06 작성
ERC-20, ERC-721, ERC-1155 토큰이 실패할 때 쓰는 에러를 공통 형식으로 정합니다. 지갑과 앱이 실패 원인을 구조화된 데이터로 해석해 사용자에게 보여 줄 수 있습니다.
왜 필요한가
이더리움 앱과 지갑은 거래가 실패한 원인을 사용자에게 보여 줄 때 오랫동안 되돌리기 사유 문자열(revert reason string)에 의존해 왔습니다. 솔리디티(Solidity) 0.8.4부터는 원인을 종류별로 해석할 수 있는 커스텀 에러(custom error)가 생겼지만 대표 토큰 표준인 ERC-20, ERC-721, ERC-1155는 그보다 먼저 확정되어 명세에 에러가 전혀 들어 있지 않습니다. 그래서 토큰마다 에러 이름과 내용이 제각각이고 지갑이나 테스트 환경이 실패 원인을 일관되게 해석하기 어렵습니다. 긴 문자열을 배포 코드에 담느라 배포 비용도 커집니다. 이 표준은 세 토큰 표준에서 흔히 일어나는 실패를 구조화된 공통 에러 목록으로 정리해 기존 문자열과 적어도 같은 정보를 더 일관되고 예측 가능한 형태로 제공하게 합니다.
구현해야 하는 것
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);: 보내는 쪽의 잔액(balance)이 필요한 양(needed)보다 적을 때의 에러입니다. 전송에 씁니다.error ERC20InvalidSender(address sender);: 전송에서 보내는 주소 자체가 잘못됐을 때의 에러입니다. 영주소에서 나가는 전송을 금지할 때 권장됩니다.error ERC20InvalidReceiver(address receiver);: 전송에서 받는 주소가 잘못됐을 때의 에러입니다. 영주소나 호환되지 않는 주소로 보내는 전송을 금지할 때 권장됩니다.error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);: 위임받은 사용 한도(allowance)가 필요한 양보다 적을 때의 에러입니다. 전송에 씁니다.error ERC20InvalidApprover(address approver);: 승인하는 쪽 주소가 잘못됐을 때의 에러입니다. 영주소의 승인 금지에 권장됩니다.error ERC20InvalidSpender(address spender);: 승인받는 쪽 주소가 잘못됐을 때의 에러입니다. 영주소나 소유자 자신에 대한 승인 금지에 권장됩니다.error ERC721InvalidOwner(address owner);: 소유자가 될 수 없는 주소를 나타내는 에러입니다. 잔액 조회에 씁니다.error ERC721NonexistentToken(uint256 tokenId);: 소유자가 영주소인 토큰, 즉 발행되지 않았거나 소각된 tokenId를 나타내는 에러입니다.error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);: 특정 토큰의 소유권에 관한 에러입니다. sender가 실제 소유자(owner)가 아닐 때 전송에서 씁니다.error ERC721InvalidSender(address sender);: 전송에서 보내는 주소가 잘못됐을 때의 에러입니다. 영주소에서 나가는 전송을 금지할 때 권장됩니다.error ERC721InvalidReceiver(address receiver);: 전송에서 받는 주소가 잘못됐을 때의 에러입니다. 영주소나 수신 인터페이스가 없는 컨트랙트로 보내는 전송을 금지할 때 권장됩니다.error ERC721InsufficientApproval(address operator, uint256 tokenId);: 전송에서 operator가 해당 토큰에 대한 승인이 없을 때의 에러입니다.error ERC721InvalidApprover(address approver);: 승인하는 쪽 주소가 잘못됐을 때의 에러입니다. 영주소의 승인 금지에 권장됩니다.error ERC721InvalidOperator(address operator);: 승인받는 쪽 operator가 잘못됐을 때의 에러입니다. operator는 승인 대상 토큰의 소유자여서는 안 됩니다.error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);: 특정 tokenId에 대해 보내는 쪽 잔액이 필요한 양보다 적을 때의 에러입니다. 전송에 씁니다.error ERC1155InvalidSender(address sender);: 전송에서 보내는 주소가 잘못됐을 때의 에러입니다. 영주소에서 나가는 전송을 금지할 때 권장됩니다.error ERC1155InvalidReceiver(address receiver);: 전송에서 받는 주소가 잘못됐을 때의 에러입니다. 영주소나 수신 인터페이스가 없는 컨트랙트로 보내는 전송을 금지할 때 권장됩니다.error ERC1155MissingApprovalForAll(address operator, address owner): 전송에서 operator가 owner로부터 전체 승인(isApprovedForAll)을 받지 않았을 때의 에러입니다.error ERC1155InvalidApprover(address approver);: 승인하는 쪽 주소가 잘못됐을 때의 에러입니다. 영주소의 승인 금지에 권장됩니다.error ERC1155InvalidOperator(address operator);: 승인받는 쪽 operator가 잘못됐을 때의 에러입니다. 소유자 자신에 대한 승인 금지에는 반드시 이 에러를 씁니다.error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);: safeBatchTransferFrom에서 ids 배열과 values 배열의 길이가 서로 다를 때의 에러입니다.
심층 설명
에러 이름의 문법: 도메인, 접두사, 주체
모든 에러 이름은 <Domain><ErrorPrefix><Subject>(<Arguments>) 문법을 따릅니다. Domain은 ERC20, ERC721, ERC1155 같은 표준 이름이고 ErrorPrefix는 실패 이유를 나타내는 Invalid(일반적 오류)나 Insufficient(양 부족) 등입니다. Subject는 Sender, Receiver, Balance, Approver, Operator, Approval처럼 실패한 주체입니다. 행위(action)는 Transfer와 Approval 두 가지로 봅니다. 행위 자체는 에러의 주체가 되지 않습니다. 어떤 주체가 어떤 행위 중에 실패했는지를 조합해 에러를 만듭니다.
도메인 접두사가 필요한 이유
에러 인자는 토큰 종류에 따라 달라집니다. 같은 이름에 다른 인자를 가진 에러가 있으면 솔리디티 컴파일러가 DeclarationError로 실패합니다. 예를 들어 InsufficientApproval(address spender, uint256 allowance, uint256 needed)와 InsufficientApproval(address operator, uint256 tokenId)는 공존할 수 없습니다. 그래서 ERC 이름과 번호를 앞에 붙인 도메인 접두사(ERC20InsufficientApproval, ERC721InsufficientApproval)로 선언 충돌을 피합니다.
인자 순서: 누가, 무엇이, 왜
인자는 정해진 순서를 따릅니다. 먼저 에러의 주체(예: address sender), 다음으로 무엇이 실패했는지(예: uint256 allowance), 마지막으로 왜 실패했는지를 추가 인자로(예: uint256 needed) 넣습니다. 한 인자가 여러 범주에 겹칠 수 있어 셋이 모두 있지는 않지만 순서는 지켜야 합니다. tokenId가 필요한 토큰은 주체가 아니라 끝의 추가 정보로 넣기를 권합니다. 확장 등 구현별 에러를 추가할 때도 일관성을 위해 이 지침을 따라야 합니다(SHOULD).
사용 지침의 성격: 되돌릴지는 구현자 몫
이 표준은 특정 상황에서 쓸 수 있는 표준 에러를 정의할 뿐, 그 상황에서 반드시 되돌려야(revert) 하는지는 정하지 않습니다. 되돌릴지는 해당 토큰 EIP가 강제하지 않는 한 구현자가 정합니다. 다만 각 에러에는 지켜야 할 규칙이 있습니다. 예를 들어 ERC20InsufficientBalance는 balance가 needed보다 작아야 하고(MUST) ERC20InvalidSender는 승인 작업이나 잔액·한도 요건에 쓰면 안 되며(MUST NOT) 그 경우 ERC20InsufficientBalance나 ERC20InsufficientAllowance를 씁니다. 인자 이름은 Parameter Glossary의 정의대로 써야 합니다(MUST).
표준별 에러 구성
ERC-20은 전송용 4개(InsufficientBalance, InvalidSender, InvalidReceiver, InsufficientAllowance)와 승인용 2개(InvalidApprover, InvalidSpender)로 총 6개입니다. ERC-721은 소유권 중심으로 8개입니다. 잔액 조회용 InvalidOwner, 미발행·소각 토큰용 NonexistentToken, sender가 소유자가 아닐 때의 IncorrectOwner, 승인 부족의 InsufficientApproval 등이 있습니다. ERC-1155는 7개로, 잔액 에러에 tokenId가 붙고 전체 승인 부재를 나타내는 MissingApprovalForAll과 배치 전송의 배열 길이 불일치를 나타내는 InvalidArrayLength가 추가됩니다.
기존 배포 토큰과의 호환
이미 배포된 토큰은 대부분 require와 되돌리기 문자열을 쓰며 솔리디티 0.8.4 이후 배포된 토큰도 문자열 기반 구현을 상속한 경우가 많습니다. 업그레이드할 수 없는 기존 토큰에는 이 표준을 강제할 수 없고 이들은 도메인 유무, 다른 접두사, 비슷한 주체, 다른 어순 같은 작은 변형이 있는 비슷한 관례를 씁니다. 업그레이드 가능한 컨트랙트는 이 표준을 구현하도록 업그레이드할 수 있습니다(MAY). 이 표준을 지원하는 구현자와 디앱 개발자는 비준수 컨트랙트가 내는 다른 에러와 고전적인 되돌리기 문자열도 허용해야 합니다(SHOULD).
쓸 때 주의할 점
- 시그니처 해시 충돌: 명세된 에러들 사이에 알려진 시그니처 해시 충돌은 없습니다.
- 문자열 의존 시스템과의 충돌: 이 표준을 구현하도록 업그레이드된 토큰은 되돌리기 문자열에 의존하는 다른 시스템의 가정을 깨뜨릴 수 있습니다.
- 신뢰할 수 없는 컨트랙트의 에러 악용: 오프체인 앱은 이 커스텀 에러로 되돌릴 수 있는 신뢰할 수 없는 컨트랙트를 조심해야 합니다. 사용자 인터페이스가 에러 디코딩 결과에 따라 행동을 유도한다면, 악의적 컨트랙트가 이를 악용해 신뢰할 수 없고 해로울 수도 있는 작업을 부추길 여지가 있습니다.