반응형
ERC721 Interface
NFT 를 위한 인터페이스를 eip721 에서 규약 하고 있으며, 해당 인터페이스를 직접 구현 해보도록 한다.
개발시 payable 키워드는 필요한 경우에만 사용 하도록 하고, 불필요한 함수들에서 제거 하도록 한다.
또한 external 을 public 으로 변경하여 구현 하도록 한다.
참고: 인터페이스 전체 함수를 구현 하지는 않고, 기본적인 함수만 구현 하도록 한다.
EIP-721
EIP721 인터페이스는 아래와 같다.
pragma solidity ^0.4.20;
interface ERC721 /* is ERC165 */ {
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function balanceOf(address _owner) public view returns (uint256);
function ownerOf(uint256 _tokenId) public view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) public;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public;
function transferFrom(address _from, address _to, uint256 _tokenId) public ;
function approve(address _approved, uint256 _tokenId) public;
function setApprovalForAll(address _operator, bool _approved) public;
function getApproved(uint256 _tokenId) public view returns (address);
function isApprovedForAll(address _owner, address _operator) public view returns (bool);
}
ERC721 구현
ERC721Impl.sol
ERC721 구현 - mint 함수
ERC721 을 상속받아서 구현
contract ERC721Impl is ERC721 {
//토큰아이디와 소유자 주소 매핑
mapping (uint256 => address) tokenOwner;
//소유자 토큰 개수 매핑
mapping (address => uint256) ownedTokensCount;
function mint(address _to, uint _tokenId) public {
//토큰 아이디를 토큰 주소로 매핑 시켜 준다.
tokenOwner[_tokenId] = _to;
//소유자 토큰 개수를 증가 시켜 준다.
ownedTokensCount[_to] += 1;
}
}
ERC721 구현 - balanceOf 함수
ERC721 을 상속한 ERC721Impl 클래스에 구현 하도록 한다.
사용자의 주소에 매핑된 토큰의 개수를 리턴 하도록 구현
function balanceOf(address _owner) public vioew returns (uint256) {
return ownedTokensCount[_owner];
}
ERC721 구현 - ownerOf 함수
토큰아이디로 소유자 주소 리턴하도록 한다.
function ownerOf(uint256 _tokenId) public view returns (address){
return tokenOwner[_tokenId];
}
ERC721 구현 - transferFrom 함수
유효성 검사 및 토큰 소유자의 주소와 갯수를 증감 / 차감 하도록 구현
function transferFrom(address _from, address _to, uint256 _tokenId) public {
address owner = ownerOf(_tokenId);
//함수를 호출한 계정이 소유자 인지 검증
require(msg.sender == owner
|| getApprove(_tokenId) == msg.sender
|| isApproveForAll(owner, msg.sender));
//비어있는지 검증
require(_from != address(0));
require(_to != address(0));
//토큰의 갯수를 차감
ownedTokensCount[_from] -= 1;
//토큰의 아이디의 기존 주소를 제거
tokenOwner[_tokenId] = address(0);
//전송받은 소유자의 토큰 갯수 증가
ownedTokensCount[_to] += 1;
//토큰소유자의 새로운 소유자로 매핑
tokenOwner[_tokenId] = _to;
}
ERC721 구현 - safeTransferFrom 함수
해당 컨트랙이 토큰을 받을수 있는지 검증하기 위한 ERC721Receiver 인터페이스 구현
ERC721Receiver
interface ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4);
}
//ERC721TokenReceiver를 구현한 컨트랙트
contract ERC721TokenReceiverImpl is ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) public returns(bytes4) {
//keccak256 에 함수시그니쳐를 넘겨주면 0x150b7a02 값을 리턴 하게 된다.
return bytes4(bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")));
}
}
//컨트랙 계정을 구분하고, 컨트랙이 토큰을 받을수있는지 확인 할수 있는 전송 함수
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public {
transferFrom(_from,_to, tokenId);
if(isContract(_to) {
//ERC721 인터페이스 검증을 위해 아래 리턴받은 값이 인터페이스 식별값과 동일한지 비교하도록 한다.
//참고: ERC721TokenReceiver 인터페이스
bytes4 returnValue = ERC721TokenReceiver(_to).onERC721Received(msg.sender, _from, _tokenId, '');
require(returnValue == 0x150b7a02);
}
}
function isContract(address _addr) private view returns (bool) {
uint256 size;
//extcodesize 를 사용하여 해당 주소의 사이즈가 0보다 크면 true
//참고1: extcodesize 는 계정의 코드 사이즈를 반환 한다. 현재 실행 환경의 컨트랙트를 대상으로 한다.
//참고2: assembly 는 solidity 에서 evm 의 low level 연산을 수행 할수있도록 도와준다.
assembly {size:=extcodesize(_addr)}
return size >0;
}
ERC721 구현 - approve 함수
소유자의 토큰을 다른 계정이 대신 전송 해주도록 승인 해주는것
토큰 전송 권한이 있는 (승인받은) 계정 정보를 담을 수 있도록 매핑 정의
mapping (uint256 => address) tokenApprovals;
전송 승인 권한 함수 구현
function approve(address _approved, uint256 _tokenId) public {
address owner = ownerOf(_tokenId);
//소유자 계정이 아니어야 한다.
require(_approved != owner);
//호출한 계정이 토큰 아이디의 소유자여야 한다.
require(msg.sender == owner);
tokenApprovals[_tokenId] = _approved;
}
ERC721 구현 - getApprove 함수
토큰 전송 승인 권한
function getApprove(uint256 _tokenId) public view returns (address) {
return tokenApprovals[_tokenId];
}
ERC721 구현 - setApprovalForAll 함수
토큰의 전송 권한을 여러명에게 넘겨주기 위해 매핑 선언 및 함수 구현
//누가 누구에게 권한 부여를 했는가를 정의하는 매핑 정보 (여러명에게 권한을 부여하기 위해 정의)
mapping (address => mapping(address => bool)) operatorApprovals;
function setApprovalForAll(address _operator, bool _approved) public {
//권한을 부여받게될 operator 계정이 현재 함수를 호출한 계정인지 확인
require(_operator != msg.sende
operatorApprovals[msg.sender][_operator] = _approved;
}
ERC721 구현 - isApprovalForAll 함수
토큰의 소유자가 오퍼레이터에게 권한을 부여했는지 검증 하는 함수
function isApprovalForAll(address _owner, address _operator) public view returns (bool) {
return operatorApprovals[_owner][_operator];
}
ERC165 Interface
ERC165 의 목적은 컨트랙트가 어떤 인터페이스를 상속 받고 있는지 확인 하는 용도이다.
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
ERC165 Interface 구현
//인터페이스 식별자 매핑 정보
mapping (bytes4 => bool) supportedInterfaces;
constructor() public {
//ERC721 인터페이스의 식별자 키 값을 0x80ac58cd 로 정의하고 값을 true 로 함으로써 ERC721 인터페이스를 구현 하고 있는것을 생성자를 통해 정의 함.
supportedInterfaces[0x80ac58cd] = true;
}
function supportsInterfacebytes4 interfaceID) public view returns (bool) {
return supportedInterfaces[interfaceID];
}
반응형
'Blockchain' 카테고리의 다른 글
Smart contract Dev setup (0) | 2022.01.09 |
---|