From 61c649c071ed33179b2575c7d0974635a1f81b6c Mon Sep 17 00:00:00 2001 From: owen05 Date: Tue, 8 Jun 2021 08:57:09 +0800 Subject: [PATCH] mineV3 --- contracts/DODOToken/DODOMineV3/BaseMine.sol | 271 +++++++++++ contracts/DODOToken/DODOMineV3/ERC20Mine.sol | 60 +++ .../DODOToken/DODOMineV3/RewardVault.sol | 59 +++ contracts/Factory/DODOMineV3Factory.sol | 162 +++++++ deploy-drops.txt | 30 ++ deploy-nft.txt | 437 ++++++++++++++++++ 6 files changed, 1019 insertions(+) create mode 100644 contracts/DODOToken/DODOMineV3/BaseMine.sol create mode 100644 contracts/DODOToken/DODOMineV3/ERC20Mine.sol create mode 100644 contracts/DODOToken/DODOMineV3/RewardVault.sol create mode 100644 contracts/Factory/DODOMineV3Factory.sol create mode 100644 deploy-drops.txt create mode 100644 deploy-nft.txt diff --git a/contracts/DODOToken/DODOMineV3/BaseMine.sol b/contracts/DODOToken/DODOMineV3/BaseMine.sol new file mode 100644 index 0000000..4f62301 --- /dev/null +++ b/contracts/DODOToken/DODOMineV3/BaseMine.sol @@ -0,0 +1,271 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {IRewardVault, RewardVault} from "./RewardVault.sol"; + +contract BaseMine is InitializableOwnable { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + // ============ Storage ============ + + struct RewardTokenInfo { + address rewardToken; + uint256 startBlock; + uint256 endBlock; + address rewardVault; + uint256 rewardPerBlock; + uint256 accRewardPerShare; + uint256 lastRewardBlock; + uint256 workThroughReward; + uint256 lastFlagBlock; + mapping(address => uint256) userRewardPerSharePaid; + mapping(address => uint256) userRewards; + } + + RewardTokenInfo[] public rewardTokenInfos; + + uint256 internal _totalSupply; + mapping(address => uint256) internal _balances; + + // ============ Event ============= + + event Claim(uint256 indexed i, address indexed user, uint256 reward); + event UpdateReward(uint256 indexed i, uint256 rewardPerBlock); + event UpdateEndBlock(uint256 indexed i, uint256 endBlock); + event NewRewardToken(uint256 indexed i, address rewardToken); + event RemoveRewardToken(address rewardToken); + event WithdrawLeftOver(address owner, uint256 i); + + // ============ View ============ + + function getPendingReward(address user, uint256 i) public view returns (uint256) { + require(i= totalReward) { + return 0; + }else { + return totalReward.sub(totalDepositReward); + } + } + } + require(false, "DODOMineV3: TOKEN_NOT_FOUND"); + } + + // ============ Claim ============ + + function claimReward(uint256 i) public { + require(i 0) { + rt.userRewards[msg.sender] = 0; + IRewardVault(rt.rewardVault).reward(msg.sender, reward); + emit Claim(i, msg.sender, reward); + } + } + + function claimAllRewards() external { + uint256 len = rewardTokenInfos.length; + for (uint256 i = 0; i < len; i++) { + claimReward(i); + } + } + + // =============== Ownable ================ + + function addRewardToken( + address rewardToken, + uint256 rewardPerBlock, + uint256 startBlock, + uint256 endBlock + ) external onlyOwner { + require(rewardToken != address(0), "DODOMineV3: TOKEN_INVALID"); + require(startBlock > block.number, "DODOMineV3: START_BLOCK_INVALID"); + require(endBlock > startBlock, "DODOMineV3: DURATION_INVALID"); + + uint256 len = rewardTokenInfos.length; + for (uint256 i = 0; i < len; i++) { + require( + rewardToken != rewardTokenInfos[i].rewardToken, + "DODOMineV3: TOKEN_ALREADY_ADDED" + ); + } + + RewardTokenInfo storage rt = rewardTokenInfos.push(); + rt.rewardToken = rewardToken; + rt.startBlock = startBlock; + rt.lastFlagBlock = startBlock; + rt.endBlock = endBlock; + rt.rewardPerBlock = rewardPerBlock; + rt.rewardVault = address(new RewardVault(rewardToken)); + + emit NewRewardToken(len, rewardToken); + } + + function setEndBlock(uint256 i, uint256 newEndBlock) + external + onlyOwner + { + require(i < rewardTokenInfos.length, "DODOMineV3: REWARD_ID_NOT_FOUND"); + _updateReward(address(0), i); + RewardTokenInfo storage rt = rewardTokenInfos[i]; + + require(block.number < newEndBlock, "DODOMineV3: END_BLOCK_INVALID"); + require(block.number > rt.startBlock, "DODOMineV3: NOT_START"); + require(block.number < rt.endBlock, "DODOMineV3: ALREADY_CLOSE"); + + rt.endBlock = newEndBlock; + emit UpdateEndBlock(i, newEndBlock); + } + + function setReward(uint256 i, uint256 newRewardPerBlock) + external + onlyOwner + { + require(i < rewardTokenInfos.length, "DODOMineV3: REWARD_ID_NOT_FOUND"); + _updateReward(address(0), i); + RewardTokenInfo storage rt = rewardTokenInfos[i]; + + require(block.number < rt.endBlock, "DODOMineV3: ALREADY_CLOSE"); + + rt.workThroughReward = rt.workThroughReward.add((block.number.sub(rt.lastFlagBlock)).mul(rt.rewardPerBlock)); + rt.rewardPerBlock = newRewardPerBlock; + rt.lastFlagBlock = block.number; + + emit UpdateReward(i, newRewardPerBlock); + } + + function withdrawLeftOver(uint256 i, uint256 amount) external onlyOwner { + require(i < rewardTokenInfos.length, "DODOMineV3: REWARD_ID_NOT_FOUND"); + + RewardTokenInfo storage rt = rewardTokenInfos[i]; + require(block.number > rt.endBlock, "DODOMineV3: MINING_NOT_FINISHED"); + + IRewardVault(rt.rewardVault).withdrawLeftOver(msg.sender,amount); + + emit WithdrawLeftOver(msg.sender, i); + } + + + function directTransferOwnership(address newOwner) external onlyOwner { + require(newOwner != address(0), "DODOMineV3: ZERO_ADDRESS"); + emit OwnershipTransferred(_OWNER_, newOwner); + _OWNER_ = newOwner; + } + + // ============ Internal ============ + + function _updateReward(address user, uint256 i) internal { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + if (rt.lastRewardBlock != block.number){ + rt.accRewardPerShare = _getAccRewardPerShare(i); + rt.lastRewardBlock = block.number; + } + if (user != address(0)) { + rt.userRewards[user] = getPendingReward(user, i); + rt.userRewardPerSharePaid[user] = rt.accRewardPerShare; + } + } + + function _updateAllReward(address user) internal { + uint256 len = rewardTokenInfos.length; + for (uint256 i = 0; i < len; i++) { + _updateReward(user, i); + } + } + + function _getUnrewardBlockNum(uint256 i) internal view returns (uint256) { + RewardTokenInfo memory rt = rewardTokenInfos[i]; + if (block.number < rt.startBlock || rt.lastRewardBlock > rt.endBlock) { + return 0; + } + uint256 start = rt.lastRewardBlock < rt.startBlock ? rt.startBlock : rt.lastRewardBlock; + uint256 end = rt.endBlock < block.number ? rt.endBlock : block.number; + return end.sub(start); + } + + function _getAccRewardPerShare(uint256 i) internal view returns (uint256) { + RewardTokenInfo memory rt = rewardTokenInfos[i]; + if (totalSupply() == 0) { + return rt.accRewardPerShare; + } + return + rt.accRewardPerShare.add( + DecimalMath.divFloor(_getUnrewardBlockNum(i).mul(rt.rewardPerBlock), totalSupply()) + ); + } + +} diff --git a/contracts/DODOToken/DODOMineV3/ERC20Mine.sol b/contracts/DODOToken/DODOMineV3/ERC20Mine.sol new file mode 100644 index 0000000..38da2c5 --- /dev/null +++ b/contracts/DODOToken/DODOMineV3/ERC20Mine.sol @@ -0,0 +1,60 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {BaseMine} from "./BaseMine.sol"; + +contract ERC20Mine is BaseMine { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + // ============ Storage ============ + + address public _TOKEN_; + + function init(address owner, address token) external { + super.initOwner(owner); + _TOKEN_ = token; + } + + // ============ Event ============ + + event Deposit(address indexed user, uint256 amount); + event Withdraw(address indexed user, uint256 amount); + + // ============ Deposit && Withdraw && Exit ============ + + function deposit(uint256 amount) external { + require(amount > 0, "DODOMineV3: CANNOT_DEPOSIT_ZERO"); + + _updateAllReward(msg.sender); + + uint256 erc20OriginBalance = IERC20(_TOKEN_).balanceOf(address(this)); + IERC20(_TOKEN_).safeTransferFrom(msg.sender, address(this), amount); + uint256 actualStakeAmount = IERC20(_TOKEN_).balanceOf(address(this)).sub(erc20OriginBalance); + + _totalSupply = _totalSupply.add(actualStakeAmount); + _balances[msg.sender] = _balances[msg.sender].add(actualStakeAmount); + + emit Deposit(msg.sender, actualStakeAmount); + } + + function withdraw(uint256 amount) external { + require(amount > 0, "DODOMineV3: CANNOT_WITHDRAW_ZERO"); + + _updateAllReward(msg.sender); + _totalSupply = _totalSupply.sub(amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + IERC20(_TOKEN_).safeTransfer(msg.sender, amount); + + emit Withdraw(msg.sender, amount); + } +} diff --git a/contracts/DODOToken/DODOMineV3/RewardVault.sol b/contracts/DODOToken/DODOMineV3/RewardVault.sol new file mode 100644 index 0000000..25ca614 --- /dev/null +++ b/contracts/DODOToken/DODOMineV3/RewardVault.sol @@ -0,0 +1,59 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {Ownable} from "../../lib/Ownable.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IERC20} from "../../intf/IERC20.sol"; + + +interface IRewardVault { + function reward(address to, uint256 amount) external; + function withdrawLeftOver(address to, uint256 amount) external; + function depositReward() external; + function _TOTAL_REWARD_() external view returns(uint256); +} + +contract RewardVault is Ownable { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + uint256 public _REWARD_RESERVE_; + uint256 public _TOTAL_REWARD_; + address public _REWARD_TOKEN_; + + // ============ Event ============= + event DepositReward(uint256 totalReward, uint256 inputReward, uint256 rewardReserve); + + constructor(address _rewardToken) public { + _REWARD_TOKEN_ = _rewardToken; + } + + function reward(address to, uint256 amount) external onlyOwner { + require(_REWARD_RESERVE_ >= amount, "VAULT_NOT_ENOUGH"); + _REWARD_RESERVE_ = _REWARD_RESERVE_.sub(amount); + IERC20(_REWARD_TOKEN_).safeTransfer(to, amount); + } + + function withdrawLeftOver(address to,uint256 amount) external onlyOwner { + require(_REWARD_RESERVE_ >= amount, "VAULT_NOT_ENOUGH"); + _REWARD_RESERVE_ = _REWARD_RESERVE_.sub(amount); + IERC20(_REWARD_TOKEN_).safeTransfer(to, amount); + } + + function depositReward() external { + uint256 rewardBalance = IERC20(_REWARD_TOKEN_).balanceOf(address(this)); + uint256 rewardInput = rewardBalance.sub(_REWARD_RESERVE_); + + _TOTAL_REWARD_ = _TOTAL_REWARD_.add(rewardInput); + _REWARD_RESERVE_ = rewardBalance; + + emit DepositReward(_TOTAL_REWARD_, rewardInput, _REWARD_RESERVE_); + } +} \ No newline at end of file diff --git a/contracts/Factory/DODOMineV3Factory.sol b/contracts/Factory/DODOMineV3Factory.sol new file mode 100644 index 0000000..70f56a4 --- /dev/null +++ b/contracts/Factory/DODOMineV3Factory.sol @@ -0,0 +1,162 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {IDODOApproveProxy} from "../SmartRoute/DODOApproveProxy.sol"; +import {IRewardVault} from "../DODOToken/DODOMineV3/RewardVault.sol"; +import {ICloneFactory} from "../lib/CloneFactory.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; + +interface IMineV3 { + function init(address owner, address token) external; + + function addRewardToken( + address rewardToken, + uint256 rewardPerBlock, + uint256 startBlock, + uint256 endBlock + ) external; + + function getVaultByRewardToken(address rewardToken) external view returns(address); + + function directTransferOwnership(address newOwner) external; +} + +/** + * @title DODOMineV3 Factory + * @author DODO Breeder + * + * @notice Create And Register DODOMineV3 Contracts + */ +contract DODOMineV3Factory is InitializableOwnable { + using SafeMath for uint256; + // ============ Templates ============ + + address public immutable _CLONE_FACTORY_; + address public _MINEV3_TEMPLATE_; + address public _DODO_APPROVE_PROXY_; + mapping (address => bool) public singleTokenList; + + // minePool -> stakeToken + mapping(address => address) public _MINE_REGISTRY_; + // lpToken -> minePool + mapping(address => address) public _LP_REGISTRY_; + // singleToken -> minePool + mapping(address => address[]) public _SINGLE_REGISTRY_; + + // ============ Events ============ + + event NewMineV3(address mine, address stakeToken, bool isLpToken); + event RemoveMineV3(address mine, address stakeToken); + + constructor( + address cloneFactory, + address mineTemplate, + address dodoApproveProxy + ) public { + _CLONE_FACTORY_ = cloneFactory; + _MINEV3_TEMPLATE_ = mineTemplate; + _DODO_APPROVE_PROXY_ = dodoApproveProxy; + } + + // ============ Functions ============ + + function createDODOMineV3( + address stakeToken, + bool isLpToken, + address[] memory rewardTokens, + uint256[] memory rewardPerBlock, + uint256[] memory startBlock, + uint256[] memory endBlock + ) external returns (address newMineV3) { + require(rewardTokens.length > 0, "REWARD_EMPTY"); + require(rewardTokens.length == rewardPerBlock.length, "REWARD_PARAM_NOT_MATCH"); + require(startBlock.length == rewardPerBlock.length, "REWARD_PARAM_NOT_MATCH"); + require(endBlock.length == rewardPerBlock.length, "REWARD_PARAM_NOT_MATCH"); + + newMineV3 = ICloneFactory(_CLONE_FACTORY_).clone(_MINEV3_TEMPLATE_); + + IMineV3(newMineV3).init(address(this), stakeToken); + + for(uint i = 0; i