// SPDX-License-Identifier: MIT pragma solidity 0.7.4; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "./WorldToken.sol"; contract WorldFarm is Ownable { using SafeMath for uint256; using SafeERC20 for IERC20; // Info of each user. struct UserInfo { uint256 amount; // How many LP tokens the user has provided. uint256 rewardDebt; // Reward debt. See explanation below. // // We do some fancy math here. Basically, any point in time, the amount of WORLD tokens // entitled to a user but is pending to be distributed is: // // pending reward = (user.amount * pool.accWorldPerShare) - user.rewardDebt // // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: // 1. The pool's `accWorldPerShare` (and `lastRewardBlock`) gets updated. // 2. User receives the pending reward sent to his/her address. // 3. User's `amount` gets updated. // 4. User's `rewardDebt` gets updated. } // Info of each pool. struct PoolInfo { IERC20 lpToken; // Address of LP token contract. uint256 allocPoint; // How many allocation points assigned to this pool. WORLD tokens to distribute per block. uint256 lastRewardBlock; // Last block number that WORLD tokens distribution occurs. uint256 accWorldPerShare; // Accumulated WORLD tokens per share, times 1e12. See below. } WorldToken public immutable world; // The WORLD ERC-20 Token. uint256 private worldPerBlock; // WORLD tokens distributed per block. Use getWorldPerBlock() to get the updated reward. PoolInfo[] public poolInfo; // Info of each pool. mapping(uint256 => mapping(address => UserInfo)) public userInfo; // Info of each user that stakes LP tokens. uint256 public totalAllocPoint; // Total allocation points. Must be the sum of all allocation points in all pools. uint256 public startBlock; // The block number when WORLD token mining starts. uint256 public blockRewardUpdateCycle = 1 days; // The cycle in which the worldPerBlock gets updated. uint256 public blockRewardLastUpdateTime = block.timestamp; // The timestamp when the block worldPerBlock was last updated. uint256 public blocksPerDay = 6525; // The estimated number of mined blocks per day. uint256 public blockRewardPercentage = 1; // The percentage used for worldPerBlock calculation. mapping(address => bool) public addedLpTokens; // Used for preventing LP tokens from being added twice in add(). event Deposit(address indexed user, uint256 indexed pid, uint256 amount); event Withdraw(address indexed user, uint256 indexed pid, uint256 amount); event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount); constructor( WorldToken _world, uint256 _startBlock ) { require(address(_world) != address(0), "WORLD address is invalid"); require(_startBlock >= block.number, "startBlock is before current block"); world = _world; startBlock = _startBlock; } modifier updateWorldPerBlock() { (uint256 blockReward, bool update) = getWorldPerBlock(); if (update) { worldPerBlock = blockReward; blockRewardLastUpdateTime = block.timestamp; } _; } function getWorldPerBlock() public view returns (uint256, bool) { if (block.number < startBlock) { return (0, false); } uint256 poolReward = world.balanceOf(address(this)); if (poolReward == 0) { return (0, worldPerBlock != 0); } if (block.timestamp >= getWorldPerBlockUpdateTime() || worldPerBlock == 0) { return (poolReward.mul(blockRewardPercentage).div(100).div(blocksPerDay), true); } return (worldPerBlock, false); } function getWorldPerBlockUpdateTime() public view returns (uint256) { // if blockRewardUpdateCycle = 1 day then roundedUpdateTime = today's UTC midnight uint256 roundedUpdateTime = blockRewardLastUpdateTime - (blockRewardLastUpdateTime % blockRewardUpdateCycle); // if blockRewardUpdateCycle = 1 day then calculateRewardTime = tomorrow's UTC midnight uint256 calculateRewardTime = roundedUpdateTime + blockRewardUpdateCycle; return calculateRewardTime; } function poolLength() external view returns (uint256) { return poolInfo.length; } // Add a new lp to the pool. Can only be called by the owner. function add( uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate ) public onlyOwner { require(address(_lpToken) != address(0), "LP token is invalid"); require(!addedLpTokens[address(_lpToken)], "LP token is already added"); require(_allocPoint >= 5 && _allocPoint <= 10, "_allocPoint is outside of range 5-10"); if (_withUpdate) { massUpdatePools(); } uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock; totalAllocPoint = totalAllocPoint.add(_allocPoint); poolInfo.push(PoolInfo({ lpToken : _lpToken, allocPoint : _allocPoint, lastRewardBlock : lastRewardBlock, accWorldPerShare : 0 })); addedLpTokens[address(_lpToken)] = true; } // Update the given pool's WORLD token allocation point. Can only be called by the owner. function set( uint256 _pid, uint256 _allocPoint, bool _withUpdate ) public onlyOwner { require(_allocPoint >= 5 && _allocPoint <= 10, "_allocPoint is outside of range 5-10"); if (_withUpdate) { massUpdatePools(); } totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint); poolInfo[_pid].allocPoint = _allocPoint; } // View function to see pending WORLD tokens on frontend. function pendingRewards(uint256 _pid, address _user) external view returns (uint256) { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][_user]; uint256 accWorldPerShare = pool.accWorldPerShare; uint256 lpSupply = pool.lpToken.balanceOf(address(this)); if (block.number > pool.lastRewardBlock && lpSupply != 0) { uint256 multiplier = block.number.sub(pool.lastRewardBlock); (uint256 blockReward, ) = getWorldPerBlock(); uint256 worldReward = multiplier.mul(blockReward).mul(pool.allocPoint).div(totalAllocPoint); accWorldPerShare = accWorldPerShare.add(worldReward.mul(1e12).div(lpSupply)); } return user.amount.mul(accWorldPerShare).div(1e12).sub(user.rewardDebt); } // Update reward variables for all pools. Be careful of gas spending! function massUpdatePools() public { uint256 length = poolInfo.length; for (uint256 pid = 0; pid < length; ++pid) { updatePool(pid); } } // Update reward variables of the given pool to be up-to-date when lpSupply changes // For every deposit/withdraw pool recalculates accumulated token value function updatePool(uint256 _pid) public updateWorldPerBlock { PoolInfo storage pool = poolInfo[_pid]; if (block.number <= pool.lastRewardBlock) { return; } uint256 lpSupply = pool.lpToken.balanceOf(address(this)); if (lpSupply == 0) { pool.lastRewardBlock = block.number; return; } uint256 multiplier = block.number.sub(pool.lastRewardBlock); uint256 worldReward = multiplier.mul(worldPerBlock).mul(pool.allocPoint).div(totalAllocPoint); // no minting is required, the contract should have WORLD token balance pre-allocated // accumulated WORLD per share is stored multiplied by 10^12 to allow small 'fractional' values pool.accWorldPerShare = pool.accWorldPerShare.add(worldReward.mul(1e12).div(lpSupply)); pool.lastRewardBlock = block.number; } // Deposit LP tokens to WorldFarming for WORLD token allocation. function deposit(uint256 _pid, uint256 _amount) public { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][msg.sender]; updatePool(_pid); if (user.amount > 0) { uint256 pending = user.amount.mul(pool.accWorldPerShare).div(1e12).sub(user.rewardDebt); if (pending > 0) { safeTokenTransfer(msg.sender, pending); } } if (_amount > 0) { pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); user.amount = user.amount.add(_amount); } user.rewardDebt = user.amount.mul(pool.accWorldPerShare).div(1e12); emit Deposit(msg.sender, _pid, _amount); } // Withdraw LP tokens from WorldFarming function withdraw(uint256 _pid, uint256 _amount) public { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][msg.sender]; require(user.amount >= _amount, "Withdraw amount is greater than user amount"); updatePool(_pid); uint256 pending = user.amount.mul(pool.accWorldPerShare).div(1e12).sub(user.rewardDebt); if (pending > 0) { safeTokenTransfer(msg.sender, pending); } if (_amount > 0) { user.amount = user.amount.sub(_amount); pool.lpToken.safeTransfer(address(msg.sender), _amount); } user.rewardDebt = user.amount.mul(pool.accWorldPerShare).div(1e12); emit Withdraw(msg.sender, _pid, _amount); } // Withdraw without caring about rewards. EMERGENCY ONLY. function emergencyWithdraw(uint256 _pid) public { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][msg.sender]; user.amount = 0; user.rewardDebt = 0; pool.lpToken.safeTransfer(address(msg.sender), user.amount); emit EmergencyWithdraw(msg.sender, _pid, user.amount); } // Safe WORLD token transfer function, just in case if // rounding error causes pool to not have enough WORLD tokens function safeTokenTransfer(address _to, uint256 _amount) internal { uint256 balance = world.balanceOf(address(this)); uint256 amount = _amount > balance ? balance : _amount; world.transfer(_to, amount); } function setBlockRewardUpdateCycle(uint256 _blockRewardUpdateCycle) external onlyOwner { require(_blockRewardUpdateCycle > 0, "Value is zero"); blockRewardUpdateCycle = _blockRewardUpdateCycle; } // Just in case an adjustment is needed since mined blocks per day // changes constantly depending on the network function setBlocksPerDay(uint256 _blocksPerDay) external onlyOwner { require(_blocksPerDay >= 6200 && _blocksPerDay <= 7000, "Value is outside of range 6200-7000"); blocksPerDay = _blocksPerDay; } function setBlockRewardPercentage(uint256 _blockRewardPercentage) external onlyOwner { require(_blockRewardPercentage >= 1 && _blockRewardPercentage <= 5, "Value is outside of range 1-5"); blockRewardPercentage = _blockRewardPercentage; } }