ERC-777 고급 토큰 표준 (훅·오퍼레이터) (Token Standard)

Final · 토큰 · 2017-11-20 작성

ERC-20과 호환되면서 토큰을 보낼 때 보내는 쪽과 받는 쪽에 알림(훅)을 주고 다른 주소(오퍼레이터)가 대신 토큰을 보내거나 소각할 수 있게 하는 토큰 표준입니다.

왜 필요한가

ERC-20 토큰을 컨트랙트에 안전하게 보내려면 approve와 transferFrom 두 번의 호출이 필요하고 두 당사자 사이의 추가 소통 방법도 표준에 정해져 있지 않습니다. 보유자가 transfer와 approve/transferFrom을 혼동해 transfer로 컨트랙트에 토큰을 보내면 토큰이 잠겨 버리기 쉽습니다. 또 받는 쪽은 토큰이 들어와도 알림을 받지 못해 바로 반응할 수 없고 원치 않는 토큰을 거절할 방법도 없습니다. ERC-20에는 토큰의 발행과 소각 과정이 명확히 정의되어 있지 않아 totalSupply가 실제 유통량과 어긋나는 경우도 있습니다. ERC-777은 이런 한계를 보완하기 위해 이더를 보내듯 send 한 번으로 토큰을 보내고 훅으로 알림과 거절을 가능하게 하며 발행부터 소각까지 토큰의 수명 주기 전체를 정의합니다.

구현해야 하는 것

심층 설명

ERC-1820 레지스트리 등록

토큰 컨트랙트는 ERC-1820 레지스트리의 setInterfaceImplementer를 호출해 자기 주소로 ERC777Token 인터페이스(해시 0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054)를 반드시 등록해야 합니다. ERC-777 기능을 켜고 끄는 스위치가 있다면 스위치를 바꿀 때마다 등록과 해제를 함께 해야 하며 해제는 구현자를 0x0으로 지정해 수행합니다. 보유자와 수신자의 훅도 같은 레지스트리에 ERC777TokensSender, ERC777TokensRecipient 인터페이스 해시로 등록합니다. 레지스트리를 쓰기 때문에 컨트랙트가 아닌 일반 주소도 프록시 컨트랙트를 통해 훅의 혜택을 받을 수 있습니다.

단위와 소수점: decimals는 항상 18

모든 수량과 잔액은 부호 없는 정수이며 내부적으로 토큰의 1E-18 단위로 저장됩니다. 사용자에게 보여 주는 표시 단위는 내부 단위의 10^18배로, wei와 ether의 관계와 같습니다. 예를 들어 잔액이 500,000,000,000,000,000이면 화면에는 0.5 토큰으로 보여야 하고 0.3 토큰을 보내려면 300,000,000,000,000,000을 넘겨야 합니다. ERC-20 호환 토큰이라면 decimals 함수를 반드시 구현하고 항상 18을 돌려줘야 합니다. 순수 ERC-777 토큰에서는 decimals가 선택 사항이며 값 18이 암묵적으로 적용됩니다.

granularity: 나눌 수 없는 최소 단위

granularity는 내부 단위 기준으로 발행·전송·소각할 수 있는 토큰의 최소 묶음입니다. 생성 시점에 정해야 하고 이후 절대 바꿀 수 없으며 1 이상이어야 합니다. 모든 잔액은 granularity의 배수여야 하고 발행·전송·소각되는 모든 수량도 배수여야 합니다. 배수가 아닌 잔액을 만드는 연산은 무효이며 트랜잭션은 revert해야 합니다. 원문은 특별한 이유가 없으면 토큰을 완전히 쪼갤 수 있도록 이 값을 1로 두는 것을 권장합니다.

오퍼레이터와 기본 오퍼레이터

오퍼레이터는 보유자를 대신해 토큰을 보내고 소각할 수 있는 주소입니다. 모든 주소는 항상 자기 자신의 오퍼레이터이며 이 권한은 취소할 수 없습니다. 보유자는 authorizeOperator와 revokeOperator로 오퍼레이터를 승인·취소하고 그때마다 AuthorizedOperator와 RevokedOperator 이벤트가 나와야 합니다. 토큰은 모든 보유자에게 암묵적으로 승인된 기본 오퍼레이터를 둘 수 있는데, 기본 오퍼레이터 목록은 컨트랙트 생성 시점에 정해지고 이후 추가나 제거가 불가능합니다. 다만 보유자는 기본 오퍼레이터를 개별적으로 취소하고 다시 승인할 수 있습니다. 기본 오퍼레이터는 보유자가 이더 없이도 토큰을 움직일 수 있게 하는 등의 용도를 염두에 둔 것입니다.

전송 규칙과 훅 호출 순서

전송 시 보유자 잔액은 amount만큼 줄고 수신자 잔액은 같은 만큼 늘며 Sent 이벤트가 나와야 합니다. 승인되지 않은 오퍼레이터의 전송, granularity 배수가 아닌 결과 잔액, 보유자나 수신자가 0x0인 경우, 잔액 부족, 훅의 revert는 모두 전송을 revert시킵니다. 수신자가 컨트랙트인데 ERC777TokensRecipient를 등록하지 않았다면 전송은 반드시 revert해야 합니다. 호출 순서가 중요합니다. tokensToSend는 상태 갱신 전에, tokensReceived는 상태 갱신 후에 호출해야 하므로 tokensToSend 안의 balanceOf는 전송 전 잔액을, tokensReceived 안의 balanceOf는 전송 후 잔액을 돌려줍니다. data와 operatorData는 전송 과정 내내 바뀌면 안 되고 0개 토큰 전송도 유효한 일반 전송으로 처리해야 합니다.

발행과 소각

발행 함수는 토큰마다 방식이 달라 표준이 일부러 정의하지 않지만 규칙은 있습니다. 발행 시 총공급량과 수신자 잔액이 늘고 Minted 이벤트가 나와야 하며 수신자가 훅을 등록했다면 tokensReceived를 호출해야 합니다. 초기 공급량도 발행으로 간주해 Minted 이벤트와 훅 호출이 필요합니다. 소각은 burn과 operatorBurn 두 함수를 반드시 구현해야 하고 총공급량과 보유자 잔액이 줄며 Burned 이벤트가 나와야 합니다. 보유자가 등록했다면 tokensToSend 훅을 호출합니다. 발행과 소각 모두 0x0 주소를 상대로는 할 수 없고 ERC-20 호환 토큰이라면 from 또는 to를 0x0으로 한 Transfer 이벤트를 함께 내는 것이 권장됩니다.

ERC-20과의 호환과 이벤트 중복 주의

ERC-777은 ERC-20과 하위 호환되며 한 컨트랙트가 두 표준을 함께 구현할 수 있습니다. 혼동을 피하려고 transfer/transferFrom 대신 send/operatorSend라는 이름을 씁니다. ERC-20을 함께 구현하면 ERC20Token 인터페이스도 ERC-1820에 등록해야 하고 ERC-20의 transfer와 transferFrom을 호출할 때도 훅이 등록되어 있으면 반드시 호출해야 합니다. 다만 ERC-20 transfer로 훅 없는 컨트랙트에 보내는 것은 계속 진행되어야 해서(SHOULD) 토큰이 잠길 수 있습니다. 토큰 이동 하나에 ERC-20 Transfer와 ERC-777 Sent/Minted/Burned 두 이벤트가 함께 나올 수 있으므로, 애플리케이션은 토큰을 ERC-20으로 다루면 Transfer만, ERC-777로 다루면 Sent·Minted·Burned만 집계해 이중 계산을 피해야 합니다.

가스 소비에 관한 참고

훅 호출 때문에 전송·발행·소각의 가스 소비를 미리 알기 어렵습니다. 원문은 디앱과 지갑이 트랜잭션 도중 가스가 바닥나는 일을 피하도록 eth_estimateGas로 필요한 가스를 먼저 추정할 것을 권장합니다.

ERC 원문 · ← ERCs