// SPDX-License-Identifier: MIT pragma solidity 0.6.12; pragma experimental ABIEncoderV2; import "@boringcrypto/boring-solidity/contracts/libraries/BoringMath.sol"; import "@boringcrypto/boring-solidity/contracts/BoringBatchable.sol"; import "@boringcrypto/boring-solidity/contracts/BoringOwnable.sol"; import "./libraries/SignedSafeMath.sol"; import "./interfaces/IRewarder.sol"; import "./interfaces/IGoldMiner.sol"; interface IMigratorMiner { // Take the current LP token address and return the new LP token address. // Migrator should have full access to the caller's LP token. function migrate(IERC20 token) external returns (IERC20); } /// @notice The (older) GoldMiner contract gives out a constant number of GOLN tokens per block. /// It is the only address with minting rights for GOLN. /// The idea for this GoldMiner V2 (MCV2) contract is therefore to be the owner of a dummy token /// that is deposited into the GoldMiner V1 (MCV1) contract. /// The allocation point for this pool on MCV1 is the total allocation point for all pools that receive double incentives. contract GoldMinerV2 is BoringOwnable, BoringBatchable { using BoringMath for uint256; using BoringMath128 for uint128; using BoringERC20 for IERC20; using SignedSafeMath for int256; /// @notice Info of each MCV2 user. /// `amount` LP token amount the user has provided. /// `rewardDebt` The amount of GOLN entitled to the user. struct UserInfo { uint256 amount; int256 rewardDebt; } /// @notice Info of each MCV2 pool. /// `allocPoint` The amount of allocation points assigned to the pool. /// Also known as the amount of GOLN to distribute per block. struct PoolInfo { uint128 accGoldNuggetPerShare; uint64 lastRewardBlock; uint64 allocPoint; } /// @notice Address of MCV1 contract. IGoldMiner public immutable GOLD_MINER; /// @notice Address of GOLN contract. IERC20 public immutable GOLN; /// @notice The index of MCV2 master pool in MCV1. uint256 public immutable GOLD_PID; // @notice The migrator contract. It has a lot of power. Can only be set through governance (owner). IMigratorMiner public migrator; /// @notice Info of each MCV2 pool. PoolInfo[] public poolInfo; /// @notice Address of the LP token for each MCV2 pool. IERC20[] public lpToken; /// @notice Address of each `IRewarder` contract in MCV2. IRewarder[] public rewarder; /// @notice Info of each user that stakes LP tokens. mapping (uint256 => mapping (address => UserInfo)) public userInfo; /// @dev Total allocation points. Must be the sum of all allocation points in all pools. uint256 public totalAllocPoint; uint256 private constant GOLDMINER_GOLN_PER_BLOCK = 1e20; uint256 private constant ACC_GOLN_PRECISION = 1e12; event Deposit(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event Withdraw(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount, address indexed to); event Harvest(address indexed user, uint256 indexed pid, uint256 amount); event LogPoolAddition(uint256 indexed pid, uint256 allocPoint, IERC20 indexed lpToken, IRewarder indexed rewarder); event LogSetPool(uint256 indexed pid, uint256 allocPoint, IRewarder indexed rewarder, bool overwrite); event LogUpdatePool(uint256 indexed pid, uint64 lastRewardBlock, uint256 lpSupply, uint256 accGoldNuggetPerShare); event LogInit(); /// @param _GOLD_MINER The LuckySwap MCV1 contract address. /// @param _goldnugget The GOLN token contract address. /// @param _GOLD_PID The pool ID of the dummy token on the base MCV1 contract. constructor(IGoldMiner _GOLD_MINER, IERC20 _goldnugget, uint256 _GOLD_PID) public { GOLD_MINER = _GOLD_MINER; GOLN = _goldnugget; GOLD_PID = _GOLD_PID; } /// @notice Deposits a dummy token to `GOLD_MINER` MCV1. This is required because MCV1 holds the minting rights for GOLN. /// Any balance of transaction sender in `dummyToken` is transferred. /// The allocation point for the pool on MCV1 is the total allocation point for all pools that receive double incentives. /// @param dummyToken The address of the ERC-20 token to deposit into MCV1. function init(IERC20 dummyToken) external { uint256 balance = dummyToken.balanceOf(msg.sender); require(balance != 0, "GoldMinerV2: Balance must exceed 0"); dummyToken.safeTransferFrom(msg.sender, address(this), balance); dummyToken.approve(address(GOLD_MINER), balance); GOLD_MINER.deposit(GOLD_PID, balance); emit LogInit(); } /// @notice Returns the number of MCV2 pools. function poolLength() public view returns (uint256 pools) { pools = poolInfo.length; } /// @notice Add a new LP to the pool. Can only be called by the owner. /// DO NOT add the same LP token more than once. Rewards will be messed up if you do. /// @param allocPoint AP of the new pool. /// @param _lpToken Address of the LP ERC-20 token. /// @param _rewarder Address of the rewarder delegate. function add(uint256 allocPoint, IERC20 _lpToken, IRewarder _rewarder) public onlyOwner { uint256 lastRewardBlock = block.number; totalAllocPoint = totalAllocPoint.add(allocPoint); lpToken.push(_lpToken); rewarder.push(_rewarder); poolInfo.push(PoolInfo({ allocPoint: allocPoint.to64(), lastRewardBlock: lastRewardBlock.to64(), accGoldNuggetPerShare: 0 })); emit LogPoolAddition(lpToken.length.sub(1), allocPoint, _lpToken, _rewarder); } /// @notice Update the given pool's GOLN allocation point and `IRewarder` contract. Can only be called by the owner. /// @param _pid The index of the pool. See `poolInfo`. /// @param _allocPoint New AP of the pool. /// @param _rewarder Address of the rewarder delegate. /// @param overwrite True if _rewarder should be `set`. Otherwise `_rewarder` is ignored. function set(uint256 _pid, uint256 _allocPoint, IRewarder _rewarder, bool overwrite) public onlyOwner { totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint); poolInfo[_pid].allocPoint = _allocPoint.to64(); if (overwrite) { rewarder[_pid] = _rewarder; } emit LogSetPool(_pid, _allocPoint, overwrite ? _rewarder : rewarder[_pid], overwrite); } /// @notice Set the `migrator` contract. Can only be called by the owner. /// @param _migrator The contract address to set. function setMigrator(IMigratorMiner _migrator) public onlyOwner { migrator = _migrator; } /// @notice Migrate LP token to another LP contract through the `migrator` contract. /// @param _pid The index of the pool. See `poolInfo`. function migrate(uint256 _pid) public { require(address(migrator) != address(0), "GoldMinerV2: no migrator set"); IERC20 _lpToken = lpToken[_pid]; uint256 bal = _lpToken.balanceOf(address(this)); _lpToken.approve(address(migrator), bal); IERC20 newLpToken = migrator.migrate(_lpToken); require(bal == newLpToken.balanceOf(address(this)), "GoldMinerV2: migrated balance must match"); lpToken[_pid] = newLpToken; } /// @notice View function to see pending GOLN on frontend. /// @param _pid The index of the pool. See `poolInfo`. /// @param _user Address of user. /// @return pending GOLN reward for a given user. function pendingGoldNugget(uint256 _pid, address _user) external view returns (uint256 pending) { PoolInfo memory pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][_user]; uint256 accGoldNuggetPerShare = pool.accGoldNuggetPerShare; uint256 lpSupply = lpToken[_pid].balanceOf(address(this)); if (block.number > pool.lastRewardBlock && lpSupply != 0) { uint256 blocks = block.number.sub(pool.lastRewardBlock); uint256 goldnuggetReward = blocks.mul(goldnuggetPerBlock()).mul(pool.allocPoint) / totalAllocPoint; accGoldNuggetPerShare = accGoldNuggetPerShare.add(goldnuggetReward.mul(ACC_GOLN_PRECISION) / lpSupply); } pending = int256(user.amount.mul(accGoldNuggetPerShare) / ACC_GOLN_PRECISION).sub(user.rewardDebt).toUInt256(); } /// @notice Update reward variables for all pools. Be careful of gas spending! /// @param pids Pool IDs of all to be updated. Make sure to update all active pools. function massUpdatePools(uint256[] calldata pids) external { uint256 len = pids.length; for (uint256 i = 0; i < len; ++i) { updatePool(pids[i]); } } /// @notice Calculates and returns the `amount` of GOLN per block. function goldnuggetPerBlock() public view returns (uint256 amount) { amount = uint256(GOLDMINER_GOLN_PER_BLOCK) .mul(GOLD_MINER.poolInfo(GOLD_PID).allocPoint) / GOLD_MINER.totalAllocPoint(); } /// @notice Update reward variables of the given pool. /// @param pid The index of the pool. See `poolInfo`. /// @return pool Returns the pool that was updated. function updatePool(uint256 pid) public returns (PoolInfo memory pool) { pool = poolInfo[pid]; if (block.number > pool.lastRewardBlock) { uint256 lpSupply = lpToken[pid].balanceOf(address(this)); if (lpSupply > 0) { uint256 blocks = block.number.sub(pool.lastRewardBlock); uint256 goldnuggetReward = blocks.mul(goldnuggetPerBlock()).mul(pool.allocPoint) / totalAllocPoint; pool.accGoldNuggetPerShare = pool.accGoldNuggetPerShare.add((goldnuggetReward.mul(ACC_GOLN_PRECISION) / lpSupply).to128()); } pool.lastRewardBlock = block.number.to64(); poolInfo[pid] = pool; emit LogUpdatePool(pid, pool.lastRewardBlock, lpSupply, pool.accGoldNuggetPerShare); } } /// @notice Deposit LP tokens to MCV2 for GOLN allocation. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to deposit. /// @param to The receiver of `amount` deposit benefit. function deposit(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][to]; // Effects user.amount = user.amount.add(amount); user.rewardDebt = user.rewardDebt.add(int256(amount.mul(pool.accGoldNuggetPerShare) / ACC_GOLN_PRECISION)); // Interactions IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onGoldNuggetReward(pid, to, to, 0, user.amount); } lpToken[pid].safeTransferFrom(msg.sender, address(this), amount); emit Deposit(msg.sender, pid, amount, to); } /// @notice Withdraw LP tokens from MCV2. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to withdraw. /// @param to Receiver of the LP tokens. function withdraw(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; // Effects user.rewardDebt = user.rewardDebt.sub(int256(amount.mul(pool.accGoldNuggetPerShare) / ACC_GOLN_PRECISION)); user.amount = user.amount.sub(amount); // Interactions IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onGoldNuggetReward(pid, msg.sender, to, 0, user.amount); } lpToken[pid].safeTransfer(to, amount); emit Withdraw(msg.sender, pid, amount, to); } /// @notice Harvest proceeds for transaction sender to `to`. /// @param pid The index of the pool. See `poolInfo`. /// @param to Receiver of GOLN rewards. function harvest(uint256 pid, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; int256 accumulatedGoldNugget = int256(user.amount.mul(pool.accGoldNuggetPerShare) / ACC_GOLN_PRECISION); uint256 _pendingGoldNugget = accumulatedGoldNugget.sub(user.rewardDebt).toUInt256(); // Effects user.rewardDebt = accumulatedGoldNugget; // Interactions if (_pendingGoldNugget != 0) { GOLN.safeTransfer(to, _pendingGoldNugget); } IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onGoldNuggetReward( pid, msg.sender, to, _pendingGoldNugget, user.amount); } emit Harvest(msg.sender, pid, _pendingGoldNugget); } /// @notice Withdraw LP tokens from MCV2 and harvest proceeds for transaction sender to `to`. /// @param pid The index of the pool. See `poolInfo`. /// @param amount LP token amount to withdraw. /// @param to Receiver of the LP tokens and GOLN rewards. function withdrawAndHarvest(uint256 pid, uint256 amount, address to) public { PoolInfo memory pool = updatePool(pid); UserInfo storage user = userInfo[pid][msg.sender]; int256 accumulatedGoldNugget = int256(user.amount.mul(pool.accGoldNuggetPerShare) / ACC_GOLN_PRECISION); uint256 _pendingGoldNugget = accumulatedGoldNugget.sub(user.rewardDebt).toUInt256(); // Effects user.rewardDebt = accumulatedGoldNugget.sub(int256(amount.mul(pool.accGoldNuggetPerShare) / ACC_GOLN_PRECISION)); user.amount = user.amount.sub(amount); // Interactions GOLN.safeTransfer(to, _pendingGoldNugget); IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onGoldNuggetReward(pid, msg.sender, to, _pendingGoldNugget, user.amount); } lpToken[pid].safeTransfer(to, amount); emit Withdraw(msg.sender, pid, amount, to); emit Harvest(msg.sender, pid, _pendingGoldNugget); } /// @notice Harvests GOLN from `GOLD_MINER` MCV1 and pool `GOLD_PID` to this MCV2 contract. function harvestFromGoldMiner() public { GOLD_MINER.deposit(GOLD_PID, 0); } /// @notice Withdraw without caring about rewards. EMERGENCY ONLY. /// @param pid The index of the pool. See `poolInfo`. /// @param to Receiver of the LP tokens. function emergencyWithdraw(uint256 pid, address to) public { UserInfo storage user = userInfo[pid][msg.sender]; uint256 amount = user.amount; user.amount = 0; user.rewardDebt = 0; IRewarder _rewarder = rewarder[pid]; if (address(_rewarder) != address(0)) { _rewarder.onGoldNuggetReward(pid, msg.sender, to, 0, 0); } // Note: transfer can fail or succeed if `amount` is zero. lpToken[pid].safeTransfer(to, amount); emit EmergencyWithdraw(msg.sender, pid, amount, to); } }