/* 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; 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 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), "DODOMineV2: TOKEN_INVALID"); require(startBlock > block.number, "DODOMineV2: START_BLOCK_INVALID"); require(endBlock > startBlock, "DODOMineV2: DURATION_INVALID"); uint256 len = rewardTokenInfos.length; for (uint256 i = 0; i < len; i++) { require( rewardToken != rewardTokenInfos[i].rewardToken, "DODOMineV2: TOKEN_ALREADY_ADDED" ); } RewardTokenInfo storage rt = rewardTokenInfos.push(); rt.rewardToken = rewardToken; rt.startBlock = startBlock; rt.endBlock = endBlock; rt.rewardPerBlock = rewardPerBlock; rt.rewardVault = address(new RewardVault(rewardToken)); emit NewRewardToken(len, rewardToken); } function removeRewardToken(address rewardToken) external onlyOwner { uint256 len = rewardTokenInfos.length; for (uint256 i = 0; i < len; i++) { if (rewardToken == rewardTokenInfos[i].rewardToken) { if(i != len - 1) { rewardTokenInfos[i] = rewardTokenInfos[len - 1]; } rewardTokenInfos.pop(); emit RemoveRewardToken(rewardToken); break; } } } function setEndBlock(uint256 i, uint256 newEndBlock) external onlyOwner { require(i < rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND"); _updateReward(address(0), i); RewardTokenInfo storage rt = rewardTokenInfos[i]; require(block.number < newEndBlock, "DODOMineV2: END_BLOCK_INVALID"); require(block.number > rt.startBlock, "DODOMineV2: NOT_START"); require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE"); rt.endBlock = newEndBlock; emit UpdateEndBlock(i, newEndBlock); } function setReward(uint256 i, uint256 newRewardPerBlock) external onlyOwner { require(i < rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND"); _updateReward(address(0), i); RewardTokenInfo storage rt = rewardTokenInfos[i]; require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE"); rt.rewardPerBlock = newRewardPerBlock; emit UpdateReward(i, newRewardPerBlock); } function withdrawLeftOver(uint256 i, uint256 amount) external onlyOwner { require(i < rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND"); RewardTokenInfo storage rt = rewardTokenInfos[i]; require(block.number > rt.endBlock, "DODOMineV2: MINING_NOT_FINISHED"); IRewardVault(rt.rewardVault).withdrawLeftOver(msg.sender,amount); emit WithdrawLeftOver(msg.sender, i); } // ============ 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()) ); } }