|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.9; |
| 3 | + |
| 4 | +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; |
| 5 | +import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; |
| 6 | +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; |
| 7 | +import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; |
| 8 | +import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/draft-ERC721VotesUpgradeable.sol"; |
| 9 | +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; |
| 10 | +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
| 11 | +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; |
| 12 | +import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; |
| 13 | + |
| 14 | +contract ProfitMaker is Initializable, ERC721Upgradeable, ERC721VotesUpgradeable, ERC721EnumerableUpgradeable, OwnableUpgradeable, UUPSUpgradeable { |
| 15 | + using CountersUpgradeable for CountersUpgradeable.Counter; |
| 16 | + |
| 17 | + event Harvest(address indexed token, address recipient, uint256 amount); |
| 18 | + event Released(address indexed token, uint256 amount); |
| 19 | + event SetMintTime(uint64 from, uint64 to); |
| 20 | + |
| 21 | + IERC20Upgradeable public profitToken; |
| 22 | + uint64 public mintingStart; |
| 23 | + uint64 public mintingEnd; |
| 24 | + |
| 25 | + struct Unlock { |
| 26 | + uint64 start; |
| 27 | + uint64 duration; |
| 28 | + uint256 released; |
| 29 | + uint256[] balances; |
| 30 | + } |
| 31 | + |
| 32 | + mapping(address => Unlock) public unlocks; |
| 33 | + |
| 34 | + CountersUpgradeable.Counter private _tokenIdCounter; |
| 35 | + |
| 36 | + /// @custom:oz-upgrades-unsafe-allow constructor |
| 37 | + constructor() initializer {} |
| 38 | + |
| 39 | + function initialize(IERC20Upgradeable profitToken_) public initializer { |
| 40 | + profitToken = profitToken_; |
| 41 | + __ERC721_init("Profit Maker", "PM"); |
| 42 | + __ERC721Enumerable_init(); |
| 43 | + __Ownable_init(); |
| 44 | + __UUPSUpgradeable_init(); |
| 45 | + } |
| 46 | + |
| 47 | + function _baseURI() internal pure override returns (string memory) { |
| 48 | + return "https://api.stabilitydao.org/maker/"; |
| 49 | + } |
| 50 | + |
| 51 | + function safeMint(address to) public { |
| 52 | + require(profitToken.balanceOf(msg.sender) >= 10000 ether, "Not enough PROFIT tokens"); |
| 53 | + require(mintingStart <= uint64(block.timestamp), "Mint is not available right now"); |
| 54 | + require(mintingEnd >= uint64(block.timestamp), "Mint is not available right now"); |
| 55 | + uint256 tokenId = _tokenIdCounter.current(); |
| 56 | + require(tokenId < 80, "All tokens have already been minted"); |
| 57 | + profitToken.transferFrom(msg.sender, address(this), 10000 ether); |
| 58 | + _tokenIdCounter.increment(); |
| 59 | + _safeMint(to, tokenId); |
| 60 | + } |
| 61 | + |
| 62 | + function setUnlock(address token_, uint64 start_, uint64 duration_) public onlyOwner { |
| 63 | + unlocks[token_].start = start_; |
| 64 | + unlocks[token_].duration = duration_; |
| 65 | + } |
| 66 | + |
| 67 | + /** |
| 68 | + * @dev Amount of token already released |
| 69 | + */ |
| 70 | + function released(address token_) public view virtual returns (uint256) { |
| 71 | + return unlocks[token_].released; |
| 72 | + } |
| 73 | + |
| 74 | + function balanceToHarvest(address token_, uint256 tokenId_) public view returns (uint256) { |
| 75 | + require(ownerOf(tokenId_) == msg.sender, "You are not owner of token."); |
| 76 | + require(unlocks[token_].start > 0, "Token dont have unlock."); |
| 77 | + return unlocks[token_].balances[tokenId_]; |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * @dev Withdraw user balance |
| 82 | + */ |
| 83 | + function harvest(address token_, uint256 tokenId_) public { |
| 84 | + require(ownerOf(tokenId_) == msg.sender, "You are not owner of token."); |
| 85 | + require(unlocks[token_].start > 0, "Token dont have unlock."); |
| 86 | + uint256 releasable = unlocks[token_].balances[tokenId_]; |
| 87 | + require(releasable > 0, "No tokens to harvest"); |
| 88 | + unlocks[token_].balances[tokenId_] = 0; |
| 89 | + SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(token_), msg.sender, releasable); |
| 90 | + emit Harvest(token_, msg.sender, releasable); |
| 91 | + } |
| 92 | + |
| 93 | + /** |
| 94 | + * @dev Release the tokens that have already vested. |
| 95 | + * |
| 96 | + * Emits a {Released} event. |
| 97 | + */ |
| 98 | + function releaseToBalance(address token) public { |
| 99 | + uint256 releasable = vestedAmount(token, uint64(block.timestamp)) - released(token); |
| 100 | + require(releasable > 0, "Zero to release"); |
| 101 | + |
| 102 | + unlocks[token].released += releasable; |
| 103 | + |
| 104 | + uint256 totalUsers = _tokenIdCounter.current(); |
| 105 | + uint256 toRelease = releasable / totalUsers; |
| 106 | + for (uint256 i; i <= totalUsers; i++) { |
| 107 | + if (unlocks[token].balances.length > i) { |
| 108 | + unlocks[token].balances[i] += toRelease; |
| 109 | + } else { |
| 110 | + unlocks[token].balances.push(toRelease); |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + emit Released(token, releasable); |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * @dev Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. |
| 119 | + */ |
| 120 | + function vestedAmount(address token, uint64 timestamp) public view virtual returns (uint256) { |
| 121 | + return _vestingSchedule(token, IERC20Upgradeable(token).balanceOf(address(this)) + released(token), timestamp); |
| 122 | + } |
| 123 | + |
| 124 | + /** |
| 125 | + * @dev Virtual implementation of the vesting formula. This returns the amout vested, as a function of time, for |
| 126 | + * an asset given its total historical allocation. |
| 127 | + */ |
| 128 | + function _vestingSchedule(address token, uint256 totalAllocation, uint64 timestamp) internal view virtual returns (uint256) { |
| 129 | + if (unlocks[token].start == 0 || timestamp < unlocks[token].start) { |
| 130 | + return 0; |
| 131 | + } else if (timestamp > unlocks[token].start + unlocks[token].duration) { |
| 132 | + return totalAllocation; |
| 133 | + } else { |
| 134 | + return (totalAllocation * (timestamp - unlocks[token].start)) / unlocks[token].duration; |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + function setMintState(uint64 start_, uint64 end_) public onlyOwner { |
| 139 | + mintingStart = start_; |
| 140 | + mintingEnd = end_; |
| 141 | + emit SetMintTime(start_, end_); |
| 142 | + } |
| 143 | + |
| 144 | + function _authorizeUpgrade(address newImplementation) |
| 145 | + internal |
| 146 | + onlyOwner |
| 147 | + override |
| 148 | + {} |
| 149 | + |
| 150 | + // The following functions are overrides required by Solidity. |
| 151 | + |
| 152 | + function _beforeTokenTransfer(address from, address to, uint256 tokenId) |
| 153 | + internal |
| 154 | + override(ERC721Upgradeable, ERC721EnumerableUpgradeable) |
| 155 | + { |
| 156 | + super._beforeTokenTransfer(from, to, tokenId); |
| 157 | + } |
| 158 | + |
| 159 | + function _afterTokenTransfer( |
| 160 | + address from, |
| 161 | + address to, |
| 162 | + uint256 tokenId |
| 163 | + ) internal virtual override(ERC721Upgradeable, ERC721VotesUpgradeable) { |
| 164 | + super._afterTokenTransfer(from, to, tokenId); |
| 165 | + } |
| 166 | + |
| 167 | + function supportsInterface(bytes4 interfaceId) |
| 168 | + public |
| 169 | + view |
| 170 | + override(ERC721Upgradeable, ERC721EnumerableUpgradeable) |
| 171 | + returns (bool) |
| 172 | + { |
| 173 | + return super.supportsInterface(interfaceId); |
| 174 | + } |
| 175 | +} |
0 commit comments