Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Yunseongpyo] NFT Contract 작성 #3

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 68 additions & 36 deletions KIP17NFTContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1127,8 +1127,10 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata {

// Optional mapping for token URIs
mapping(uint256 => string) private _tokenURIs;
// 🔥 mapping for token Level
mapping(uint256 => uint) private _tokenLevel;
// 🔥 mapping for nftType
mapping(uint256 => uint) private _nftType;

mapping(uint256 => string) private _menuType;
/*
* bytes4(keccak256('name()')) == 0x06fdde03
* bytes4(keccak256('symbol()')) == 0x95d89b41
Expand Down Expand Up @@ -1179,18 +1181,27 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata {
}

/**
* @dev 🔥 Returns an level for a given token ID.
* @dev 🔥 Returns an nftType for a given token ID.
* Throws if the token ID does not exist. May return an empty string.
* @param tokenId uint256 ID of the token to query
*/
function tokenLevel(uint256 tokenId) external view returns (uint) {
function nftType(uint256 tokenId) external view returns (uint) {
require(_exists(tokenId), "KIP17Metadata: URI query for nonexistent token");
return _tokenLevel[tokenId];
return _nftType[tokenId];
}

function menuType(uint256 tokenId) external view returns (string memory) {
require(
_exists(tokenId),
"KIP17Metadata: URI query for nonexistent token"
);
return _menuType[tokenId];
}


//소유한 토큰 종류 확인(맞으면 true, 아니면 false)
function _ownTokenLevel(uint256 tokenId, uint level) internal returns (bool){
return _tokenLevel[tokenId]==level;
function _ownNftType(uint256 tokenId, string memory menuType) internal returns (bool){
return keccak256(abi.encodePacked(_menuType[tokenId])) == keccak256(abi.encodePacked(menuType));
}

/**
Expand All @@ -1207,16 +1218,19 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata {
_tokenURIs[tokenId] = uri;
}

/**
* @dev 🔥 Internal function to set the token level for a given token.
* Reverts if the token ID does not exist.
* @param tokenId uint256 ID of the token to set its URI
* @param level uint to assign
*/
function _setTokenLevel(uint256 tokenId, uint level) internal {

function _setNftType(uint256 tokenId, uint nftType) internal {
require(_exists(tokenId), "KIP17Metadata: URI set of nonexistent token");
_tokenLevel[tokenId] = level;
_nftType[tokenId] = nftType;
}


function _setMenuType(uint256 tokenId, string memory menuType) internal{
require(_exists(tokenId), "KIP17Metadata: URI set of nonexistent token");
_menuType[tokenId] = menuType;
}


/**
* @dev Internal function to burn a specific token.
* Reverts if the token does not exist.
Expand All @@ -1225,10 +1239,14 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata {
* @param tokenId uint256 ID of the token being burned by the msg.sender
*/
//마스터 발급을 위한 기존 메뉴 NFT삭제
function _burnForMasterNFT(address owner, uint256 tokenId, uint level) internal{
if(level == _tokenLevel[tokenId])
function _burnForMasterNFT(address owner, uint256 tokenId, string memory menuType) internal returns (bool){
if(keccak256(abi.encodePacked(_menuType[tokenId])) == keccak256(abi.encodePacked(menuType)))
{
_burn(owner, tokenId);
return true;
}
else{
return false;
}
}

Expand All @@ -1240,8 +1258,12 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata {
if (bytes(_tokenURIs[tokenId]).length != 0) {
delete _tokenURIs[tokenId];
}
if (_tokenLevel[tokenId] > 0) {
delete _tokenLevel[tokenId];
if (_nftType[tokenId] > 0) {
delete _nftType[tokenId];
}
if (bytes(_menuType[tokenId]).length != 0){
delete _menuType[tokenId];

}

}
Expand Down Expand Up @@ -1392,16 +1414,17 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole {
* @param tokenURI The token URI of the minted token.
* @return A boolean that indicates if the operation was successful.
*/
// tokenLevel 추가
function mintWithTokenURI(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    //mint 유료: 0.5 klay 소모
    function mintWithKlay(
        address to,
        uint256 tokenId,
        string memory tokenURI,
        string memory menuType,
        address payable receiver
    ) public payable returns (bool) {
        receiver.transfer(10**17*5);

        mintWithTokenURI(to, tokenId, tokenURI, menuType);
        return true;
    }

위와 같이 보완했어요! mintWithTokenURI 에서 nftType을 인자로 넣지 않는 것을 반영했어요.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 receiver를 인자로 받지 않기 위해 아래와 같이 다시 수정했습니다.

   address private _owner;

    /**
     * @dev Constructor function.
     */
    constructor () public {
        // register the supported interface to conform to KIP17Mintable via KIP13
        _registerInterface(_INTERFACE_ID_KIP17_METADATA_MINTABLE);
        _owner = msg.sender;
    }

    function owner() public view returns (address) {
        return _owner;
    }
   //mint 유료: 0.5 klay 소모
    function mintWithKlay(
        address to,
        uint256 tokenId,
        string memory tokenURI,
        string memory menuType
    ) public payable returns (bool) {
        address payable receiver = address(uint160(owner())); //receiver 세팅
        receiver.transfer(10**17*5);

        mintWithTokenURI(to, tokenId, tokenURI, menuType);
        return true;
    }

address to,
uint256 tokenId,
string memory tokenURI,
uint level
uint nftType,
string memory menuType
) public onlyMinter returns (bool) {
_mint(to, tokenId);
_setTokenURI(tokenId, tokenURI);
_setTokenLevel(tokenId, level);
_setNftType(tokenId, nftType);
_setMenuType(tokenId, menuType);
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nftType == 1 : 일반 NFT
nftType == 2 : 마스터 NFT

    function mintWithTokenURI(
        address to,
        uint256 tokenId,
        string memory tokenURI,
        string memory menuType
    ) public onlyMinter returns (bool) {
        uint256 userBalance = balanceOf(to);

        //특정 NFT(ex: 국밥 NFT)를 19개 이상 소유했는지 판별해서 19개를 삭제한 후 마스터 NFT 배지 발행
        if(_checkMenu(to, menuType, userBalance) >= 19) {
            _removeOwnToken(to, menuType);
            _mint(to, tokenId);
            _setTokenURI(tokenId, tokenURI);
            _setNftType(tokenId, 2); // nftType을 인자로 넘겨받지 않고 2로 세팅해서 실행
            _setMenuType(tokenId, menuType);
        } else {
           //메뉴 NFT 19개 보유자가 아니라면 그냥 일반 mint 실행
            _mint(to, tokenId);
            _setTokenURI(tokenId, tokenURI);
            _setNftType(tokenId, 1); // nftType을 인자로 넘겨받지 않고 1로 세팅해서 실행
            _setMenuType(tokenId, menuType);
        }
        return true;
    }

mintMasterBadge 함수 호출 대신 mintWithTokenURI 에서 한번에 처리해봤어요!
그리고 민팅할 때마다 몇개인지 판별할 수 있어서 더 괜찮게 보완한 것 같습니다 :)

}

Expand All @@ -1410,21 +1433,22 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole {
address to,
uint256 tokenId,
string memory tokenURI,
uint level,
uint nftType,
string memory menuType,
address payable reciver
) public payable returns (bool) {
// reciver = Ownable(NFT).owner();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 저도 찾아보려고 했는데 Ownable을 상속받을 때 순서가 중요한데 잘 안되더라구요 ㅠㅠ 그래서 그냥 인자로 넣어주는 방식으로 가죠!

reciver.transfer(10**17*5);
mintWithTokenURI(to,tokenId, tokenURI,level);
mintWithTokenURI(to,tokenId, tokenURI,nftType, menuType);
return true;
}
//마스터 뱃지 mint (수정사항 : 마스터 뱃지레벨, 일반 뱃지(메뉴별 레벨)로 구분)
//마스터 뱃지 mint
function mintMasterBadge(
address to,
uint256 tokenId,
string memory tokenURI,
uint setLevel,
uint delLevel,
uint nftType,
string memory menuType,
address NFT
) public returns (bool){
uint256 userBalance;
Expand All @@ -1433,17 +1457,17 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole {
_listOfUserToeknId(userBalance, to, NFT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_listOfUserToeknId 함수는 필요 없을 것 같아요!

    function getOwnedTokens(address owner) public view returns (uint256[] memory) {
        return _ownedTokens[owner];
    }

기존에 있는 위 함수를 써서 owner 에 소유자 address를 인자로 넣으면 tokenId 배열 리스트를 가져올 수 있습니다 :)


//특정 NFT(국밥 마스터 뱃지 인지)가 20개 이상 소유한지 판별
require(_checkMenu(delLevel, userBalance) >= 20, "You must have menu20NFTs");
_removeOwnToken(to, delLevel);
mintWithTokenURI(to,tokenId, tokenURI,setLevel);
require(_checkMenu(menuType, userBalance) >= 20, "You must have menu20NFTs");
_removeOwnToken(to, menuType);
mintWithTokenURI(to,tokenId, tokenURI,nftType,menuType);
return true;
}

//소유한 특정메뉴 갯수 확인
function _checkMenu(uint level, uint256 balance) private returns(uint256){
function _checkMenu(string memory menuType, uint256 balance) private returns(uint256){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    //소유한 특정 메뉴 NFT 갯수 확인
    function _checkMenu(address owner, string memory menuType, uint256 balance) private returns(uint256){
        uint256 result = 0;
        uint256[] memory owendAllTokenList = getOwnedTokens(owner);
        for (uint256 i = 0; i< balance; i++){
            if(_ownNftType(owendAllTokenList[i], menuType)){
                result++;
            }
        }
        return result;
    }

userId라는 배열을 굳이 만들지 않고 getOwnedTokens함수를 활용해서 토큰 리스트를 가져올 수 있어요.
그래서 위와 같이 보완해봤어요 :)

uint256 result = 0;
for (uint256 i =0 ; i< balance; i++){
if(_ownTokenLevel(_userid[i], level)){
if(_ownNftType(_userid[i], menuType)){
result++;
}
}
Expand All @@ -1459,11 +1483,19 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole {
}
}
// 소유한 20개의 메뉴 NFT 삭제
function _removeOwnToken(address to, uint level) private{

function _removeOwnToken(address to, string memory menuType) private{
uint256 count =0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    //소유한 19개 메뉴 NFT burn
    function _removeOwnToken(address to, string memory menuType) private{
        uint256 count = 0;
        uint256[] memory owendAllTokenList = getOwnedTokens(to);
        //유저가 가지고 있는 기존 NFT 19개 삭제
        for (uint256 i = 0; i < owendAllTokenList.length; i++) {
            bool isSucess = _burnForMasterNFT(to, owendAllTokenList[i], menuType);
            if(isSucess) {
                count ++;
            }
            if(count == 19) {
                break;
            }
        }
    }

여기도 마찬가지로 getOwnedTokens 함수를 활용했습니다.
그리고 mintWithTokenURI 함수를 호출할 때 19개를 소유했으면 추가로 같은 메뉴 NFT 1개를 발행하지 않고 바로 19개 소각하고 마스터 NFT배지를 발행하면 되서 19개까지만 반복문을 돌았습니다. :)

//유저가 가지고 있는 기존 NFT 삭제(20개 이상을 소유할 수도 있으니 20개만 삭제)
for (uint256 i = 0; i < 20; i++) {
_burnForMasterNFT(to, _userid[i],level);
for (uint256 i = 0; i < _userid.length; i++) {
bool isSucess = _burnForMasterNFT(to, _userid[i],menuType);
if(isSucess)
{
count ++;
}
if(count ==20)
{
break;
}
}

_useridInit();
Expand Down
90 changes: 68 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,76 @@
2. 함수 추가
#### Ownable, TokenLevel 추가
- [수빈님 코드 참조](https://github.com/BadgeMeal/badgemeal-contract/tree/subin#:~:text=KIP17Metadata%20%EC%BB%A8%ED%8A%B8%EB%9E%99%ED%8A%B8%20%EC%88%98%EC%A0%95%20%EC%82%AC%ED%95%AD, "링크")
- level -> nftType 변경(1 : 일반 nft, 2 : 마스터 nft)
- menuType 추가 (string 형식) : menuType, _setMenuType 추가, 기존 _burn함수에 menuType삭제 추가
```sol
mapping(uint256 => string) private _menuType;

function menuType(uint256 tokenId) external view returns (string memory) {
require(
_exists(tokenId),
"KIP17Metadata: URI query for nonexistent token"
);
return _menuType[tokenId];
}
function _setMenuType(uint256 tokenId, string memory menuType) internal{
require(_exists(tokenId), "KIP17Metadata: URI set of nonexistent token");
_menuType[tokenId] = menuType;
}

function _burn(address owner, uint256 tokenId) internal {

super._burn(owner, tokenId);
// Clear metadata (if any)
if (bytes(_tokenURIs[tokenId]).length != 0) {
delete _tokenURIs[tokenId];
}
if (_nftType[tokenId] > 0) {
delete _nftType[tokenId];
}
if (bytes(_menuType[tokenId]).length != 0){
delete _menuType[tokenId];
}

}
```
#### mintWithTokenURI : NFT 발행
- [mint함수명 원복](https://github.com/BadgeMeal/badgemeal-contract/pull/3#discussion_r806472363, "review01")
- level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨)
- ~~level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨)~~
- level -> nftType 변경, menuType 추가
```sol
function mintWithTokenURI(
address to,
uint256 tokenId,
string memory tokenURI,
uint level
uint nftType,
string memory menuType
) public onlyMinter returns (bool) {
_mint(to, tokenId);
_setTokenURI(tokenId, tokenURI);
_setTokenLevel(tokenId, level);
_setNftType(tokenId, nftType);
_setMenuType(tokenId, menuType);
return true;
}
```

***
#### mintWithKlay : NFT 발행(수수료0.5Klay)
- level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨)
- ~~level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨)~~
- owner address를 불러와 시도를 해볼려고 했지만 payable가 제대로 안되어서 우선은 기존처럼 해놓았습니다.
- level -> nftType 변경, menuType 추가
```sol
//mint 유료, 0.5 klay
function mintWithKlay(
address to,
uint256 tokenId,
string memory tokenURI,
uint level,
uint nftType,
string memory menuType,
address payable reciver
) public payable returns (bool) {
// reciver = Ownable(NFT).owner();
reciver.transfer(10**17*5);
mintWithTokenURI(to,tokenId, tokenURI,level);
mintWithTokenURI(to,tokenId, tokenURI,nftType, menuType);
return true;
}
```
Expand All @@ -55,15 +92,17 @@ function mintWithKlay(
#### mintMasterBadge : 마스터 뱃지 발행
- [특정메뉴 20개 이상 소유 확인](https://github.com/BadgeMeal/badgemeal-contract/pull/3#discussion_r805961010, "review2")
- [userList 초기화](https://github.com/BadgeMeal/badgemeal-contract/pull/3#discussion_r806795345, "review3")
- level기준으로 메뉴 갯수 확인 및 삭제 기능 보완
- ~~level기준으로 메뉴 갯수 확인 및 삭제 기능 보완~~
- menuType기준으로 마스터 NFT 메뉴명 등록 및 기존 일반NFT 삭제
- 메뉴 삭제시 중간에 다른 메뉴 타입이 있는경우 안지워지는 버그 수정
```sol
//마스터 뱃지 mint (수정사항 : 마스터 뱃지레벨, 일반 뱃지(메뉴별 레벨)로 구분)
//마스터 뱃지 mint
function mintMasterBadge(
address to,
uint256 tokenId,
string memory tokenURI,
uint setLevel,
uint delLevel,
uint nftType,
string memory menuType,
address NFT
) public returns (bool){
uint256 userBalance;
Expand All @@ -72,17 +111,17 @@ function mintWithKlay(
_listOfUserToeknId(userBalance, to, NFT);

//특정 NFT(국밥 마스터 뱃지 인지)가 20개 이상 소유한지 판별
require(_checkMenu(delLevel, userBalance) >= 20, "You must have menu20NFTs");
_removeOwnToken(to, delLevel);
mintWithTokenURI(to,tokenId, tokenURI,setLevel);
require(_checkMenu(menuType, userBalance) >= 20, "You must have menu20NFTs");
_removeOwnToken(to, menuType);
mintWithTokenURI(to,tokenId, tokenURI,nftType,menuType);
return true;
}

//소유한 특정메뉴 갯수 확인
function _checkMenu(uint level, uint256 balance) private returns(uint256){
function _checkMenu(string memory menuType, uint256 balance) private returns(uint256){
uint256 result = 0;
for (uint256 i =0 ; i< balance; i++){
if(_ownTokenLevel(_userid[i], level)){
if(_ownNftType(_userid[i], menuType)){
result++;
}
}
Expand All @@ -98,11 +137,19 @@ function mintWithKlay(
}
}
// 소유한 20개의 메뉴 NFT 삭제
function _removeOwnToken(address to, uint level) private{

function _removeOwnToken(address to, string memory menuType) private{
uint256 count =0;
//유저가 가지고 있는 기존 NFT 삭제(20개 이상을 소유할 수도 있으니 20개만 삭제)
for (uint256 i = 0; i < 20; i++) {
_burnForMasterNFT(to, _userid[i],level);
for (uint256 i = 0; i < _userid.length; i++) {
bool isSucess = _burnForMasterNFT(to, _userid[i],menuType);
if(isSucess)
{
count ++;
}
if(count ==20)
{
break;
}
}

_useridInit();
Expand All @@ -116,5 +163,4 @@ function mintWithKlay(
}

```
***
- 현재 마스터 NFT와 일반 NFT는 level로 구분이 되는데 일반 NFT에서 메뉴끼리는 구별이 안되어 있습니다. 구별을 데이터에서 가져올지 안가져올지 확실하지 않아서 우선은 컨트랙트자체에서 해결할 수 있게 level에서 구분할수 있게 짜 놓았습니다.
***