Created
April 27, 2021 05:14
-
-
Save amazingandyyy/565d71987d2e83d17e094c9611c50bbf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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