일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- server
- geth
- 제어의역전
- ERC165
- JavaScript
- Ethereum
- 솔리디티
- ethers
- 스마트 컨트랙트
- Docker
- 네트워크
- 이더리움
- tcp
- Python
- MySQL
- erc721
- 트랜잭션
- github
- solidity
- web3.js
- blockchain
- web3
- erc
- git
- web
- NFT
- Programming
- ERC20
- truffle
- 블록체인
- Today
- Total
멍개의 연구소
[ethereum] ERC721을 이용한 NFT 만들기 - 2편 본문
지난 시간에 우리는 NFT의 기본 개념과 ERC721을 이용하여 구현해보았습니다.
2022.08.28 - [블록체인] - [ethereum] ERC721을 이용한 NFT 만들기 - 1편
1편에서 작성한 코드를 추상화하여 좀 더 클린한 형태로 발전시켜 보겠습니다.
우리가 구현한 실제 비즈니스 로직이 포함된 코드는 다음과 같습니다.
contract CharactorByERC721 is ERC721{
struct Charactor {
string name; // 캐릭터 이름
uint256 level; // 캐릭터 레벨
}
Charactor[] public charactors; // default: []
address public owner; // 컨트랙트 소유자
constructor () public {
owner = msg.sender;
}
modifier isOwner() {
require(owner == msg.sender);
_;
}
// 캐릭터 생성
function mint(string memory name, address account) public isOwner {
uint256 cardId = charactors.length; // 유일한 캐릭터 ID
charactors.push(Charactor(name, 1));
_mint(account, cardId); // ERC721 소유권 등록
}
}
그렇다면, 우리는 이외의 코드는 굳이 직접 작성할 필요는 없습니다. 하지만 울리가 앞서서 ERC721 컨트랙트 인터페이스로 제공하지 않는 _mint()를 추가하였는데 이를 확장하고 우리가 개발에 집중할 수 있는 형태로 제공해보겠습니다.
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_tokenOwner[tokenId] = to;
_ownedTokensCount[to].increment();
emit Transfer(address(0), to, tokenId);
}
자 먼저 _mint에서 접근하는 녀석들을 알아보아야 합니다. _tokenOwner, _ownedTokensCount, _exists를 접근하고 있습니다.
// 토큰 ID에서 소유자로의 매핑
mapping (uint256 => address) private _tokenOwner;
// 토큰 ID에서 승인된 주소로의 매핑
mapping (uint256 => address) private _tokenApprovals;
function _exists(uint256 tokenId) internal view returns (bool) {
address owner = _tokenOwner[tokenId];
return owner != address(0);
}
이를 상속받은 컨트랙트에서 접근할 수 있도로 접근자를 수정합니다. 접근자는 private에서 interval로 바꿔줍니다.
// 토큰 ID에서 소유자로의 매핑
mapping (uint256 => address) interval _tokenOwner;
// 토큰 ID에서 승인된 주소로의 매핑
mapping (uint256 => address) internal _tokenApprovals;
function _exists(uint256 tokenId) internal view returns (bool) {
address owner = _tokenOwner[tokenId];
return owner != address(0);
}
· 수정된 ERC721 컨트랙트 코드
contract ERC721 is ERC165, IERC721 {
using SafeMath for uint256;
using Address for address;
using Counters for Counters.Counter;
bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
mapping (uint256 => address) internal _tokenOwner;
mapping (uint256 => address) internal _tokenApprovals;
mapping (address => Counters.Counter) internal _ownedTokensCount;
mapping (address => mapping (address => bool)) private _operatorApprovals;
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
constructor () public {
_registerInterface(_INTERFACE_ID_ERC721);
}
function balanceOf(address owner) public view returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address");
return _ownedTokensCount[owner].current();
}
function ownerOf(uint256 tokenId) public view returns (address) {
address owner = _tokenOwner[tokenId];
require(owner != address(0), "ERC721: owner query for nonexistent token");
return owner;
}
function approve(address to, uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
"ERC721: approve caller is not owner nor approved for all"
);
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
function getApproved(uint256 tokenId) public view returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId];
}
function setApprovalForAll(address to, bool approved) public {
require(to != msg.sender, "ERC721: approve to caller");
_operatorApprovals[msg.sender][to] = approved;
emit ApprovalForAll(msg.sender, to, approved);
}
function isApprovedForAll(address owner, address operator) public view returns (bool) {
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) public {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_transferFrom(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
transferFrom(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
function _exists(uint256 tokenId) internal view returns (bool) {
address owner = _tokenOwner[tokenId];
return owner != address(0);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
function _burn(address owner, uint256 tokenId) internal {
require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");
_clearApproval(tokenId);
_ownedTokensCount[owner].decrement();
_tokenOwner[tokenId] = address(0);
emit Transfer(owner, address(0), tokenId);
}
function _burn(uint256 tokenId) internal {
_burn(ownerOf(tokenId), tokenId);
}
function _transferFrom(address from, address to, uint256 tokenId) internal {
require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
require(to != address(0), "ERC721: transfer to the zero address");
_clearApproval(tokenId);
_ownedTokensCount[from].decrement();
_ownedTokensCount[to].increment();
_tokenOwner[tokenId] = to;
emit Transfer(from, to, tokenId);
}
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
internal returns (bool)
{
if (!to.isContract()) {
return true;
}
bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
return (retval == _ERC721_RECEIVED);
}
function _clearApproval(uint256 tokenId) private {
if (_tokenApprovals[tokenId] != address(0)) {
_tokenApprovals[tokenId] = address(0);
}
}
}
https://github.com/Exchange-M/NFT-ERC721/blob/master/ERC721.sol
이제 우리는 깃허브에 배포한 파일을 가져와서 ERC721의 _mint를 구현한 후 비즈니스 로직에 집중할 수 있습니다. 기능 여하에 따라 코드를 개선한 후 올려도 됩니다.
· 개선코드 적용
pragma solidity ^0.5.0;
import 'https://github.com/Exchange-M/NFT-ERC721/blob/master/ERC721.sol';
contract ERC721Impl is ERC721 {
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_tokenOwner[tokenId] = to;
_ownedTokensCount[to].increment();
emit Transfer(address(0), to, tokenId);
}
}
contract CharactorByERC721 is ERC721Impl{
struct Charactor {
string name; // 캐릭터 이름
uint256 level; // 캐릭터 레벨
}
Charactor[] public charactors; // default: []
address public owner; // 컨트랙트 소유자
constructor () public {
owner = msg.sender;
}
modifier isOwner() {
require(owner == msg.sender);
_;
}
// 캐릭터 생성
function mint(string memory name, address account) public isOwner {
uint256 cardId = charactors.length; // 유일한 캐릭터 ID
charactors.push(Charactor(name, 1));
_mint(account, cardId); // ERC721 소유권 등록
}
}
이런 형태로 작성할 수 있습니다. 코드 자체가 훨씬 클린해졌습니다.
추각적으로 ERC721 컨트랙트에 있는 메서드들 중에 _burn 또한 ERC721의 기본 인터페이스가 아니므로 상속받은 컨트랙트에서 구성할 수 있습니다.
정상적으로 배포된 모습을 확인할 수 있습니다.