참고문헌
•
이전에 포스팅 했던 내용에서 safeTransferFrom 함수는 말그대로 토큰을 안전하게 보내는 함수이다.
•
안전하게 토큰을 보내는 의미는 즉 그동안은 안전하지 못했다라는 것을 의미한다.
•
이전에는 어떻게 토큰을 보냈을까?
•
ERC721이 나오기 ERC20토큰이 있었는데 문제점은 ERC20토큰을 컨트랙 주소로 보냈을때 분실하는 경우가 많았다.
•
토큰을 교환하는 방식은 월렛과 월렛사이를 주고받을 수 있는 일반계정 방식이 있고 두번째는 컨트랙 계정으로 보내는 방식이 있다.
•
컨트랙을 배포했을때 나오는 주소를 계정이라고도 하는데, 일반계정들끼리는 토큰이 분실되는 위험이 적지만 컨트랙 계정으로 전송했을때 만약 그 컨트랙에 토큰을 다루는 기능이 없다면 토큰이 사라지게 된다.
•
이 문제를 해결하기위해 ERC721에서 safeTransferFrom()이라는 함수가 나오게 되었다.
•
safeTransferFrom()함수는 이전에 배웠던 transeferFrom()함수와 로직은 똑같다.
•
한가지 다른점은 safeTransferFrom()함수에 토큰을 받게되는 _to계정이 컨트랙계정일 경우에 해당 컨트랙이 ERC721호환성이 있는지 체크를 해줄수 있다.
실습
•
지난 코드
pragma solidity >=0.4.24 <=0.5.6;
interface ERC721 {
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);
}
contract ERC721Implementation is ERC721{
mapping(uint256 => address) tokenOwner;
mapping(address => uint256) ownedTokensCount;
function mint(address _to, uint _tokenId) public {
tokenOwner[_tokenId] = _to;
ownedTokensCount[_to] += 1;
}
function balanceOf(address _owner) public view returns (uint256){
return ownedTokensCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address){
return tokenOwner[_tokenId];
}
function transferFrom(address _from, address _to, uint256 _tokenId) public{
address owner = ownerOf(_tokenId);
require(msg.sender == owner);
require(_from != address(0));
require(_to != address(0));
ownedTokensCount[_from] -= 1;
tokenOwner[_tokenId] = address(0);
ownedTokensCount[_to] += 1;
tokenOwner[_tokenId] = _to;
}
}
Solidity
복사
•
본코드에서 아래의 코드 부분의 주석을 풀어준다.
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public;
Solidity
복사
•
아래의 컨트랙 안에 아래의 코드를 작성한다.
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public{
transferFrom(_from, _to, _tokenId);
if (isContract(_to)) {
}
}
Solidity
복사
•
상기의 코드 내용을 보면 매개변수로 받은 애들을 transferFrom에 넣어주고, 보낼 계정이 컨트랙계정인지 아닌지 확인하기위해서, if조건절로 isContract함수를 넣었다.
•
다음처럼 isContract함수를 작성하였다.
function isContract(address _addr) private view returns (bool){
uint256 size;
assembly { size:= extcodesize(_addr)}
return size >0;
}
Solidity
복사
•
주소를 받았을때 이 주소가 Contract 주소이면 True를 리턴하고, 일반계정이면 False를 리턴하는 함수이다. 이를 판별하기위해서 확인하는방법은 assembly code를 사용해야한다.
•
주소를 extcodesize에 넘겨서 사이즈가 0이면 일반계정이고, 0보다 크면 컨트랙 계정이라는 뜻이다.
•
이제 주소가 일반 계정인지 Contract 계정인지 판별을 했으면, 우리는 만약 Contract 계정일 경우, 이 Contract ERC721을 받을 수 있는 계정인지 확인해줘야한다.
•
상기의 내용을 위해 작성하는 함수는 다음과 같다.
/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface ERC721TokenReceiver {
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}
Solidity
복사
•
상기 함수는 아래의 링크에서 확인할 수 있다.
•
ERC721TokenReceiver함수는 onERC721Received함수를 구현하여, '제대로된4bytes 타입의 값'을 리턴을 했다면, 토큰을 받을 수 있는 컨트랙이라고 판별하는 함수라고 판단한다. 여기서 '제대로된 리턴값'이란 magic value라고부르며, 각 인터페이스마다 유티크한 식별자가 있는데, 이 식별자 값을 리턴해야만, 제대로된 값을 리턴했다고 판단한다. 위 경우 매직 벨류는, 맨위에 주석처리된 "0x150b7a02" 이다.
•
이값(매직 벨류는, 맨위에 주석처리된 "0x150b7a02")은 어디서 생성된 값일까요?
•
이 값은 ERC721TokenReceiver 인터페이스안에 있는 함수 이름과, 매개변수 타입들을 암호화시키고 4bytes로 자른 값입니다.
•
더 자세히말하면 bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")) 이 처럼 함수이름, 매개변수 타입들을 keccak256 알고리즘을 사용하여 해쉬로 변환하고 그 값을 4bytes로 잘라낸 값입니다.
최종 코드
// Klaytn IDE uses solidity 0.4.24, 0.5.6 versions.
pragma solidity >=0.4.24 <=0.5.6;
interface ERC721 {
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);
}
contract ERC721Implementation is ERC721{
mapping(uint256 => address) tokenOwner;
mapping(address => uint256) ownedTokensCount;
function mint(address _to, uint _tokenId) public {
tokenOwner[_tokenId] = _to;
ownedTokensCount[_to] += 1;
}
function balanceOf(address _owner) public view returns (uint256){
return ownedTokensCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address){
return tokenOwner[_tokenId];
}
function transferFrom(address _from, address _to, uint256 _tokenId) public{
address owner = ownerOf(_tokenId);
require(msg.sender == owner);
require(_from != address(0));
require(_to != address(0));
ownedTokensCount[_from] -= 1;
tokenOwner[_tokenId] = address(0);
ownedTokensCount[_to] += 1;
tokenOwner[_tokenId] = _to;
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId) public{
transferFrom(_from, _to, _tokenId);
if (isContract(_to)) {
}
}
function isContract(address _addr) private view returns (bool){
uint256 size;
assembly { size:= extcodesize(_addr)}
return size >0;
}
}
Solidity
복사