// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "./IStarNFT.sol"; interface IERC20Burnable is IERC20Upgradeable { function burnFrom(address account, uint256 amount) external; } contract StarNFTLogic is ContextUpgradeable, OwnableUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; using SafeMathUpgradeable for uint256; using CountersUpgradeable for CountersUpgradeable.Counter; CountersUpgradeable.Counter private cateIds; struct StarCate { uint256 maxUnit; uint256 multiple; uint256 usedUnit; } struct StarMeta { StarLevel level; uint256 cateId; uint256 price; uint256 multiple; } struct LevelMethod { uint256 usedUnit; uint256 burned; uint256 price; bool isInc; uint256 base; uint256 step; bool isUnique; } enum StarLevel { COMMON, SPECIAL, EXCLUSIVE } IERC20Burnable public starToken; IStarNFT public starNFT; address public bonusAddr; uint256 public totalPrice; uint256 fee; mapping(uint256 => StarCate) public cateId; mapping(StarLevel => uint256[]) public starCate; mapping(StarLevel => LevelMethod) public levelMethod; mapping(uint256 => StarMeta) public starMeta; function initialize(address _starToken, address _bonus, address _starNFT, uint256 _fee) public initializer { __starNFTLogic_init(_starToken, _bonus, _starNFT, _fee); } function __starNFTLogic_init(address _starToken, address _bonus, address _starNFT, uint256 _fee) internal initializer { __Context_init_unchained(); __Ownable_init_unchained(); __starNFTLogic_init_unchained(_starToken, _bonus, _starNFT, _fee); } function __starNFTLogic_init_unchained(address _starToken, address _bonus, address _starNFT, uint256 _fee) internal initializer { starToken = IERC20Burnable(_starToken); starNFT = IStarNFT(_starNFT); bonusAddr = _bonus; fee = _fee; } //mint function function mint() public returns (uint256) { uint256 _cateId = starCate[StarLevel.COMMON][getRndCateIndex(StarLevel.COMMON)]; LevelMethod storage _levelMethod = levelMethod[StarLevel.COMMON]; uint256 _price = _levelMethod.price; require(_price > 0, "level price error"); if (_levelMethod.isInc && _levelMethod.base > 0) { _price = _price.add(_levelMethod.usedUnit.mod(_levelMethod.base).mul(_levelMethod.step)); } uint256 _fee = _price.mul(fee).div(100); starToken.transferFrom(_msgSender(), address(this), _price); starToken.burnFrom(address(this), _price.sub(_fee)); starToken.transfer(bonusAddr, _fee); uint256 _starId = starNFT.mint(_msgSender(), _cateId); uint256 _multi = cateId[_cateId].multiple; starMeta[_starId] = StarMeta(StarLevel.COMMON, _cateId, _price, _multi); cateId[_cateId].usedUnit += 1; _levelMethod.usedUnit += 1; totalPrice = totalPrice.add(_price.mul(_multi).div(100)); return _starId; } function melt(uint256[] memory _tokenIds, StarLevel _from, StarLevel _to) public returns (uint256) { require(uint256(_to) == uint256(_from) + 1, "melt level error"); require(starNFT.massVerifyOwner(_msgSender(), _tokenIds), "not your tokens"); (uint256 _price, uint256 _multiple) = verifyTokens(_from, _to, _tokenIds); require(_price != 0, "token error"); starNFT.massBurn(_tokenIds); levelMethod[_from].burned += _tokenIds.length; uint256 _cateId = starCate[_to][getRndCateIndex(_to)]; uint256 _starId = starNFT.mint(_msgSender(), _cateId); uint256 _multi = _multiple.mul(cateId[_cateId].multiple).div(100); starMeta[_starId] = StarMeta(_to, _cateId, _price, _multi); cateId[_cateId].usedUnit += 1; levelMethod[_to].usedUnit += 1; totalPrice = totalPrice.add(_price.mul(_multi).div(100)); return _starId; } function getRndCateIndex(StarLevel _level) public view returns (uint256) { uint256[] storage _cate = starCate[_level]; require(_cate.length > 0, "no cate"); uint256 _rndCateIndex; uint256 nonce; do { _rndCateIndex = uint256(keccak256(abi.encodePacked(block.coinbase, msg.sender, nonce, block.timestamp))) % (_cate.length -1) +1; if((cateId[_cate[_rndCateIndex]].maxUnit - cateId[_cate[_rndCateIndex]].usedUnit) == 1){ _rndCateIndex = 0; break; } if (cateId[_cate[_rndCateIndex]].maxUnit > cateId[_cate[_rndCateIndex]].usedUnit) break; nonce ++; } while (true); return _rndCateIndex; } function indexOf(uint256[] memory A, uint256 a) internal pure returns (bool) { uint256 length = A.length; for (uint256 i = 0; i < length; i++) { if (A[i] == a) { return true; } } return false; } function verifyTokens(StarLevel _level, StarLevel _newLevel, uint256[] memory _tokenIds) view internal returns (uint256, uint256) { LevelMethod memory _levelMethod = levelMethod[_newLevel]; uint256 _price = _levelMethod.price; if (_levelMethod.isInc && _levelMethod.base > 0) { _price = _price.add(_levelMethod.usedUnit.div(_levelMethod.base).mul(_levelMethod.step)); } require(_tokenIds.length == _price, "token amount error"); uint256 multiple; uint256 price; uint256[] memory _cateIds = new uint256[](_tokenIds.length); for (uint256 i = 0; i < _tokenIds.length; i ++) { require(starMeta[_tokenIds[i]].level == _level, "error level"); price = price.add(starMeta[_tokenIds[i]].price); multiple = multiple.add(starMeta[_tokenIds[i]].multiple); if (!_levelMethod.isUnique) { require(!indexOf(_cateIds, starMeta[_tokenIds[i]].cateId), "must be unique cate"); } _cateIds[i] = starMeta[_tokenIds[i]].cateId; } return (price, multiple.div(_tokenIds.length)); } function getCateLength(StarLevel _level) public view returns (uint256) { return starCate[_level].length; } function addCate(StarLevel _level, uint256 _maxUnit, uint256 _multiple, string memory _cateUri) onlyOwner public { cateIds.increment(); uint256 _cateId = cateIds.current(); cateId[_cateId] = StarCate(_maxUnit, _multiple, 0); starCate[_level].push(_cateId); starNFT.setCateURI(_cateId, _cateUri); } function editCate(uint256 _cateId, uint256 _maxUnit, uint256 _multiple, string memory _cateUri) onlyOwner public { StarCate storage _cate = cateId[_cateId]; _cate.maxUnit = _maxUnit; _cate.multiple = _multiple; starNFT.setCateURI(_cateId, _cateUri); } function setLevelMethod(StarLevel _level, uint256 _price, bool _isInc, uint256 _base, uint256 _step, bool _isUnique) onlyOwner public { LevelMethod storage _levelMethod = levelMethod[_level]; _levelMethod.price = _price; _levelMethod.isInc = _isInc; _levelMethod.base = _base; _levelMethod.step = _step; _levelMethod.isUnique = _isUnique; } function setStarNFT(address _starNFT) onlyOwner public { require(_starNFT != address(0), "address error"); starNFT = IStarNFT(_starNFT); } }