ERC-1363 결제형 토큰 (Payable Token) (Payable Token)
Final · 토큰 · 2018-08-30 작성
ERC-20 토큰을 보내거나 사용 승인을 한 직후, 받는 쪽 컨트랙트의 코드를 같은 트랜잭션에서 바로 실행하게 해 주는 표준입니다.
왜 필요한가
ERC-20 토큰은 전송(transfer)이나 승인(approve)을 한 뒤에 받는 쪽에서 어떤 코드를 실행할 방법이 없습니다. 그래서 토큰으로 무언가를 결제하고 그 결제에 반응해 동작을 일으키려면, 먼저 토큰을 보내는 트랜잭션을 하나 보내고 받는 쪽이 그 사실을 알아채도록 또 다른 트랜잭션을 보내야 합니다. 트랜잭션을 두 번 보내니 가스(GAS) 수수료도 두 번 들고 받는 쪽이 전송을 감시하는 별도 장치(listener)에 의존해야 합니다. 토큰으로 서비스를 팔거나 청구서를 결제하거나 구독을 거는 일처럼, 결제와 그에 따른 동작을 한 번에 묶고 싶은 경우에 이 과정이 번거롭고 비용이 많이 듭니다.
구현해야 하는 것
function transferAndCall(address to, uint256 value) external returns (bool);: 토큰을 to에게 보낸 뒤 받는 컨트랙트의 onTransferReceived를 호출합니다.function transferAndCall(address to, uint256 value, bytes memory data) external returns (bool);: 토큰을 to에게 보내고 추가 데이터(data)를 함께 넘겨 onTransferReceived를 호출합니다.function transferFromAndCall(address from, address to, uint256 value) external returns (bool);: from의 토큰을 to에게 보낸 뒤 받는 컨트랙트의 onTransferReceived를 호출합니다.function transferFromAndCall(address from, address to, uint256 value, bytes memory data) external returns (bool);: from의 토큰을 to에게 보내고 추가 데이터를 함께 넘겨 onTransferReceived를 호출합니다.function approveAndCall(address spender, uint256 value) external returns (bool);: spender에게 value만큼 사용을 승인한 뒤 그 컨트랙트의 onApprovalReceived를 호출합니다.function approveAndCall(address spender, uint256 value, bytes memory data) external returns (bool);: 사용을 승인하고 추가 데이터를 함께 넘겨 onApprovalReceived를 호출합니다.function onTransferReceived(address operator, address from, uint256 value, bytes memory data) external returns (bytes4);: 토큰을 받은 컨트랙트가 전송을 처리하는 콜백입니다. 정해진 매직값을 돌려주어야 전송이 성립합니다.function onApprovalReceived(address owner, uint256 value, bytes memory data) external returns (bytes4);: 사용 승인을 받은 컨트랙트가 승인을 처리하는 콜백입니다. 정해진 매직값을 돌려주어야 승인이 성립합니다.
심층 설명
구현해야 하는 인터페이스
ERC-1363을 구현하는 컨트랙트는 ERC-1363 인터페이스와 함께 ERC-20, ERC-165 인터페이스를 반드시 구현해야 합니다. ERC-1363은 ERC-20의 transfer, transferFrom, approve와 같은 동작에 콜백을 더한 것으로, 기존 transfer와 transferFrom을 덮어쓰지 않고 별도 함수로 추가합니다. 따라서 ERC-20 하위 호환성이 유지됩니다.
전송 계열 함수와 onTransferReceived
transferAndCall과 transferFromAndCall은 토큰을 옮긴 뒤 받는 주소의 onTransferReceived를 호출합니다. transferAndCall은 msg.sender에서 to로 보내고 transferFromAndCall은 from에서 to로 보냅니다. 각각 data 없는 버전과 data를 추가로 넘기는 버전이 있으며 data는 형식이 정해지지 않은 추가 데이터로 호출에 함께 전달됩니다. 모든 함수는 예외를 던지지 않는 한 true를 반환합니다. 토큰 결제를 받으려는 컨트랙트는 ERC1363Receiver 인터페이스를 반드시 구현해야 합니다.
승인 함수와 onApprovalReceived
approveAndCall은 spender에게 value만큼의 토큰 사용을 승인한 뒤 spender의 onApprovalReceived를 호출합니다. data 없는 버전과 data를 넘기는 버전이 있습니다. 승인 결제를 받으려는 컨트랙트는 ERC1363Spender 인터페이스를 반드시 구현해야 합니다. 이 설계는 ERC-721의 onERC721Received와 ERC721TokenReceiver 동작에서 영감을 받았습니다.
매직값과 되돌림 규칙
onTransferReceived는 bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))를, onApprovalReceived는 bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))를 돌려주어야 합니다. 이 매직값이 아닌 다른 값을 돌려주면 트랜잭션은 반드시 되돌려집니다. 콜백은 예외를 던져 전송이나 승인을 거부할 수도 있습니다. 두 콜백에서 메시지 발신자(msg.sender)는 항상 토큰 컨트랙트 주소입니다. operator와 from, owner로 실제 호출자와 토큰 출처를 구분합니다.
ERC-165 식별자
ERC-1363 인터페이스의 ERC-165 식별자는 0xb0202a11이며 여섯 개 함수 시그니처의 bytes4 keccak256 해시를 XOR한 값입니다. ERC1363Receiver의 식별자는 0x88a7ca5c, ERC1363Spender의 식별자는 0x7b04a2d0입니다. supportsInterface로 이 식별자들을 조회해 토큰과 받는 쪽이 각 인터페이스를 지원하는지 확인할 수 있습니다.
쓸 때 주의할 점
- 승인 경쟁 상태(race condition): approveAndCall과 transferFromAndCall은 ERC-20의 approve, transferFrom과 같은 문제가 있을 수 있습니다. approveAndCall로 승인 한도를 바꿀 때, 트랜잭션 순서가 불리하게 꼬이면 누군가가 이전 한도와 새 한도를 모두 사용할 위험이 있습니다. 이를 완화하는 한 방법은 먼저 spender의 한도를 0으로 줄인 뒤에 원하는 값을 설정하는 것입니다.