ERC-20 토큰 표준 (Token Standard)
Final · 토큰 · 2015-11-19 작성
스마트 컨트랙트로 만든 토큰이 공통 함수와 이벤트를 갖도록 정한 표준입니다. 지갑과 거래소는 토큰마다 다른 코드를 짜지 않아도 잔액 조회, 전송, 대리 출금을 처리할 수 있습니다.
왜 필요한가
이더리움에서는 누구나 스마트 컨트랙트(블록체인 위에서 자동으로 실행되는 프로그램)로 토큰을 만들 수 있습니다. 그런데 토큰마다 잔액을 읽는 함수 이름, 전송하는 방식, 전송 사실을 알리는 방법이 제각각이면 지갑이나 탈중앙 거래소 같은 앱은 토큰 하나를 지원할 때마다 그 컨트랙트를 따로 분석하고 전용 코드를 붙여야 합니다. 다른 컨트랙트가 사용자를 대신해 토큰을 옮기는 일도 공통 규칙이 없으면 토큰마다 다르게 구현해야 합니다. 이 표준은 토큰이 갖춰야 할 함수와 이벤트의 이름, 인자, 동작을 하나로 정해 이더리움 위의 어떤 토큰이든 지갑부터 탈중앙 거래소까지 다른 앱이 그대로 재사용할 수 있게 합니다.
구현해야 하는 것
function name() public view returns (string): 토큰 이름을 돌려줍니다. 선택 함수입니다.function symbol() public view returns (string): 토큰 심볼을 돌려줍니다. 선택 함수입니다.function decimals() public view returns (uint8): 토큰이 쓰는 소수 자릿수를 돌려줍니다. 선택 함수입니다.function totalSupply() public view returns (uint256): 토큰 전체 발행량을 돌려줍니다.function balanceOf(address _owner) public view returns (uint256 balance): _owner 주소의 토큰 잔액을 돌려줍니다.function transfer(address _to, uint256 _value) public returns (bool success): 호출자의 토큰 _value만큼을 _to에게 보내고 Transfer 이벤트를 냅니다.function transferFrom(address _from, address _to, uint256 _value) public returns (bool success): _from이 허용한 범위 안에서 _from의 토큰을 _to에게 옮기고 Transfer 이벤트를 냅니다.function approve(address _spender, uint256 _value) public returns (bool success): _spender가 호출자의 계정에서 최대 _value까지 꺼내 갈 수 있게 허용합니다. 다시 부르면 한도를 덮어씁니다.function allowance(address _owner, address _spender) public view returns (uint256 remaining): _spender가 _owner 계정에서 아직 꺼내 갈 수 있는 남은 수량을 돌려줍니다.event Transfer(address indexed _from, address indexed _to, uint256 _value): 토큰이 옮겨질 때 발생합니다. 수량 0인 전송도 포함합니다.event Approval(address indexed _owner, address indexed _spender, uint256 _value): approve 호출이 성공할 때마다 발생합니다.
심층 설명
선택 함수 name, symbol, decimals
name()은 토큰 이름(예: MyToken), symbol()은 심볼(예: HIX), decimals()는 소수 자릿수를 돌려줍니다. decimals가 8이면 컨트랙트가 저장한 수량을 100000000으로 나눈 값이 사용자에게 보여 줄 수량입니다. 세 함수는 모두 OPTIONAL입니다. 사용성을 높이는 용도로만 쓰이며 다른 인터페이스와 컨트랙트는 이 값이 반드시 있다고 기대해서는 안 됩니다(MUST NOT). 따라서 지갑이나 앱은 이 함수가 없는 토큰도 처리할 수 있게 만들어야 합니다.
totalSupply와 balanceOf
totalSupply()는 토큰의 전체 발행량을 uint256으로 돌려줍니다. balanceOf(address _owner)는 _owner 주소가 가진 잔액을 uint256으로 돌려줍니다. 두 함수 모두 view 함수이므로 상태를 바꾸지 않고 읽기만 합니다. 지갑이 사용자의 보유량을 표시하거나 앱이 전송 전에 잔액을 확인할 때 이 두 함수를 씁니다. 명세의 함수 선언은 Solidity 0.4.17 이상 문법으로 쓰여 있습니다.
transfer: 내 토큰 보내기
transfer(address _to, uint256 _value)는 호출자의 계정에서 _to 주소로 _value만큼 토큰을 옮기고 반드시 Transfer 이벤트를 발생시켜야 합니다(MUST). 호출자의 잔액이 부족하면 throw 하는 것이 권장됩니다(SHOULD). 수량이 0인 전송도 일반 전송과 똑같이 취급해 Transfer 이벤트를 내야 합니다(MUST). 함수는 bool success를 돌려주는데, 호출하는 쪽은 false가 돌아올 수 있다는 점을 반드시 처리해야 하며(MUST) false가 절대 돌아오지 않는다고 가정해서는 안 됩니다(MUST NOT).
transferFrom: 대신 옮기기
transferFrom(address _from, address _to, uint256 _value)는 _from 주소에서 _to 주소로 _value만큼 옮기고 반드시 Transfer 이벤트를 발생시킵니다(MUST). 원문은 이를 출금(withdraw) 방식의 흐름이라고 설명합니다. 컨트랙트가 사용자를 대신해 토큰을 옮기거나, 토큰으로 수수료를 걷는 데 쓸 수 있습니다. _from 계정이 어떤 방식으로든 호출자에게 의도적으로 권한을 준 경우가 아니면 throw 하는 것이 권장됩니다(SHOULD). 수량 0인 전송도 일반 전송으로 취급해 Transfer 이벤트를 내야 합니다(MUST).
approve와 allowance: 출금 한도
approve(address _spender, uint256 _value)는 _spender가 호출자의 계정에서 여러 번에 걸쳐 최대 _value까지 꺼내 갈 수 있게 허용합니다. 다시 호출하면 기존 한도를 새 _value로 덮어씁니다. allowance(address _owner, address _spender)는 _spender가 _owner 계정에서 아직 꺼내 갈 수 있는 남은 수량을 돌려줍니다. 원문은 한도를 덮어쓰는 방식 때문에 생기는 공격을 막기 위해, 클라이언트가 같은 _spender의 한도를 바꿀 때 먼저 0으로 설정한 뒤 새 값을 넣도록 UI를 만들 것을 권장합니다(SHOULD). 다만 이미 배포된 컨트랙트와의 호환을 위해 컨트랙트 자체가 이 순서를 강제해서는 안 된다고 적고 있습니다.
Transfer와 Approval 이벤트
Transfer(address indexed _from, address indexed _to, uint256 _value)는 토큰이 옮겨질 때마다 반드시 발생해야 하며(MUST) 수량 0인 전송도 포함합니다. 새 토큰을 만들어 내는 컨트랙트는 발행 시 _from을 0x0으로 둔 Transfer 이벤트를 내는 것이 권장됩니다(SHOULD). Approval(address indexed _owner, address indexed _spender, uint256 _value)는 approve 호출이 성공할 때마다 반드시 발생해야 합니다(MUST). _from, _to, _owner, _spender는 indexed로 선언되어 있어 특정 주소의 이벤트만 골라 조회할 수 있습니다. 지갑과 앱이 토큰 이동 내역을 추적할 때 이 이벤트를 읽습니다.
구현과 상태
원문은 이더리움 네트워크에 이미 많은 ERC20 호환 토큰이 배포되어 있고 여러 팀이 가스 절약부터 보안 강화까지 절충점이 서로 다른 구현을 만들었다고 적고 있습니다. 예시 구현으로 OpenZeppelin 구현과 ConsenSys 구현을 링크합니다. 이 표준은 2015년 11월 19일에 만들어졌고 상태는 Final이므로 내용이 더 바뀌지 않습니다. 역사 항목에는 Vitalik Buterin의 최초 제안, Reddit 토론, 원래 Issue #20 링크가 있습니다.