From 4caec2e85ceead8dfeb9928500da57e37cf6dca0 Mon Sep 17 00:00:00 2001 From: anxiubin Date: Fri, 11 Feb 2022 10:50:49 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20=EA=B8=B0=EC=A1=B4=EC=97=90=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=96=88=EB=8D=98=20vote=20contract=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- voteContract.sol | 128 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 voteContract.sol diff --git a/voteContract.sol b/voteContract.sol new file mode 100644 index 0000000..d613c51 --- /dev/null +++ b/voteContract.sol @@ -0,0 +1,128 @@ +pragma solidity ^0.5.0; + +contract Vote { + + struct Proposal { + string name; // 메뉴 이름 + uint voteCount; // 투표 받은 수 + string imageUrl; // 메뉴 이미지 url + address proposer; // 메뉴 제안자 + + } + + struct Voter { + bool voted; // 투표 진행 여부 (true,false) + uint vote; // Menu 리스트 요소의 index (0,1,2 ...) + uint weight; // 투표 권한 + + } + + address public deployer; // 컨트랙트 배포자 + + mapping(address => Voter) public voters; // 투표자 매핑 + + address[] public voterList; // 투표자 리스트 + + Proposal[] public proposals; // 메뉴 리스트 + + Proposal[] public winnerProposals; // 투표로 채택된 메뉴 리스트 + + constructor() public { + deployer = msg.sender; + } + + + // 메뉴 추가 함수 + function proposeMenu(string memory name, string memory imageUrl) public { + /** 🔥 pseudocode 추가 + [require] msg.sender == 뱃지밀 마스터 NFT 소유자, "메뉴 추가 제안 권한이 없습니다." + */ + + proposals.push(Proposal({ + name: name, + voteCount: 0, + imageUrl: imageUrl, + proposer: msg.sender + })); + } + + // 투표자 리스트 추가 함수 + function addVoters(address[] memory addressList) public { + require( + msg.sender == deployer, + "Only deployer can give right to vote." + ); + require( + voterList.length == 0, + "Already added Voters." + ); + + /** 🔥 수정 필요 + addressList를 입력값으로 받는 것이 아니라 NFT 홀더 리스트를 가져오는 함수를 호출한 후 아래 로직을 실행하는게 좋을 것 같다. + */ + + for (uint i = 0; i < addressList.length; i++) { + voterList.push(addressList[i]); + } + } + + // 투표 권한 부여 함수 + function giveRightToVote(address voter) private { + require( + msg.sender == deployer, + "Only deployer can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + // 투표자 리스트 모두에게 권한 부여 + function giveVotersRightToVote() public { + for (uint i = 0; i < voterList.length; i++) { + giveRightToVote(voterList[i]); + } + } + + + // 투표 함수 + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + + /** 🔥 pseudocode 추가 + [require] msg.sender == 뱃지밀 일반 NFT 소유자, "투표 권한이 없습니다." + */ + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + + sender.voted = true; + sender.vote = proposal; + + proposals[proposal].voteCount += sender.weight; + } + + // 가장 많은 득표수를 얻은 메뉴 index 출력하는 함수 + function winningProposal() public view returns (uint winningProposal_) { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + // 가장 많은 득표수를 얻은 메뉴 이름을 리턴하는 함수 + function winnerName() public view returns (string memory winnerName_) { + winnerName_ = proposals[winningProposal()].name; + } + + /** 🔥 pseudocode 추가 + function 투표 마감 + 1. 투표 시간이 마감되면 가장 많은 득표수를 얻은 메뉴 Proposal을 winnerProposals 에 push 한다. + 2. Proposals를 초기화한다. + */ +} \ No newline at end of file From 99d98a457d1b2e967efa821d9cd1b28dcbe35da1 Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Sun, 13 Feb 2022 13:36:30 +0900 Subject: [PATCH 2/7] =?UTF-8?q?:construction:=20=EA=B8=B0=EC=A1=B4KIP17=20?= =?UTF-8?q?NFTContract=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KIP17NFTContract.sol | 1629 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1629 insertions(+) create mode 100644 KIP17NFTContract.sol diff --git a/KIP17NFTContract.sol b/KIP17NFTContract.sol new file mode 100644 index 0000000..7456458 --- /dev/null +++ b/KIP17NFTContract.sol @@ -0,0 +1,1629 @@ +// File: contracts/introspection/IKIP13.sol + +pragma solidity ^0.5.0; + +/** + * @dev Interface of the KIP-13 standard, as defined in the + * [KIP-13](http://kips.klaytn.com/KIPs/kip-13-interface_query_standard). + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others. + * + * For an implementation, see `KIP13`. + */ +interface IKIP13 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * [KIP-13 section](http://kips.klaytn.com/KIPs/kip-13-interface_query_standard#how-interface-identifiers-are-defined) + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// File: contracts/token/KIP17/IKIP17.sol + +pragma solidity ^0.5.0; + +/** + * @dev Required interface of an KIP17 compliant contract. + */ +contract IKIP17 is IKIP13 { + 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 + ); + + /** + * @dev Returns the number of NFTs in `owner`'s account. + */ + function balanceOf(address owner) public view returns (uint256 balance); + + /** + * @dev Returns the owner of the NFT specified by `tokenId`. + */ + function ownerOf(uint256 tokenId) public view returns (address owner); + + /** + * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to + * another (`to`). + * + * Requirements: + * - `from`, `to` cannot be zero. + * - `tokenId` must be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this + * NFT by either `approve` or `setApproveForAll`. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public; + + /** + * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to + * another (`to`). + * + * Requirements: + * - If the caller is not `from`, it must be approved to move this NFT by + * either `approve` or `setApproveForAll`. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public; + + function approve(address to, uint256 tokenId) public; + + function getApproved(uint256 tokenId) + public + view + returns (address operator); + + function setApprovalForAll(address operator, bool _approved) public; + + function isApprovedForAll(address owner, address operator) + public + view + returns (bool); + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory data + ) public; +} + +// File: contracts/token/KIP17/IERC721Receiver.sol + +pragma solidity ^0.5.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +contract IERC721Receiver { + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safeTransfer`. This function MUST return the function selector, + * otherwise the caller will revert the transaction. The selector to be + * returned can be obtained as `this.onERC721Received.selector`. This + * function MAY throw to revert and reject the transfer. + * Note: the ERC721 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 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes memory data + ) public returns (bytes4); +} + +// File: contracts/token/KIP17/IKIP17Receiver.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from KIP17 asset contracts. + * @dev see http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract IKIP17Receiver { + /** + * @notice Handle the receipt of an NFT + * @dev The KIP17 smart contract calls this function on the recipient + * after a `safeTransfer`. This function MUST return the function selector, + * otherwise the caller will revert the transaction. The selector to be + * returned can be obtained as `this.onKIP17Received.selector`. This + * function MAY throw to revert and reject the transfer. + * Note: the KIP17 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 `bytes4(keccak256("onKIP17Received(address,address,uint256,bytes)"))` + */ + function onKIP17Received( + address operator, + address from, + uint256 tokenId, + bytes memory data + ) public returns (bytes4); +} + +// File: contracts/math/SafeMath.sol + +pragma solidity ^0.5.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * - Subtraction cannot overflow. + * + * _Available since v2.4.0._ + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + // Solidity only automatically asserts when dividing by 0 + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * - The divisor cannot be zero. + * + * _Available since v2.4.0._ + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +// File: contracts/utils/Address.sol + +pragma solidity ^0.5.0; + +/** + * @dev Collection of functions related to the address type, + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * This test is non-exhaustive, and there may be false-negatives: during the + * execution of a contract's constructor, its address will be reported as + * not containing a contract. + * + * > It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(account) + } + return size > 0; + } +} + +// File: contracts/drafts/Counters.sol + +pragma solidity ^0.5.0; + +/** + * @title Counters + * @author Matt Condon (@shrugs) + * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number + * of elements in a mapping, issuing ERC721 ids, or counting request ids. + * + * Include with `using Counters for Counters.Counter;` + * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath + * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never + * directly accessed. + */ +library Counters { + using SafeMath for uint256; + + struct Counter { + // This variable should never be directly accessed by users of the library: interactions must be restricted to + // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add + // this feature: see https://github.com/ethereum/solidity/issues/4637 + uint256 _value; // default: 0 + } + + function current(Counter storage counter) internal view returns (uint256) { + return counter._value; + } + + function increment(Counter storage counter) internal { + counter._value += 1; + } + + function decrement(Counter storage counter) internal { + counter._value = counter._value.sub(1); + } +} + +// File: contracts/introspection/KIP13.sol + +pragma solidity ^0.5.0; + +/** + * @dev Implementation of the `IKIP13` interface. + * + * Contracts may inherit from this and call `_registerInterface` to declare + * their support of an interface. + */ +contract KIP13 is IKIP13 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_KIP13 = 0x01ffc9a7; + + /** + * @dev Mapping of interface ids to whether or not it's supported. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor() internal { + // Derived contracts need only register support for their own interfaces, + // we register support for KIP13 itself here + _registerInterface(_INTERFACE_ID_KIP13); + } + + /** + * @dev See `IKIP13.supportsInterface`. + * + * Time complexity O(1), guaranteed to always use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) + external + view + returns (bool) + { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registers the contract as an implementer of the interface defined by + * `interfaceId`. Support of the actual KIP13 interface is automatic and + * registering its interface id is not required. + * + * See `IKIP13.supportsInterface`. + * + * Requirements: + * + * - `interfaceId` cannot be the KIP13 invalid interface (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal { + require(interfaceId != 0xffffffff, "KIP13: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} + +// File: contracts/token/KIP17/KIP17.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17 Non-Fungible Token Standard basic implementation + * @dev see http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract KIP17 is KIP13, IKIP17 { + using SafeMath for uint256; + using Address for address; + using Counters for Counters.Counter; + + // Equals to `bytes4(keccak256("onKIP17Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IKIP17Receiver(0).onKIP17Received.selector` + bytes4 private constant _KIP17_RECEIVED = 0x6745782b; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping(uint256 => address) private _tokenOwner; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to number of owned token + mapping(address => Counters.Counter) private _ownedTokensCount; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /* + * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 + * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e + * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 + * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc + * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 + * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c + * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde + * + * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ + * 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd + */ + bytes4 private constant _INTERFACE_ID_KIP17 = 0x80ac58cd; + + constructor() public { + // register the supported interfaces to conform to KIP17 via KIP13 + _registerInterface(_INTERFACE_ID_KIP17); + } + + /** + * @dev Gets the balance of the specified address. + * @param owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address owner) public view returns (uint256) { + require( + owner != address(0), + "KIP17: balance query for the zero address" + ); + + return _ownedTokensCount[owner].current(); + } + + /** + * @dev Gets the owner of the specified token ID. + * @param tokenId uint256 ID of the token to query the owner of + * @return address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 tokenId) public view returns (address) { + address owner = _tokenOwner[tokenId]; + require( + owner != address(0), + "KIP17: owner query for nonexistent token" + ); + + return owner; + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param to address to be approved for the given token ID + * @param tokenId uint256 ID of the token to be approved + */ + function approve(address to, uint256 tokenId) public { + address owner = ownerOf(tokenId); + require(to != owner, "KIP17: approval to current owner"); + + require( + msg.sender == owner || isApprovedForAll(owner, msg.sender), + "KIP17: approve caller is not owner nor approved for all" + ); + + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * Reverts if the token ID does not exist. + * @param tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 tokenId) public view returns (address) { + require( + _exists(tokenId), + "KIP17: approved query for nonexistent token" + ); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf. + * @param to operator address to set the approval + * @param approved representing the status of the approval to be set + */ + function setApprovalForAll(address to, bool approved) public { + require(to != msg.sender, "KIP17: approve to caller"); + + _operatorApprovals[msg.sender][to] = approved; + emit ApprovalForAll(msg.sender, to, approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner. + * @param owner owner address which you want to query the approval of + * @param operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll(address owner, address operator) + public + view + returns (bool) + { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address. + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible. + * Requires the msg.sender to be the owner, approved, or operator. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public { + //solhint-disable-next-line max-line-length + require( + _isApprovedOrOwner(msg.sender, tokenId), + "KIP17: transfer caller is not owner nor approved" + ); + + _transferFrom(from, to, tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onKIP17Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onKIP17Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onKIP17Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onKIP17Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg.sender to be the owner, approved, or operator + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public { + transferFrom(from, to, tokenId); + require( + _checkOnKIP17Received(from, to, tokenId, _data), + "KIP17: transfer to non KIP17Receiver implementer" + ); + } + + /** + * @dev Returns whether the specified token exists. + * @param tokenId uint256 ID of the token to query the existence of + * @return bool whether the token exists + */ + function _exists(uint256 tokenId) internal view returns (bool) { + address owner = _tokenOwner[tokenId]; + return owner != address(0); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID. + * @param spender address of the spender to query + * @param tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) + internal + view + returns (bool) + { + require( + _exists(tokenId), + "KIP17: operator query for nonexistent token" + ); + address owner = ownerOf(tokenId); + return (spender == owner || + getApproved(tokenId) == spender || + isApprovedForAll(owner, spender)); + } + + /** + * @dev Internal function to mint a new token. + * Reverts if the given token ID already exists. + * @param to The address that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + require(to != address(0), "KIP17: mint to the zero address"); + require(!_exists(tokenId), "KIP17: token already minted"); + + _tokenOwner[tokenId] = to; + _ownedTokensCount[to].increment(); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + require( + ownerOf(tokenId) == owner, + "KIP17: burn of token that is not own" + ); + + _clearApproval(tokenId); + + _ownedTokensCount[owner].decrement(); + _tokenOwner[tokenId] = address(0); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * @param tokenId uint256 ID of the token being burned + */ + function _burn(uint256 tokenId) internal { + _burn(ownerOf(tokenId), tokenId); + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom( + address from, + address to, + uint256 tokenId + ) internal { + require( + ownerOf(tokenId) == from, + "KIP17: transfer of token that is not own" + ); + require(to != address(0), "KIP17: transfer to the zero address"); + + _clearApproval(tokenId); + + _ownedTokensCount[from].decrement(); + _ownedTokensCount[to].increment(); + + _tokenOwner[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Internal function to invoke `onKIP17Received` on a target address. + * The call is not executed if the target address is not a contract. + * + * This function is deprecated. + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnKIP17Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal returns (bool) { + bool success; + bytes memory returndata; + + if (!to.isContract()) { + return true; + } + + // Logic for compatibility with ERC721. + (success, returndata) = to.call( + abi.encodeWithSelector( + _ERC721_RECEIVED, + msg.sender, + from, + tokenId, + _data + ) + ); + if ( + returndata.length != 0 && + abi.decode(returndata, (bytes4)) == _ERC721_RECEIVED + ) { + return true; + } + + (success, returndata) = to.call( + abi.encodeWithSelector( + _KIP17_RECEIVED, + msg.sender, + from, + tokenId, + _data + ) + ); + if ( + returndata.length != 0 && + abi.decode(returndata, (bytes4)) == _KIP17_RECEIVED + ) { + return true; + } + + return false; + } + + /** + * @dev Private function to clear current approval of a given token ID. + * @param tokenId uint256 ID of the token to be transferred + */ + function _clearApproval(uint256 tokenId) private { + if (_tokenApprovals[tokenId] != address(0)) { + _tokenApprovals[tokenId] = address(0); + } + } +} + +// File: contracts/token/KIP17/IKIP17Enumerable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP-17 Non-Fungible Token Standard, optional enumeration extension + * @dev See http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract IKIP17Enumerable is IKIP17 { + function totalSupply() public view returns (uint256); + + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256 tokenId); + + function tokenByIndex(uint256 index) public view returns (uint256); +} + +// File: contracts/token/KIP17/KIP17Enumerable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP-17 Non-Fungible Token with optional enumeration extension logic + * @dev See http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract KIP17Enumerable is KIP13, KIP17, IKIP17Enumerable { + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) private _ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] private _allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) private _allTokensIndex; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_KIP17_ENUMERABLE = 0x780e9d63; + + /** + * @dev Constructor function. + */ + constructor() public { + // register the supported interface to conform to KIP17Enumerable via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_ENUMERABLE); + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner. + * @param owner address owning the tokens list to be accessed + * @param index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256) + { + require( + index < balanceOf(owner), + "KIP17Enumerable: owner index out of bounds" + ); + return _ownedTokens[owner][index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract. + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return _allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens. + * @param index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 index) public view returns (uint256) { + require( + index < totalSupply(), + "KIP17Enumerable: global index out of bounds" + ); + return _allTokens[index]; + } + + /** + * @dev Internal function to transfer ownership of a given token ID to another address. + * As opposed to transferFrom, this imposes no restrictions on msg.sender. + * @param from current owner of the token + * @param to address to receive the ownership of the given token ID + * @param tokenId uint256 ID of the token to be transferred + */ + function _transferFrom( + address from, + address to, + uint256 tokenId + ) internal { + super._transferFrom(from, to, tokenId); + + _removeTokenFromOwnerEnumeration(from, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + } + + /** + * @dev Internal function to mint a new token. + * Reverts if the given token ID already exists. + * @param to address the beneficiary that will own the minted token + * @param tokenId uint256 ID of the token to be minted + */ + function _mint(address to, uint256 tokenId) internal { + super._mint(to, tokenId); + + _addTokenToOwnerEnumeration(to, tokenId); + + _addTokenToAllTokensEnumeration(tokenId); + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned + */ + function _burn(address owner, uint256 tokenId) internal { + super._burn(owner, tokenId); + + _removeTokenFromOwnerEnumeration(owner, tokenId); + // Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund + _ownedTokensIndex[tokenId] = 0; + + _removeTokenFromAllTokensEnumeration(tokenId); + } + + /** + * @dev Gets the list of token IDs of the requested owner. + * @param owner address owning the tokens + * @return uint256[] List of token IDs owned by the requested address + */ + function _tokensOfOwner(address owner) + internal + view + returns (uint256[] storage) + { + return _ownedTokens[owner]; + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + _ownedTokensIndex[tokenId] = _ownedTokens[to].length; + _ownedTokens[to].push(tokenId); + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) + private + { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _ownedTokens[from].length.sub(1); + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + _ownedTokens[from].length--; + + // Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by + // lastTokenId, or just over the end of the array if the token was the last one). + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length.sub(1); + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + _allTokens.length--; + _allTokensIndex[tokenId] = 0; + } +} + +// File: contracts/token/KIP17/IKIP17Metadata.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP-17 Non-Fungible Token Standard, optional metadata extension + * @dev See http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract IKIP17Metadata is IKIP17 { + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function tokenURI(uint256 tokenId) external view returns (string memory); +} + +// File: contracts/token/KIP17/KIP17Metadata.sol + +pragma solidity ^0.5.0; + +contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata { + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Optional mapping for token URIs + mapping(uint256 => string) private _tokenURIs; + + /* + * bytes4(keccak256('name()')) == 0x06fdde03 + * bytes4(keccak256('symbol()')) == 0x95d89b41 + * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd + * + * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f + */ + bytes4 private constant _INTERFACE_ID_KIP17_METADATA = 0x5b5e139f; + + /** + * @dev Constructor function + */ + constructor(string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + + // register the supported interfaces to conform to KIP17 via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_METADATA); + } + + /** + * @dev Gets the token name. + * @return string representing the token name + */ + function name() external view returns (string memory) { + return _name; + } + + /** + * @dev Gets the token symbol. + * @return string representing the token symbol + */ + function symbol() external view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns an URI 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 tokenURI(uint256 tokenId) external view returns (string memory) { + require( + _exists(tokenId), + "KIP17Metadata: URI query for nonexistent token" + ); + return _tokenURIs[tokenId]; + } + + /** + * @dev Internal function to set the token URI for a given token. + * Reverts if the token ID does not exist. + * @param tokenId uint256 ID of the token to set its URI + * @param uri string URI to assign + */ + function _setTokenURI(uint256 tokenId, string memory uri) internal { + require( + _exists(tokenId), + "KIP17Metadata: URI set of nonexistent token" + ); + _tokenURIs[tokenId] = uri; + } + + /** + * @dev Internal function to burn a specific token. + * Reverts if the token does not exist. + * Deprecated, use _burn(uint256) instead. + * @param owner owner of the token to burn + * @param tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address owner, uint256 tokenId) internal { + super._burn(owner, tokenId); + + // Clear metadata (if any) + if (bytes(_tokenURIs[tokenId]).length != 0) { + delete _tokenURIs[tokenId]; + } + } +} + +// File: contracts/token/KIP17/KIP17Full.sol + +pragma solidity ^0.5.0; + +/** + * @title Full KIP-17 Token + * This implementation includes all the required and some optional functionality of the KIP-17 standard + * Moreover, it includes approve all functionality using operator terminology + * @dev see http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract KIP17Full is KIP17, KIP17Enumerable, KIP17Metadata { + constructor(string memory name, string memory symbol) + public + KIP17Metadata(name, symbol) + { + // solhint-disable-previous-line no-empty-blocks + } +} + +// File: contracts/access/Roles.sol + +pragma solidity ^0.5.0; + +/** + * @title Roles + * @dev Library for managing addresses assigned to a Role. + */ +library Roles { + struct Role { + mapping(address => bool) bearer; + } + + /** + * @dev Give an account access to this role. + */ + function add(Role storage role, address account) internal { + require(!has(role, account), "Roles: account already has role"); + role.bearer[account] = true; + } + + /** + * @dev Remove an account's access to this role. + */ + function remove(Role storage role, address account) internal { + require(has(role, account), "Roles: account does not have role"); + role.bearer[account] = false; + } + + /** + * @dev Check if an account has this role. + * @return bool + */ + function has(Role storage role, address account) + internal + view + returns (bool) + { + require(account != address(0), "Roles: account is the zero address"); + return role.bearer[account]; + } +} + +// File: contracts/access/roles/MinterRole.sol + +pragma solidity ^0.5.0; + +contract MinterRole { + using Roles for Roles.Role; + + event MinterAdded(address indexed account); + event MinterRemoved(address indexed account); + + Roles.Role private _minters; + + constructor() internal { + _addMinter(msg.sender); + } + + modifier onlyMinter() { + require( + isMinter(msg.sender), + "MinterRole: caller does not have the Minter role" + ); + _; + } + + function isMinter(address account) public view returns (bool) { + return _minters.has(account); + } + + function addMinter(address account) public onlyMinter { + _addMinter(account); + } + + function renounceMinter() public { + _removeMinter(msg.sender); + } + + function _addMinter(address account) internal { + _minters.add(account); + emit MinterAdded(account); + } + + function _removeMinter(address account) internal { + _minters.remove(account); + emit MinterRemoved(account); + } +} + +// File: contracts/token/KIP17/KIP17MetadataMintable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17MetadataMintable + * @dev KIP17 minting logic with metadata. + */ +contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole { + /* + * bytes4(keccak256('mintWithTokenURI(address,uint256,string)')) == 0x50bb4e7f + * bytes4(keccak256('isMinter(address)')) == 0xaa271e1a + * bytes4(keccak256('addMinter(address)')) == 0x983b2d56 + * bytes4(keccak256('renounceMinter()')) == 0x98650275 + * + * => 0x50bb4e7f ^ 0xaa271e1a ^ 0x983b2d56 ^ 0x98650275 == 0xfac27f46 + */ + bytes4 private constant _INTERFACE_ID_KIP17_METADATA_MINTABLE = 0xfac27f46; + + /** + * @dev Constructor function. + */ + constructor() public { + // register the supported interface to conform to KIP17Mintable via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_METADATA_MINTABLE); + } + + /** + * @dev Function to mint tokens. + * @param to The address that will receive the minted tokens. + * @param tokenId The token id to mint. + * @param tokenURI The token URI of the minted token. + * @return A boolean that indicates if the operation was successful. + */ + function mintWithTokenURI( + address to, + uint256 tokenId, + string memory tokenURI + ) public onlyMinter returns (bool) { + _mint(to, tokenId); + _setTokenURI(tokenId, tokenURI); + return true; + } +} + +// File: contracts/token/KIP17/KIP17Mintable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17Mintable + * @dev KIP17 minting logic. + */ +contract KIP17Mintable is KIP17, MinterRole { + /* + * bytes4(keccak256('isMinter(address)')) == 0xaa271e1a + * bytes4(keccak256('addMinter(address)')) == 0x983b2d56 + * bytes4(keccak256('renounceMinter()')) == 0x98650275 + * bytes4(keccak256('mint(address,uint256)')) == 0x40c10f19 + * + * => 0xaa271e1a ^ 0x983b2d56 ^ 0x98650275 ^ 0x40c10f19 == 0xeab83e20 + */ + bytes4 private constant _INTERFACE_ID_KIP17_MINTABLE = 0xeab83e20; + + /** + * @dev Constructor function. + */ + constructor() public { + // register the supported interface to conform to KIP17Mintable via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_MINTABLE); + } + + /** + * @dev Function to mint tokens. + * @param to The address that will receive the minted tokens. + * @param tokenId The token id to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address to, uint256 tokenId) + public + onlyMinter + returns (bool) + { + _mint(to, tokenId); + return true; + } +} + +// File: contracts/token/KIP17/KIP17Burnable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17 Burnable Token + * @dev KIP17 Token that can be irreversibly burned (destroyed). + * See http://kips.klaytn.com/KIPs/kip-17-non_fungible_token + */ +contract KIP17Burnable is KIP13, KIP17 { + /* + * bytes4(keccak256('burn(uint256)')) == 0x42966c68 + * + * => 0x42966c68 == 0x42966c68 + */ + bytes4 private constant _INTERFACE_ID_KIP17_BURNABLE = 0x42966c68; + + /** + * @dev Constructor function. + */ + constructor() public { + // register the supported interface to conform to KIP17Burnable via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_BURNABLE); + } + + /** + * @dev Burns a specific KIP17 token. + * @param tokenId uint256 id of the KIP17 token to be burned. + */ + function burn(uint256 tokenId) public { + //solhint-disable-next-line max-line-length + require( + _isApprovedOrOwner(msg.sender, tokenId), + "KIP17Burnable: caller is not owner nor approved" + ); + _burn(tokenId); + } +} + +// File: contracts/access/roles/PauserRole.sol + +pragma solidity ^0.5.0; + +contract PauserRole { + using Roles for Roles.Role; + + event PauserAdded(address indexed account); + event PauserRemoved(address indexed account); + + Roles.Role private _pausers; + + constructor() internal { + _addPauser(msg.sender); + } + + modifier onlyPauser() { + require( + isPauser(msg.sender), + "PauserRole: caller does not have the Pauser role" + ); + _; + } + + function isPauser(address account) public view returns (bool) { + return _pausers.has(account); + } + + function addPauser(address account) public onlyPauser { + _addPauser(account); + } + + function renouncePauser() public { + _removePauser(msg.sender); + } + + function _addPauser(address account) internal { + _pausers.add(account); + emit PauserAdded(account); + } + + function _removePauser(address account) internal { + _pausers.remove(account); + emit PauserRemoved(account); + } +} + +// File: contracts/lifecycle/Pausable.sol + +pragma solidity ^0.5.0; + +/** + * @dev Contract module which allows children to implement an emergency stop + * mechanism that can be triggered by an authorized account. + * + * This module is used through inheritance. It will make available the + * modifiers `whenNotPaused` and `whenPaused`, which can be applied to + * the functions of your contract. Note that they will not be pausable by + * simply including this module, only once the modifiers are put in place. + */ +contract Pausable is PauserRole { + /** + * @dev Emitted when the pause is triggered by a pauser (`account`). + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by a pauser (`account`). + */ + event Unpaused(address account); + + bool private _paused; + + /** + * @dev Initializes the contract in unpaused state. Assigns the Pauser role + * to the deployer. + */ + constructor() internal { + _paused = false; + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() public view returns (bool) { + return _paused; + } + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + /** + * @dev Called by a pauser to pause, triggers stopped state. + */ + function pause() public onlyPauser whenNotPaused { + _paused = true; + emit Paused(msg.sender); + } + + /** + * @dev Called by a pauser to unpause, returns to normal state. + */ + function unpause() public onlyPauser whenPaused { + _paused = false; + emit Unpaused(msg.sender); + } +} + +// File: contracts/token/KIP17/KIP17Pausable.sol + +pragma solidity ^0.5.0; + +/** + * @title KIP17 Non-Fungible Pausable token + * @dev KIP17 modified with pausable transfers. + */ +contract KIP17Pausable is KIP13, KIP17, Pausable { + /* + * bytes4(keccak256('paused()')) == 0x5c975abb + * bytes4(keccak256('pause()')) == 0x8456cb59 + * bytes4(keccak256('unpause()')) == 0x3f4ba83a + * bytes4(keccak256('isPauser(address)')) == 0x46fbf68e + * bytes4(keccak256('addPauser(address)')) == 0x82dc1ec4 + * bytes4(keccak256('renouncePauser()')) == 0x6ef8d66d + * + * => 0x5c975abb ^ 0x8456cb59 ^ 0x3f4ba83a ^ 0x46fbf68e ^ 0x82dc1ec4 ^ 0x6ef8d66d == 0x4d5507ff + */ + bytes4 private constant _INTERFACE_ID_KIP17_PAUSABLE = 0x4d5507ff; + + /** + * @dev Constructor function. + */ + constructor() public { + // register the supported interface to conform to KIP17Pausable via KIP13 + _registerInterface(_INTERFACE_ID_KIP17_PAUSABLE); + } + + function approve(address to, uint256 tokenId) public whenNotPaused { + super.approve(to, tokenId); + } + + function setApprovalForAll(address to, bool approved) public whenNotPaused { + super.setApprovalForAll(to, approved); + } + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public whenNotPaused { + super.transferFrom(from, to, tokenId); + } +} + +// File: contracts/token/KIP17/KIP17Token.sol + +pragma solidity ^0.5.0; + +contract KIP17Token is + KIP17Full, + KIP17Mintable, + KIP17MetadataMintable, + KIP17Burnable, + KIP17Pausable +{ + constructor(string memory name, string memory symbol) + public + KIP17Full(name, symbol) + {} +} \ No newline at end of file From 4762cd2952573a14c740aaea19ab8205bb48be69 Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Sun, 13 Feb 2022 20:07:05 +0900 Subject: [PATCH 3/7] =?UTF-8?q?:sparkles:=20KIP17=20=EA=B8=B0=EB=B0=98=20N?= =?UTF-8?q?FT=20contract=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit burn 수정, 일반& 마스터NFT 발행 추가 --- KIP17NFTContract.sol | 71 +++++++++++++++++++++--- README.md | 126 +++++++++++++++++++++++++++++++++++++++++- voteContract.sol | 128 ------------------------------------------- 3 files changed, 189 insertions(+), 136 deletions(-) delete mode 100644 voteContract.sol diff --git a/KIP17NFTContract.sol b/KIP17NFTContract.sol index 7456458..fd3b722 100644 --- a/KIP17NFTContract.sol +++ b/KIP17NFTContract.sol @@ -1320,7 +1320,6 @@ contract MinterRole { // File: contracts/token/KIP17/KIP17MetadataMintable.sol pragma solidity ^0.5.0; - /** * @title KIP17MetadataMintable * @dev KIP17 minting logic with metadata. @@ -1335,6 +1334,9 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole { * => 0x50bb4e7f ^ 0xaa271e1a ^ 0x983b2d56 ^ 0x98650275 == 0xfac27f46 */ bytes4 private constant _INTERFACE_ID_KIP17_METADATA_MINTABLE = 0xfac27f46; + + //yun_add 유저가 소유한 id + uint256[] userid; /** * @dev Constructor function. @@ -1360,6 +1362,59 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole { _setTokenURI(tokenId, tokenURI); return true; } + //mint 무료 3회 + //onlyMinter 삭제 + function mintNft( + address to, + uint256 tokenId, + string memory nftMetadata + ) public returns (bool) { + _mint(to, tokenId); + _setTokenURI(tokenId, nftMetadata); + return true; + } + + //mint 유료, 0.5 klay + function mintWithKlay( + address to, + uint256 tokenId, + string memory nftMetadata, + address payable receiver // klay받는 주소 + ) public payable returns (bool) { + + receiver.transfer(10**17*5); + mintNft(to,tokenId, nftMetadata); + return true; + } + //마스터 뱃지 mint (수정사항 : 마스터 뱃지와 일반 뱃지 구분 필요) + function mintMasterBadge( + address to, + uint256 tokenId, + string memory nftMetaData, + address NFT + ) public returns (bool){ + uint256 userBalance; + userBalance = balanceOf(to); + //require(userBalance == 20, "You must have 20NFTs") + _removeOwnToken(userBalance, to, NFT); + //_burn(tokenId); + mintNft(to,tokenId, nftMetaData); + return true; + } + + // 소유한 전체 NFT 삭제 + function _removeOwnToken(uint256 balance, address to,address NFT) private{ + //유저가 가지고 있는 토큰id 리트스 확인 + for (uint256 i = 0; i < balance; i++) { + uint256 id = KIP17Enumerable(NFT).tokenOfOwnerByIndex(to,i); + userid.push(id); + } + //유저가 가지고 있는 기존 NFT 삭제 + for (uint256 i = 0; i < balance; i++) { + _burn(userid[i]); + } + } + } // File: contracts/token/KIP17/KIP17Mintable.sol @@ -1414,7 +1469,8 @@ pragma solidity ^0.5.0; * @dev KIP17 Token that can be irreversibly burned (destroyed). * See http://kips.klaytn.com/KIPs/kip-17-non_fungible_token */ -contract KIP17Burnable is KIP13, KIP17 { + //yun add 관리자 추가 +contract KIP17Burnable is KIP13, KIP17, MinterRole { /* * bytes4(keccak256('burn(uint256)')) == 0x42966c68 * @@ -1434,12 +1490,13 @@ contract KIP17Burnable is KIP13, KIP17 { * @dev Burns a specific KIP17 token. * @param tokenId uint256 id of the KIP17 token to be burned. */ - function burn(uint256 tokenId) public { + //yun add burn 권한 소유자가 아닌 관리자로 변경 + function burn(uint256 tokenId) public onlyMinter{ //solhint-disable-next-line max-line-length - require( - _isApprovedOrOwner(msg.sender, tokenId), - "KIP17Burnable: caller is not owner nor approved" - ); + // require( + // _isApprovedOrOwner(msg.sender, tokenId), + // "KIP17Burnable: caller is not owner nor approved" + // ); _burn(tokenId); } } diff --git a/README.md b/README.md index 9c0b97f..a3fc92b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,127 @@ # BadgeMeal Smart Contract -각자 이름으로 branch 생성 후 작업 \ No newline at end of file + +## KIP17NFTContract + + + +### 기존 KIP17Token.sol 파일에 추가 및 변경 + +1. KIP17Burnable contract 권한 수정 +- 기존 소유자가 지울수 있는 권한에서 발행자만 지울 수 있게 변경 +```Solidity +function burn(uint256 tokenId) public onlyMinter{ + +//solhint-disable-next-line max-line-length + +// require( + +// _isApprovedOrOwner(msg.sender, tokenId), + +// "KIP17Burnable: caller is not owner nor approved" + +// ); + + _burn(tokenId); + +} +``` + +2. 함수 추가 +#### mintNft : NFT 발행 + + ```Solidity +function mintNft( + +address to, + +uint256 tokenId, + +string memory nftMetadata + +) public returns (bool) { + + _mint(to, tokenId); + + _setTokenURI(tokenId, nftMetadata); + + return true; + +} +``` + +*** + #### mintWithKlay : NFT 발행(수수료0.5Klay) +```Solidity +function mintNft( + +address to, + +uint256 tokenId, + +string memory nftMetadata + +) public returns (bool) { + + _mint(to, tokenId); + + _setTokenURI(tokenId, nftMetadata); + + return true; + +} +``` + +*** +#### mintMasterBadge : 마스터 뱃지 발행 +```Solidity +function mintMasterBadge( + +address to, + +uint256 tokenId, + +string memory nftMetaData, + +address NFT + +) public returns (bool){ + + uint256 userBalance; + + userBalance = balanceOf(to); + + //require(userBalance == 20, "You must have 20NFTs") + + _removeOwnToken(userBalance, to, NFT); + + //_burn(tokenId); + + mintNft(to,tokenId, nftMetaData); + + return true; + +} +// 소유한 전체 NFT 삭제 + +function _removeOwnToken(uint256 balance, address to,address NFT) private{ + + //유저가 가지고 있는 토큰id 리트스 확인 + for (uint256 i = 0; i < balance; i++) { + + uint256 id = KIP17Enumerable(NFT).tokenOfOwnerByIndex(to,i); + + userid.push(id); + + } + //유저가 가지고 있는 기존 NFT 삭제 + for (uint256 i = 0; i < balance; i++) { + + _burn(userid[i]); + + } + +} +``` +*** +- 마스터 뱃지 발행 시 일반 nft와 마스터 nft 구분 필요 \ No newline at end of file diff --git a/voteContract.sol b/voteContract.sol deleted file mode 100644 index d613c51..0000000 --- a/voteContract.sol +++ /dev/null @@ -1,128 +0,0 @@ -pragma solidity ^0.5.0; - -contract Vote { - - struct Proposal { - string name; // 메뉴 이름 - uint voteCount; // 투표 받은 수 - string imageUrl; // 메뉴 이미지 url - address proposer; // 메뉴 제안자 - - } - - struct Voter { - bool voted; // 투표 진행 여부 (true,false) - uint vote; // Menu 리스트 요소의 index (0,1,2 ...) - uint weight; // 투표 권한 - - } - - address public deployer; // 컨트랙트 배포자 - - mapping(address => Voter) public voters; // 투표자 매핑 - - address[] public voterList; // 투표자 리스트 - - Proposal[] public proposals; // 메뉴 리스트 - - Proposal[] public winnerProposals; // 투표로 채택된 메뉴 리스트 - - constructor() public { - deployer = msg.sender; - } - - - // 메뉴 추가 함수 - function proposeMenu(string memory name, string memory imageUrl) public { - /** 🔥 pseudocode 추가 - [require] msg.sender == 뱃지밀 마스터 NFT 소유자, "메뉴 추가 제안 권한이 없습니다." - */ - - proposals.push(Proposal({ - name: name, - voteCount: 0, - imageUrl: imageUrl, - proposer: msg.sender - })); - } - - // 투표자 리스트 추가 함수 - function addVoters(address[] memory addressList) public { - require( - msg.sender == deployer, - "Only deployer can give right to vote." - ); - require( - voterList.length == 0, - "Already added Voters." - ); - - /** 🔥 수정 필요 - addressList를 입력값으로 받는 것이 아니라 NFT 홀더 리스트를 가져오는 함수를 호출한 후 아래 로직을 실행하는게 좋을 것 같다. - */ - - for (uint i = 0; i < addressList.length; i++) { - voterList.push(addressList[i]); - } - } - - // 투표 권한 부여 함수 - function giveRightToVote(address voter) private { - require( - msg.sender == deployer, - "Only deployer can give right to vote." - ); - require( - !voters[voter].voted, - "The voter already voted." - ); - require(voters[voter].weight == 0); - voters[voter].weight = 1; - } - - // 투표자 리스트 모두에게 권한 부여 - function giveVotersRightToVote() public { - for (uint i = 0; i < voterList.length; i++) { - giveRightToVote(voterList[i]); - } - } - - - // 투표 함수 - function vote(uint proposal) public { - Voter storage sender = voters[msg.sender]; - - /** 🔥 pseudocode 추가 - [require] msg.sender == 뱃지밀 일반 NFT 소유자, "투표 권한이 없습니다." - */ - require(sender.weight != 0, "Has no right to vote"); - require(!sender.voted, "Already voted."); - - sender.voted = true; - sender.vote = proposal; - - proposals[proposal].voteCount += sender.weight; - } - - // 가장 많은 득표수를 얻은 메뉴 index 출력하는 함수 - function winningProposal() public view returns (uint winningProposal_) { - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal_ = p; - } - } - } - - // 가장 많은 득표수를 얻은 메뉴 이름을 리턴하는 함수 - function winnerName() public view returns (string memory winnerName_) { - winnerName_ = proposals[winningProposal()].name; - } - - /** 🔥 pseudocode 추가 - function 투표 마감 - 1. 투표 시간이 마감되면 가장 많은 득표수를 얻은 메뉴 Proposal을 winnerProposals 에 push 한다. - 2. Proposals를 초기화한다. - */ -} \ No newline at end of file From 8a752c316b36884b2c33dd0ca80d403ca12454f4 Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Tue, 15 Feb 2022 14:55:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?:bug:=20=EC=9C=A0=EB=A3=8C=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=9C=20=EC=BB=A8=ED=8A=B8=EB=9E=99=ED=8A=B8=20=EC=84=A4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a3fc92b..b1f3655 100644 --- a/README.md +++ b/README.md @@ -52,25 +52,20 @@ string memory nftMetadata *** #### mintWithKlay : NFT 발행(수수료0.5Klay) -```Solidity -function mintNft( - -address to, - -uint256 tokenId, - -string memory nftMetadata - -) public returns (bool) { - - _mint(to, tokenId); - - _setTokenURI(tokenId, nftMetadata); - - return true; - -} -``` +```sol +//mint 유료, 0.5 klay + function mintWithKlay( + address to, + uint256 tokenId, + string memory nftMetadata, + address payable receiver // klay받는 주소 + ) public payable returns (bool) { + + receiver.transfer(10**17*5); + mintNft(to,tokenId, nftMetadata); + return true; + } +``` *** #### mintMasterBadge : 마스터 뱃지 발행 From 0beedda722bb5f30f552cc2c1d100a61bdc775e5 Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Wed, 16 Feb 2022 17:37:18 +0900 Subject: [PATCH 5/7] =?UTF-8?q?:zap:=20=EB=A6=AC=EB=B7=B0=ED=9B=84=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=9E=99=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KIP17NFTContract.sol | 258 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 216 insertions(+), 42 deletions(-) diff --git a/KIP17NFTContract.sol b/KIP17NFTContract.sol index fd3b722..5d2a3b4 100644 --- a/KIP17NFTContract.sol +++ b/KIP17NFTContract.sol @@ -1127,7 +1127,8 @@ 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; /* * bytes4(keccak256('name()')) == 0x06fdde03 * bytes4(keccak256('symbol()')) == 0x95d89b41 @@ -1177,6 +1178,21 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata { return _tokenURIs[tokenId]; } + /** + * @dev 🔥 Returns an level 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) { + require(_exists(tokenId), "KIP17Metadata: URI query for nonexistent token"); + return _tokenLevel[tokenId]; + } + + //소유한 토큰 종류 확인(맞으면 true, 아니면 false) + function _ownTokenLevel(uint256 tokenId, uint level) internal returns (bool){ + return _tokenLevel[tokenId]==level; + } + /** * @dev Internal function to set the token URI for a given token. * Reverts if the token ID does not exist. @@ -1191,6 +1207,16 @@ 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 { + require(_exists(tokenId), "KIP17Metadata: URI set of nonexistent token"); + _tokenLevel[tokenId] = level; + } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. @@ -1198,13 +1224,26 @@ contract KIP17Metadata is KIP13, KIP17, IKIP17Metadata { * @param owner owner of the token to burn * @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]) + { + _burn(owner, tokenId); + } + } + 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 (_tokenLevel[tokenId] > 0) { + delete _tokenLevel[tokenId]; + } + } } @@ -1336,7 +1375,7 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole { bytes4 private constant _INTERFACE_ID_KIP17_METADATA_MINTABLE = 0xfac27f46; //yun_add 유저가 소유한 id - uint256[] userid; + uint256[] _userid; /** * @dev Constructor function. @@ -1353,69 +1392,199 @@ 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( address to, uint256 tokenId, - string memory tokenURI + string memory tokenURI, + uint level ) public onlyMinter returns (bool) { _mint(to, tokenId); _setTokenURI(tokenId, tokenURI); + _setTokenLevel(tokenId, level); return true; } - //mint 무료 3회 - //onlyMinter 삭제 - function mintNft( - address to, - uint256 tokenId, - string memory nftMetadata - ) public returns (bool) { - _mint(to, tokenId); - _setTokenURI(tokenId, nftMetadata); - return true; - } - + //mint 유료, 0.5 klay function mintWithKlay( address to, uint256 tokenId, - string memory nftMetadata, - address payable receiver // klay받는 주소 + string memory tokenURI, + uint level, + address payable reciver ) public payable returns (bool) { - - receiver.transfer(10**17*5); - mintNft(to,tokenId, nftMetadata); + // reciver = Ownable(NFT).owner(); + reciver.transfer(10**17*5); + mintWithTokenURI(to,tokenId, tokenURI,level); return true; } - //마스터 뱃지 mint (수정사항 : 마스터 뱃지와 일반 뱃지 구분 필요) + //마스터 뱃지 mint (수정사항 : 마스터 뱃지레벨, 일반 뱃지(메뉴별 레벨)로 구분) function mintMasterBadge( address to, uint256 tokenId, - string memory nftMetaData, + string memory tokenURI, + uint setLevel, + uint delLevel, address NFT ) public returns (bool){ uint256 userBalance; userBalance = balanceOf(to); - //require(userBalance == 20, "You must have 20NFTs") - _removeOwnToken(userBalance, to, NFT); - //_burn(tokenId); - mintNft(to,tokenId, nftMetaData); + + _listOfUserToeknId(userBalance, to, NFT); + + //특정 NFT(국밥 마스터 뱃지 인지)가 20개 이상 소유한지 판별 + require(_checkMenu(delLevel, userBalance) >= 20, "You must have menu20NFTs"); + _removeOwnToken(to, delLevel); + mintWithTokenURI(to,tokenId, tokenURI,setLevel); return true; } - // 소유한 전체 NFT 삭제 - function _removeOwnToken(uint256 balance, address to,address NFT) private{ - //유저가 가지고 있는 토큰id 리트스 확인 + //소유한 특정메뉴 갯수 확인 + function _checkMenu(uint level, uint256 balance) private returns(uint256){ + uint256 result = 0; + for (uint256 i =0 ; i< balance; i++){ + if(_ownTokenLevel(_userid[i], level)){ + result++; + } + } + return result; + } + // 유저가 현재 소유한 전체 tokenId 리스트 생성 + function _listOfUserToeknId(uint256 balance, address to, address NFT) private + { + _useridInit(); for (uint256 i = 0; i < balance; i++) { uint256 id = KIP17Enumerable(NFT).tokenOfOwnerByIndex(to,i); - userid.push(id); + _userid.push(id); } - //유저가 가지고 있는 기존 NFT 삭제 - for (uint256 i = 0; i < balance; i++) { - _burn(userid[i]); + } + // 소유한 20개의 메뉴 NFT 삭제 + function _removeOwnToken(address to, uint level) private{ + + //유저가 가지고 있는 기존 NFT 삭제(20개 이상을 소유할 수도 있으니 20개만 삭제) + for (uint256 i = 0; i < 20; i++) { + _burnForMasterNFT(to, _userid[i],level); + } + + _useridInit(); + } + //_userid 초기화 + function _useridInit() private{ + for (uint256 i=0; i< _userid.length;i++){ + _userid.pop; } + } } +// File: @openzeppelin/contracts/GSN/Context.sol + +pragma solidity ^0.5.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +contract Context { + // Empty internal constructor, to prevent people from mistakenly deploying + // an instance of this contract, which should be used via inheritance. + constructor () internal { } + // solhint-disable-previous-line no-empty-blocks + + function _msgSender() internal view returns (address payable) { + return msg.sender; + } + + function _msgData() internal view returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + +// File: @openzeppelin/contracts/ownership/Ownable.sol + +pragma solidity ^0.5.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Context { + address private _owner; + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(isOwner(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Returns true if the caller is the current owner. + */ + function isOwner() public view returns (bool) { + return _msgSender() == _owner; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + */ + function _transferOwnership(address newOwner) internal { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} // File: contracts/token/KIP17/KIP17Mintable.sol @@ -1490,13 +1659,12 @@ contract KIP17Burnable is KIP13, KIP17, MinterRole { * @dev Burns a specific KIP17 token. * @param tokenId uint256 id of the KIP17 token to be burned. */ - //yun add burn 권한 소유자가 아닌 관리자로 변경 - function burn(uint256 tokenId) public onlyMinter{ + function burn(uint256 tokenId) public{ //solhint-disable-next-line max-line-length - // require( - // _isApprovedOrOwner(msg.sender, tokenId), - // "KIP17Burnable: caller is not owner nor approved" - // ); + require( + _isApprovedOrOwner(msg.sender, tokenId), + "KIP17Burnable: caller is not owner nor approved" + ); _burn(tokenId); } } @@ -1677,10 +1845,16 @@ contract KIP17Token is KIP17Mintable, KIP17MetadataMintable, KIP17Burnable, - KIP17Pausable + KIP17Pausable, + Ownable { + event Klaytn17Burn(address _to, uint256 tokenId); + constructor(string memory name, string memory symbol) - public - KIP17Full(name, symbol) + KIP17Mintable() + KIP17MetadataMintable() + KIP17Burnable() + KIP17Pausable() + KIP17Full(name, symbol) public {} } \ No newline at end of file From d3f7208743c052bf6ceafb0071e20541ac5918fd Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Wed, 16 Feb 2022 18:09:02 +0900 Subject: [PATCH 6/7] =?UTF-8?q?:recycle:=20=EB=A6=AC=EB=B7=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 180 +++++++++++++++++++++++++++--------------------------- 1 file changed, 89 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index b1f3655..d33ffcf 100644 --- a/README.md +++ b/README.md @@ -8,115 +8,113 @@ ### 기존 KIP17Token.sol 파일에 추가 및 변경 1. KIP17Burnable contract 권한 수정 -- 기존 소유자가 지울수 있는 권한에서 발행자만 지울 수 있게 변경 -```Solidity -function burn(uint256 tokenId) public onlyMinter{ - -//solhint-disable-next-line max-line-length - -// require( - -// _isApprovedOrOwner(msg.sender, tokenId), - -// "KIP17Burnable: caller is not owner nor approved" - -// ); - - _burn(tokenId); - -} -``` +- ~~기존 소유자가 지울수 있는 권한에서 발행자만 지울 수 있게 변경~~ +- 기존 KIP17Token으로 수정 2. 함수 추가 -#### mintNft : NFT 발행 - - ```Solidity -function mintNft( - -address to, - -uint256 tokenId, - -string memory nftMetadata - -) public returns (bool) { - - _mint(to, tokenId); - - _setTokenURI(tokenId, nftMetadata); - - return true; - -} +#### 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, "링크") +#### mintWithTokenURI : NFT 발행 +- [mint함수명 원복](https://github.com/BadgeMeal/badgemeal-contract/pull/3#discussion_r806472363, "review01") +- level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨) +```sol + function mintWithTokenURI( + address to, + uint256 tokenId, + string memory tokenURI, + uint level + ) public onlyMinter returns (bool) { + _mint(to, tokenId); + _setTokenURI(tokenId, tokenURI); + _setTokenLevel(tokenId, level); + return true; + } ``` *** #### mintWithKlay : NFT 발행(수수료0.5Klay) +- level 추가(마스터, 구체적인 메뉴 구분을 위한 레벨) +- owner address를 불러와 시도를 해볼려고 했지만 payable가 제대로 안되어서 우선은 기존처럼 해놓았습니다. ```sol //mint 유료, 0.5 klay - function mintWithKlay( +function mintWithKlay( address to, uint256 tokenId, - string memory nftMetadata, - address payable receiver // klay받는 주소 + string memory tokenURI, + uint level, + address payable reciver ) public payable returns (bool) { - - receiver.transfer(10**17*5); - mintNft(to,tokenId, nftMetadata); + // reciver = Ownable(NFT).owner(); + reciver.transfer(10**17*5); + mintWithTokenURI(to,tokenId, tokenURI,level); return true; } ``` *** #### mintMasterBadge : 마스터 뱃지 발행 -```Solidity -function mintMasterBadge( - -address to, - -uint256 tokenId, - -string memory nftMetaData, - -address NFT - -) public returns (bool){ - - uint256 userBalance; - - userBalance = balanceOf(to); - - //require(userBalance == 20, "You must have 20NFTs") - - _removeOwnToken(userBalance, to, NFT); - - //_burn(tokenId); - - mintNft(to,tokenId, nftMetaData); - - return true; - -} -// 소유한 전체 NFT 삭제 - -function _removeOwnToken(uint256 balance, address to,address NFT) private{ - - //유저가 가지고 있는 토큰id 리트스 확인 - for (uint256 i = 0; i < balance; i++) { - - uint256 id = KIP17Enumerable(NFT).tokenOfOwnerByIndex(to,i); - - userid.push(id); - - } - //유저가 가지고 있는 기존 NFT 삭제 - for (uint256 i = 0; i < balance; i++) { - - _burn(userid[i]); +- [특정메뉴 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기준으로 메뉴 갯수 확인 및 삭제 기능 보완 +```sol +//마스터 뱃지 mint (수정사항 : 마스터 뱃지레벨, 일반 뱃지(메뉴별 레벨)로 구분) + function mintMasterBadge( + address to, + uint256 tokenId, + string memory tokenURI, + uint setLevel, + uint delLevel, + address NFT + ) public returns (bool){ + uint256 userBalance; + userBalance = balanceOf(to); + + _listOfUserToeknId(userBalance, to, NFT); + + //특정 NFT(국밥 마스터 뱃지 인지)가 20개 이상 소유한지 판별 + require(_checkMenu(delLevel, userBalance) >= 20, "You must have menu20NFTs"); + _removeOwnToken(to, delLevel); + mintWithTokenURI(to,tokenId, tokenURI,setLevel); + return true; + } - } + //소유한 특정메뉴 갯수 확인 + function _checkMenu(uint level, uint256 balance) private returns(uint256){ + uint256 result = 0; + for (uint256 i =0 ; i< balance; i++){ + if(_ownTokenLevel(_userid[i], level)){ + result++; + } + } + return result; + } + // 유저가 현재 소유한 전체 tokenId 리스트 생성 + function _listOfUserToeknId(uint256 balance, address to, address NFT) private + { + _useridInit(); + for (uint256 i = 0; i < balance; i++) { + uint256 id = KIP17Enumerable(NFT).tokenOfOwnerByIndex(to,i); + _userid.push(id); + } + } + // 소유한 20개의 메뉴 NFT 삭제 + function _removeOwnToken(address to, uint level) private{ + + //유저가 가지고 있는 기존 NFT 삭제(20개 이상을 소유할 수도 있으니 20개만 삭제) + for (uint256 i = 0; i < 20; i++) { + _burnForMasterNFT(to, _userid[i],level); + } + + _useridInit(); + } + //_userid 초기화 + function _useridInit() private{ + for (uint256 i=0; i< _userid.length;i++){ + _userid.pop; + } + + } -} ``` *** -- 마스터 뱃지 발행 시 일반 nft와 마스터 nft 구분 필요 \ No newline at end of file +- 현재 마스터 NFT와 일반 NFT는 level로 구분이 되는데 일반 NFT에서 메뉴끼리는 구별이 안되어 있습니다. 구별을 데이터에서 가져올지 안가져올지 확실하지 않아서 우선은 컨트랙트자체에서 해결할 수 있게 level에서 구분할수 있게 짜 놓았습니다. \ No newline at end of file From 4ee326ca4ed0cf5e7ec2c026cc5a38541e2b57c0 Mon Sep 17 00:00:00 2001 From: Sungpyo Date: Thu, 17 Feb 2022 13:54:09 +0900 Subject: [PATCH 7/7] =?UTF-8?q?:zap:=20=EB=A9=94=EB=89=B4=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KIP17NFTContract.sol | 104 ++++++++++++++++++++++++++++--------------- README.md | 90 ++++++++++++++++++++++++++++--------- 2 files changed, 136 insertions(+), 58 deletions(-) diff --git a/KIP17NFTContract.sol b/KIP17NFTContract.sol index 5d2a3b4..84ddaf6 100644 --- a/KIP17NFTContract.sol +++ b/KIP17NFTContract.sol @@ -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 @@ -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)); } /** @@ -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. @@ -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; } } @@ -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]; + } } @@ -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( 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; } @@ -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(); 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; @@ -1433,17 +1457,17 @@ contract KIP17MetadataMintable is KIP13, KIP17, KIP17Metadata, MinterRole { _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++; } } @@ -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; //유저가 가지고 있는 기존 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(); diff --git a/README.md b/README.md index d33ffcf..c3f0067 100644 --- a/README.md +++ b/README.md @@ -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; } ``` @@ -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; @@ -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++; } } @@ -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(); @@ -116,5 +163,4 @@ function mintWithKlay( } ``` -*** -- 현재 마스터 NFT와 일반 NFT는 level로 구분이 되는데 일반 NFT에서 메뉴끼리는 구별이 안되어 있습니다. 구별을 데이터에서 가져올지 안가져올지 확실하지 않아서 우선은 컨트랙트자체에서 해결할 수 있게 level에서 구분할수 있게 짜 놓았습니다. \ No newline at end of file +*** \ No newline at end of file