Skip to content

Instantly share code, notes, and snippets.

@amazingandyyy
Created April 27, 2021 05:14
Show Gist options
  • Save amazingandyyy/565d71987d2e83d17e094c9611c50bbf to your computer and use it in GitHub Desktop.
Save amazingandyyy/565d71987d2e83d17e094c9611c50bbf to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: NO LICENSE
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/EnumerableMap.sol";
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "./utils/ReentrancyGuard.sol";
import "./utils/Ownable.sol";
import "./ERC165/IERC165.sol";
import "./ERC165/ERC165.sol";
import "./ERC20/IERC20.sol";
import "./ERC721/IERC721Enumerable.sol";
import "./IBearNBearToken.sol";
import "./IMiniBearToken.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
}
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
*/
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
/**
* @title BearNBear contract
* @dev Extends ERC721 Non-Fungible Token Standard basic implementation
*/
contract BearNBearToken is Context, Ownable, ERC165, IBearNBearToken, IERC721Metadata, ReentrancyGuard {
using SafeMath for uint256;
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableMap for EnumerableMap.UintToAddressMap;
using Strings for uint256;
address public creator1 = 0xC765f38888CF7395aD13B69A016b97A8C3E747BB;
address public creator2 = 0x3Eb981F779D00D0D1051aAB28eED2F3e16b4b2EB;
uint public creator1Shares;
uint public creator2Shares;
uint public creator1SharesContinue;
uint public creator2SharesContinue;
bool public creator1SharesLocked;
bool public creator2SharesLocked;
uint constant PRECISION = 10 ** 18;
uint public BASE_PRICE = 1 * PRECISION;
uint private ACCUMULATED_TOTAL_SUPPLY = 0;
// BASIC INFORMATION
// This is the provenance record of all artworks in existence
string public constant IPFS_PROVENANCE = "ae0eaabd9134aa09782b4dfd43a7280adeea9987f1e4cdf4817f610716ec5685";
uint256 public MAX_NFT_SUPPLY = 17153;
// IMPORTANT TIMESTMAPS:
uint256 public SALE_START_TIMESTAMP; // only set by constructor
uint256 public MBT_BONUS_END_TIMESTAMP; // Timestamp when bonus mBT ends
uint256 public HARD_FINALIZE_TIMESTAMP; // The latest timestamp when the arts will be revealed
uint256 public ACTUAL_REVEAL_TIMESTAMP; // overwritten by finalize()
// IMPORTANT PRICES:
uint256 public constant NAME_CHANGE_PRICE = 2000 * PRECISION; // price to change name
uint256 public constant DESCRIPTION_CHANGE_PRICE = 2000 * PRECISION; // price to change description
uint256 public constant BURN_PRICE = (NAME_CHANGE_PRICE + DESCRIPTION_CHANGE_PRICE) * 6; // price to burn BBT
// IMPORTANT VARIABLES
uint256 public startingIndex; // set by mint when sale ends at
uint256 public burnRewards; // set by finalize
uint256 public rewardPoolSize; // set by finalize
uint256 public teamPoolSize; // set by finalize
uint256[] public burnRewardPoolRecords;
// 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;
// IMPORTANT DATA STRUCTURES
mapping (address => EnumerableSet.UintSet) private _holderNFT; // Mapping from holder address to their (enumerable) set of owned NFT
EnumerableMap.UintToAddressMap private _tokenOwners; // Enumerable mapping from token ids to their owners
// BBT token informations
mapping (uint256 => address) private _tokenApprovals; // Mapping from token ID to approved address
mapping (uint256 => string) private _tokenName; // Mapping from token ID to name
mapping (uint256 => string) private _tokenDescription; // Mapping from token ID to description
mapping (uint256 => uint256) private _tokenMBTGeneratingMultiplier; // Mapping from token ID to MBT generating multiplier
// utility informations
mapping (string => bool) private _nameReserved; // Mapping if certain name string has already been reserved
mapping (uint256 => bool) private _mintedWithBonusMBT; // Mapping from token ID to whether the Hashmask was minted before reveal
mapping (address => mapping (address => bool)) private _operatorApprovals; // Mapping from owner to operator approvals
// contract basic information
string private _name; // BearNBearToken
string private _symbol; // BBT
address private _mbtAddress; // miniBearToken token address
/*
* 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)')) == 0xe985e9c5
* 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 ^ 0xe985e9c5 ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
*/
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
/*
* bytes4(keccak256('name()')) == 0x06fdde03
* bytes4(keccak256('symbol()')) == 0x95d89b41
*
* => 0x06fdde03 ^ 0x95d89b41 == 0x93254542
*/
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x93254542;
/*
* bytes4(keccak256('totalSupply()')) == 0x18160ddd
* bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
* bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
*
* => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
*/
bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
// Events
event Refund (uint256 amount, address indexed user);
event NameChange (uint256 indexed tokenId, string newName);
event DescriptionChange (uint256 indexed tokenId, string newDescription);
event Burn (uint256 indexed tokenId, address indexed user);
event BnbRewards (uint256 indexed tokenId, address indexed user, uint256 bnbAmount);
event BurnRewardsInitialized(uint256 contractBalance, uint256 burnRewardPool, uint256 accumulatedSupply, uint256 burnRewards, uint256 emissionStart);
// check if the caller is the tokenId's owner
modifier onlyTokenOwner(uint256 tokenId) {
address owner = ownerOf(tokenId);
require(_msgSender() == owner, "caller is not the token's owner");
_;
}
modifier onlyCreator() {
require(address(msg.sender) == address(creator1) || address(msg.sender) == address(creator2), "you are not authorized");
_;
}
modifier onlyCreator1() {
require(address(msg.sender) == address(creator1), "you are not creator1");
_;
}
modifier onlyCreator2() {
require(address(msg.sender) == address(creator2), "you are not creator2");
_;
}
modifier isFinalized() {
require(burnRewards != 0, "burnRewards is not set");
require(startingIndex != 0, "startingIndex is not set");
require(teamPoolSize != 0, "teamPoolSize is not set");
_;
}
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor (string memory name, string memory symbol, address mbtAddress, uint256 saleStartTimestamp, uint256 maxSupply, uint base_price) {
_name = name;
_symbol = symbol;
_mbtAddress = mbtAddress;
MAX_NFT_SUPPLY = maxSupply;
BASE_PRICE = base_price;
SALE_START_TIMESTAMP = saleStartTimestamp; // set by constructor
MBT_BONUS_END_TIMESTAMP = SALE_START_TIMESTAMP + 30 days;
HARD_FINALIZE_TIMESTAMP = SALE_START_TIMESTAMP + 30 days + 30 days;
// register the supported interfaces to conform to ERC721 via ERC165
_registerInterface(_INTERFACE_ID_ERC721);
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
burnRewardPoolRecords = [700000000000000000, 690184328064006930, 680506295293092491, 670963971657413890, 661555454190756566, 652278866611037483, 643132358946129872, 634114107164934803, 625222312813626013, 616455202656995457, 607811028324828041, 599288065963235036, 590884615890876622, 582599002260005018, 574429572722260597, 566374698099154335, 558432772057170882, 550602210787427472, 542881452689824768, 535268958061626679, 527763208790407021, 520362708051301808, 513065980008506782, 505871569520960666, 498778041852155436, 491783982384015752, 484887996334790475, 478088708480900027, 471384762882684109, 464774822613995094, 458257569495583167, 451831703832220039, 445495944153508818, 439249026958328343, 433089706462861019, 427016754352153903, 421028959535163498, 415125127903235403, 409304082091970658, 403564661246431286, 397905720789638221, 392326132194315441, 386824782757834799, 381400575380316663, 376052428345842115, 370779275106733076, 365580064070857342, 360453758391916109, 355399335762672170, 350415788211077542, 345502121899259878, 340657356925327561, 335880527127953972, 331170679893701948, 326526875967050013, 321948189263082495, 317433706682806175, 312982527931056637, 308593765336958009, 304266543676900281, 300000000000000000];
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view override returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address");
return _holderNFT[owner].length();
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view override returns (address) {
return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token");
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
return _holderNFT[owner].at(index);
}
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
// _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds
return _tokenOwners.length();
}
function accumulatedSupply() public view override returns (uint256) {
return ACCUMULATED_TOTAL_SUPPLY;
}
function accumulatedBurned() public view returns (uint256) {
return accumulatedSupply().sub(totalSupply());
}
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
function tokenByIndex(uint256 index) public view override returns (uint256) {
(uint256 tokenId, ) = _tokenOwners.at(index);
return tokenId;
}
/**
* @dev Returns name of the NFT at index.
*/
function tokenNameByIndex(uint256 index) public view returns (string memory) {
return _tokenName[index];
}
/**
* @dev Returns description of the NFT at index.
*/
function tokenDescriptionByIndex(uint256 index) public view returns (string memory) {
return _tokenDescription[index];
}
/**
* @dev Gets face's MBT generation multiplier
*/
function tokenMBTGeneratingMultiplierByIndex(uint256 index) public view override returns (uint256) {
return _tokenMBTGeneratingMultiplier[index];
}
/**
* @dev Returns if the name has been reserved.
*/
function isNameReserved(string memory nameString) public view returns (bool) {
return _nameReserved[toLower(nameString)];
}
/**
* @dev Returns if the NFT has bonus MBTs
*/
function isMintedWithBonusMBT(uint256 index) public view override returns (bool) {
return _mintedWithBonusMBT[index];
}
/**
* @dev Returns initial granted MBT amount
*/
function initGrantedMBT() public pure override returns (uint256) {
return NAME_CHANGE_PRICE + DESCRIPTION_CHANGE_PRICE + 170 * PRECISION; // 170 more :)
}
/**
* @dev Init BearNBear initial price
*/
function initNFTPrice() public view returns (uint256) {
require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");
require(accumulatedSupply() <= MAX_NFT_SUPPLY, "Sale has already ended");
uint currentSupply = accumulatedSupply();
if (currentSupply >= 17150) {
// 17150, 17151, 17152 // 3 pieces
// 100.00 BNB
return 1000*BASE_PRICE;
} else if (currentSupply >= 17050) {
// 17050 - 17149
// 10.0 BNB
return 100*BASE_PRICE;
} else if (currentSupply >= 13800) {
// 13800 - 17049
// 2.4 BNB
return 24*BASE_PRICE;
} else if (currentSupply >= 10500) {
// 10500 - 13799
// 1.2 BNB
return 12*BASE_PRICE;
} else if (currentSupply >= 7100) {
// 7100 - 10499
// 0.6 BNB
return 6*BASE_PRICE;
} else if (currentSupply >= 3600) {
// 3600 - 7099
// 0.3 BNB
return 3*BASE_PRICE;
} else {
// 0 - 3599
// 0.1 BNB
return 1*BASE_PRICE;
}
}
/**
* @dev Init MBT generation multiplier
*/
function initMBTMultiplier() public view returns (uint256) {
require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");
require(accumulatedSupply() <= MAX_NFT_SUPPLY, "Sale has already ended");
uint currentSupply = accumulatedSupply();
if (currentSupply >= 17150) {
// 17150 - 17153
return 22;
} else if (currentSupply >= 17050) {
// 17050 - 17150
return 20;
} else if (currentSupply >= 13800) {
// 13800 - 17049
return 18;
} else if (currentSupply >= 10500) {
// 10500 - 13799
return 16;
} else if (currentSupply >= 7100) {
// 7100 - 10499
return 14;
} else if (currentSupply >= 3600) {
// 3600 - 7099
return 12;
} else {
// 0 - 3599
// generate 10 MBT per day
return 10;
}
}
/**
* @dev Buy/mint BBTs
*/
function mintBBT(uint256 numberOfNfts) nonReentrant public payable {
require(block.timestamp >= SALE_START_TIMESTAMP, "Sale has not started");
require(accumulatedSupply() <= MAX_NFT_SUPPLY, "Sale has already ended");
require(numberOfNfts > 0, "numberOfNfts cannot be 0");
require(numberOfNfts <= 50, "You may not buy more than 50 NFTs at once");
require(accumulatedSupply().add(numberOfNfts) <= MAX_NFT_SUPPLY, "Exceeds MAX_NFT_SUPPLY");
uint256 senderValue = msg.value;
require(senderValue >= initNFTPrice(), "BNB value is not enought");
while(numberOfNfts>0) {
uint price = initNFTPrice();
if(senderValue < price){
break;
}
uint mintIndex = accumulatedSupply();
if (block.timestamp < HARD_FINALIZE_TIMESTAMP && block.timestamp < MBT_BONUS_END_TIMESTAMP) {
_mintedWithBonusMBT[mintIndex] = true;
}
numberOfNfts = numberOfNfts.sub(1);
senderValue = senderValue.sub(price);
if(teamPoolSize != 0) {
uint halfPrice = price.div(2);
creator1SharesContinue = creator1SharesContinue.add(halfPrice);
creator2SharesContinue = creator2SharesContinue.add(halfPrice);
}
_safeMint(_msgSender(), mintIndex);
}
// require(numberOfNfts==0, "The amoutn doesn no match what you want.")
if(senderValue>0){
// sanity check
require(senderValue <= msg.value, "Cannot refund more than what the buyer initially sent");
// refund BNB back to the buyer
(bool sent, ) = _msgSender().call{value : senderValue}("");
require(sent, "Failed to refund BNB after minting");
emit Refund(senderValue, _msgSender());
}
/**
* Source of randomness. Theoretical miner withhold manipulation possible but should be sufficient in a pragmatic sense
*/
if (accumulatedSupply() == MAX_NFT_SUPPLY || block.timestamp >= HARD_FINALIZE_TIMESTAMP) {
finalize(block.number);
}
}
function getBurnRewardPoolSize() public view returns (uint256) {
// before sale starts
if(ACTUAL_REVEAL_TIMESTAMP != 0) {
uint length = ACTUAL_REVEAL_TIMESTAMP.sub(SALE_START_TIMESTAMP) / 1 days;
return burnRewardPoolRecords[length];
}
if(block.timestamp < SALE_START_TIMESTAMP) {
return burnRewardPoolRecords[0];
}
if(block.timestamp.sub(SALE_START_TIMESTAMP) >= 60 days) {
return 300000000000000000;
}
uint length = block.timestamp.sub(SALE_START_TIMESTAMP) / 1 days;
return burnRewardPoolRecords[length];
}
/**
* @dev to completely end the sale:
1. finalize startingIndex
2. set burnRewards/rewardPoolSize/teamPoolSize
3. set emissionStart of MiniBearToken contract
*/
function finalize(uint blockNumber) public {
require(startingIndex == 0, "Starting index is already set");
require(burnRewards == 0, "Rewards is already set");
require(rewardPoolSize == 0, "Rewards is already set");
uint seed1 = uint(keccak256(abi.encodePacked(blockhash(blockNumber-1))));
uint seed2 = uint(keccak256(abi.encodePacked("BearNBear")));
uint seed3 = uint(keccak256(abi.encodePacked(_msgSender())));
uint seed4 = uint(keccak256(abi.encodePacked(gasleft())));
uint seed5 = uint(keccak256(abi.encodePacked("BinanceSmartChain")));
uint seed6 = uint(keccak256(abi.encodePacked(block.difficulty)));
startingIndex = (seed1 ^ seed2 ^ seed3 ^ seed4 ^ seed5 ^ seed6) % MAX_NFT_SUPPLY; // 17153 0 ~ 17152
// Prevent default sequence
if (startingIndex == 0) {
startingIndex = startingIndex.add(accumulatedSupply()-1);
}
ACTUAL_REVEAL_TIMESTAMP = block.timestamp;
// burn rewards set as 125% of everage BNB redistribution
// lead to up to 80% of BBT will be/can be burned at the end of the extinction of the BNB rewards pool
uint burnRewardPool = getBurnRewardPoolSize();
rewardPoolSize = address(this).balance.mul(burnRewardPool).div(PRECISION); // in BNB
teamPoolSize = address(this).balance.sub(rewardPoolSize); // in
burnRewards = rewardPoolSize.div(accumulatedSupply()).mul(125).div(100);
emit BurnRewardsInitialized(address(this).balance, burnRewardPool, accumulatedSupply(), burnRewards, block.timestamp);
IMiniBearToken(_mbtAddress).setEmissionStart(block.timestamp);
}
/**
* @dev Changes the name for BearNBear tokenId
*/
function changeName(uint256 tokenId, string memory newName) onlyTokenOwner(tokenId) public {
require(validateName(newName) == true, "Not a valid new name");
require(sha256(bytes(newName)) != sha256(bytes(_tokenName[tokenId])), "New name is same as the current one");
require(isNameReserved(newName) == false, "Name already reserved");
IERC20(_mbtAddress).transferFrom(_msgSender(), address(this), NAME_CHANGE_PRICE);
// If already named, dereserve old name
if (bytes(_tokenName[tokenId]).length > 0) {
toggleReserveName(_tokenName[tokenId], false);
}
toggleReserveName(newName, true);
_tokenName[tokenId] = newName;
IERC20(_mbtAddress).burn(NAME_CHANGE_PRICE);
emit NameChange(tokenId, newName);
}
/**
* @dev Changes the description for BearNBear tokenId
*/
function changeDescription(uint256 tokenId, string memory newDescription) onlyTokenOwner(tokenId) public {
require(validateDescription(newDescription) == true, "Not a valid new description");
require(sha256(bytes(newDescription)) != sha256(bytes(_tokenDescription[tokenId])), "New name is same as the current one");
IERC20(_mbtAddress).transferFrom(_msgSender(), address(this), DESCRIPTION_CHANGE_PRICE);
_tokenDescription[tokenId] = newDescription;
IERC20(_mbtAddress).burn(DESCRIPTION_CHANGE_PRICE);
emit DescriptionChange(tokenId, newDescription);
}
/**
* @dev Creators only
*/
function creator1SetNewCreator1(address newCreator) onlyCreator1 public {
creator1 = newCreator;
}
function creator2SetNewCreator2(address newCreator) onlyCreator2 public {
creator2 = newCreator;
}
function creator2SetCreator1Shares(uint shares) onlyCreator2 public {
require(!creator1SharesLocked, "creator1SharesLocked is locked");
require(shares < teamPoolSize, "Not enough teamPoolSize allocation");
creator1Shares = shares;
}
function creator1SetCreator2Shares(uint shares) onlyCreator1 public {
require(!creator2SharesLocked, "creator2SharesLocked is locked");
require(shares < teamPoolSize, "Not enough teamPoolSize allocation");
creator2Shares = shares;
}
function adminResetCreatorShares() onlyOwner public {
creator2Shares = 0;
creator1Shares = 0;
creator1SharesLocked = false;
creator1SharesLocked = false;
}
function creator1LockShares(uint lockAmount) onlyCreator1 public {
require(lockAmount == creator1Shares, "lockAmount not correct");
creator1SharesLocked = true;
}
function creator2LockShares(uint lockAmount) onlyCreator2 public {
require(lockAmount == creator2Shares, "lockAmount not correct");
creator2SharesLocked = true;
}
function creator1Withdraw() isFinalized onlyCreator1 nonReentrant public {
require(creator1SharesLocked, "creator1Shares is not locked");
require(creator2SharesLocked, "creator2Shares is not locked");
require(creator1Shares.add(creator2Shares) <= teamPoolSize, "creator1Shares + creator2Shares > teamPoolSize");
(bool sent, ) = _msgSender().call{value : creator1Shares+creator1SharesContinue}("");
require(sent, "Failed to withdraw BNB");
creator1Shares = creator1Shares.sub(creator1Shares);
creator1SharesContinue = creator1SharesContinue.sub(creator1SharesContinue);
}
function creator2Withdraw() isFinalized onlyCreator2 nonReentrant public {
require(creator2SharesLocked, "creator2Shares is not locked");
require(creator1SharesLocked, "creator1Shares is not locked");
require(creator1Shares.add(creator2Shares) <= teamPoolSize, "creator1Shares + creator2Shares > teamPoolSize");
(bool sent, ) = _msgSender().call{value : creator2Shares+creator2SharesContinue}("");
require(sent, "Failed to withdraw BNB");
creator2Shares = creator2Shares.sub(creator2Shares);
creator2SharesContinue = creator2SharesContinue.sub(creator2SharesContinue);
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not owner nor approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view override returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
require(operator != _msgSender(), "ERC721: approve to caller");
_operatorApprovals[_msgSender()][operator] = approved;
emit ApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(from, to, tokenId, _data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `_data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view returns (bool) {
return _tokenOwners.contains(tokenId);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
d*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual {
_mint(to, tokenId);
require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId);
_holderNFT[to].add(tokenId);
_tokenMBTGeneratingMultiplier[tokenId] = initMBTMultiplier();
_tokenOwners.set(tokenId, to);
ACCUMULATED_TOTAL_SUPPLY = ACCUMULATED_TOTAL_SUPPLY.add(1);
emit Transfer(address(0), to, tokenId);
}
/**
* @dev Burns `tokenId`. See {ERC721-_burn}.
*
* Requirements:
*
* - The caller must own `tokenId` or be an approved operator.
*/
function burn(uint256 tokenId) onlyTokenOwner(tokenId) public virtual {
require(_exists(tokenId), "ERC721: cannot burn for nonexistent token");
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721Burnable: caller is not owner nor approved");
require(burnRewards != 0, "Burn rewards are all gone");
IERC20(_mbtAddress).transferFrom(_msgSender(), address(this), BURN_PRICE);
IERC20(_mbtAddress).burn(BURN_PRICE);
_burn(tokenId);
(bool sent, ) = _msgSender().call{value: burnRewards}("");
require(sent, "Failed to reward BNB");
emit Burn(tokenId, _msgSender());
emit BnbRewards(tokenId, _msgSender(), burnRewards);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
// Clear approvals
_approve(address(0), tokenId);
_holderNFT[owner].remove(tokenId);
_tokenOwners.remove(tokenId);
emit Transfer(owner, address(0), tokenId);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on _msgSender().
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId);
// Clear approvals from the previous owner
_approve(address(0), tokenId);
_holderNFT[from].remove(tokenId);
_holderNFT[to].add(tokenId);
_tokenOwners.set(tokenId, to);
emit Transfer(from, to, tokenId);
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @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 _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
private returns (bool)
{
if (!to.isContract()) {
return true;
}
bytes memory returndata = to.functionCall(abi.encodeWithSelector(
IERC721Receiver(to).onERC721Received.selector,
_msgSender(),
from,
tokenId,
_data
), "ERC721: transfer to non ERC721Receiver implementer");
bytes4 retval = abi.decode(returndata, (bytes4));
return (retval == _ERC721_RECEIVED);
}
function _approve(address to, uint256 tokenId) private {
_tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { }
/**
* @dev Reserves the name if isReserve is set to true, de-reserves if set to false
*/
function toggleReserveName(string memory str, bool isReserve) internal {
_nameReserved[toLower(str)] = isReserve;
}
/**
* @dev Check if the name string is valid (Alphanumeric and spaces without leading or trailing space)
*/
function validateName(string memory str) public pure returns (bool){
bytes memory b = bytes(str);
if(b.length < 1) return false;
if(b.length > 25) return false; // Cannot be longer than 25 characters
if(b[0] == 0x20) return false; // Leading space
if (b[b.length - 1] == 0x20) return false; // Trailing space
bytes1 lastChar = b[0];
for(uint i; i<b.length; i++){
bytes1 char = b[i];
if (char == 0x20 && lastChar == 0x20) return false; // Cannot contain continous spaces
if(
!(char >= 0x30 && char <= 0x39) && //9-0
!(char >= 0x41 && char <= 0x5A) && //A-Z
!(char >= 0x61 && char <= 0x7A) && //a-z
!(char == 0x20) //space
)
return false;
lastChar = char;
}
return true;
}
/**
* @dev Check if the description string is valid (Alphanumeric and spaces without leading or trailing space)
*/
function validateDescription(string memory str) public pure returns (bool){
bytes memory b = bytes(str);
if(b.length > 250) return false; // Cannot be longer than 250 characters
if(b[0] == 0x20) return false; // Leading space
if (b[b.length - 1] == 0x20) return false; // Trailing space
return true;
}
/**
* @dev Converts the string to lowercase
*/
function toLower(string memory str) public pure returns (string memory){
bytes memory bStr = bytes(str);
bytes memory bLower = new bytes(bStr.length);
for (uint i = 0; i < bStr.length; i++) {
// Uppercase character
if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) {
bLower[i] = bytes1(uint8(bStr[i]) + 32);
} else {
bLower[i] = bStr[i];
}
}
return string(bLower);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment