diff --git a/contracts/token/DODOMine.sol b/contracts/token/DODOMine.sol new file mode 100644 index 0000000..61c00d4 --- /dev/null +++ b/contracts/token/DODOMine.sol @@ -0,0 +1,307 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {Ownable} from "../lib/Ownable.sol"; +import {DecimalMath} from "../lib/DecimalMath.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; +import {IERC20} from "../intf/IERC20.sol"; + + +contract DODOMine 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 DODOs + // entitled to a user but is pending to be distributed is: + // + // pending reward = (user.amount * pool.accDODOPerShare) - user.rewardDebt + // + // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: + // 1. The pool's `accDODOPerShare` (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 { + address lpToken; // Address of LP token contract. + uint256 allocPoint; // How many allocation points assigned to this pool. DODOs to distribute per block. + uint256 lastRewardBlock; // Last block number that DODOs distribution occurs. + uint256 accDODOPerShare; // Accumulated DODOs per share, times 1e12. See below. + } + + address public dodoToken; + uint256 public dodoPerBlock; + + // Info of each pool. + PoolInfo[] public poolInfos; + mapping(address => uint256) public lpTokenRegistry; + + // Info of each user that stakes LP tokens. + mapping(uint256 => mapping(address => UserInfo)) public userInfo; + mapping(address => uint256) public realizedReward; + + // Total allocation poitns. Must be the sum of all allocation points in all pools. + uint256 public totalAllocPoint = 0; + // The block number when DODO mining starts. + uint256 public startBlock; + + event Deposit(address indexed user, uint256 indexed pid, uint256 amount); + event Withdraw(address indexed user, uint256 indexed pid, uint256 amount); + event Claim(address indexed user, uint256 amount); + + constructor(address _dodoToken, uint256 _startBlock) public { + dodoToken = _dodoToken; + startBlock = _startBlock; + } + + // ============ Modifiers ============ + + modifier lpTokenExist(address lpToken) { + require(lpTokenRegistry[lpToken] > 0, "LP Token Not Exist"); + _; + } + + modifier lpTokenNotExist(address lpToken) { + require(lpTokenRegistry[lpToken] == 0, "LP Token Already Exist"); + _; + } + + // ============ Helper ============ + + function poolLength() external view returns (uint256) { + return poolInfos.length; + } + + function getPid(address _lpToken) public view lpTokenExist(_lpToken) returns (uint256) { + return lpTokenRegistry[_lpToken] - 1; + } + + function getUserLpBalance(address _lpToken, address _user) public view returns (uint256) { + uint256 pid = getPid(_lpToken); + return userInfo[pid][_user].amount; + } + + // ============ Ownable ============ + + function addLpToken( + uint256 _allocPoint, + address _lpToken, + bool _withUpdate + ) public lpTokenNotExist(_lpToken) onlyOwner { + if (_withUpdate) { + massUpdatePools(); + } + uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock; + totalAllocPoint = totalAllocPoint.add(_allocPoint); + poolInfos.push( + PoolInfo({ + lpToken: _lpToken, + allocPoint: _allocPoint, + lastRewardBlock: lastRewardBlock, + accDODOPerShare: 0 + }) + ); + lpTokenRegistry[_lpToken] = poolInfos.length; + } + + function setLpToken( + address _lpToken, + uint256 _allocPoint, + bool _withUpdate + ) public onlyOwner { + if (_withUpdate) { + massUpdatePools(); + } + uint256 pid = getPid(_lpToken); + totalAllocPoint = totalAllocPoint.sub(poolInfos[pid].allocPoint).add(_allocPoint); + poolInfos[pid].allocPoint = _allocPoint; + } + + function setReward(uint256 _dodoPerBlock) external onlyOwner { + dodoPerBlock = _dodoPerBlock; + } + + // ============ View Rewards ============ + + function getPendingReward(address _lpToken, address _user) external view returns (uint256) { + uint256 pid = getPid(_lpToken); + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][_user]; + uint256 accDODOPerShare = pool.accDODOPerShare; + uint256 lpSupply = IERC20(pool.lpToken).balanceOf(address(this)); + if (block.number > pool.lastRewardBlock && lpSupply != 0) { + uint256 DODOReward = block + .number + .sub(pool.lastRewardBlock) + .mul(dodoPerBlock) + .mul(pool.allocPoint) + .div(totalAllocPoint); + accDODOPerShare = accDODOPerShare.add(DecimalMath.divFloor(DODOReward, lpSupply)); + } + return DecimalMath.mul(user.amount, accDODOPerShare).sub(user.rewardDebt); + } + + function getAllPendingReward(address _user) external view returns (uint256) { + uint256 length = poolInfos.length; + uint256 totalReward = 0; + for (uint256 pid = 0; pid < length; ++pid) { + if (userInfo[pid][msg.sender].amount == 0) { + continue; // save gas + } + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][_user]; + uint256 accDODOPerShare = pool.accDODOPerShare; + uint256 lpSupply = IERC20(pool.lpToken).balanceOf(address(this)); + if (block.number > pool.lastRewardBlock && lpSupply != 0) { + uint256 DODOReward = block + .number + .sub(pool.lastRewardBlock) + .mul(dodoPerBlock) + .mul(pool.allocPoint) + .div(totalAllocPoint); + accDODOPerShare = accDODOPerShare.add(DecimalMath.divFloor(DODOReward, lpSupply)); + } + totalReward = totalReward.add( + DecimalMath.mul(user.amount, accDODOPerShare).sub(user.rewardDebt) + ); + } + return totalReward; + } + + function getRealizedReward(address _user) external view returns (uint256) { + return realizedReward[_user]; + } + + // ============ Update Pools ============ + + // Update reward vairables for all pools. Be careful of gas spending! + function massUpdatePools() public { + uint256 length = poolInfos.length; + for (uint256 pid = 0; pid < length; ++pid) { + updatePool(pid); + } + } + + // Update reward variables of the given pool to be up-to-date. + function updatePool(uint256 _pid) public { + PoolInfo storage pool = poolInfos[_pid]; + if (block.number <= pool.lastRewardBlock) { + return; + } + uint256 lpSupply = IERC20(pool.lpToken).balanceOf(address(this)); + if (lpSupply == 0) { + pool.lastRewardBlock = block.number; + return; + } + uint256 DODOReward = block + .number + .sub(pool.lastRewardBlock) + .mul(dodoPerBlock) + .mul(pool.allocPoint) + .div(totalAllocPoint); + pool.accDODOPerShare = pool.accDODOPerShare.add(DecimalMath.divFloor(DODOReward, lpSupply)); + pool.lastRewardBlock = block.number; + } + + // ============ Deposit & Withdraw & Claim ============ + // Deposit & withdraw will also trigger claim + + function deposit(address _lpToken, uint256 _amount) public { + uint256 pid = getPid(_lpToken); + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + updatePool(pid); + if (user.amount > 0) { + uint256 pending = DecimalMath.mul(user.amount, pool.accDODOPerShare).sub( + user.rewardDebt + ); + safeDODOTransfer(msg.sender, pending); + } + IERC20(pool.lpToken).safeTransferFrom(address(msg.sender), address(this), _amount); + user.amount = user.amount.add(_amount); + user.rewardDebt = DecimalMath.divFloor(user.amount, pool.accDODOPerShare); + emit Deposit(msg.sender, pid, _amount); + } + + // Withdraw LP tokens from MasterChef. + function withdraw(address _lpToken, uint256 _amount) public { + uint256 pid = getPid(_lpToken); + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + require(user.amount >= _amount, "withdraw: not good"); + updatePool(pid); + uint256 pending = DecimalMath.mul(user.amount, pool.accDODOPerShare).sub(user.rewardDebt); + safeDODOTransfer(msg.sender, pending); + user.amount = user.amount.sub(_amount); + user.rewardDebt = DecimalMath.divFloor(user.amount, pool.accDODOPerShare); + IERC20(pool.lpToken).safeTransfer(address(msg.sender), _amount); + emit Withdraw(msg.sender, pid, _amount); + } + + function withdrawAll(address _lpToken) public { + uint256 balance = getUserLpBalance(_lpToken, msg.sender); + withdraw(_lpToken, balance); + } + + // Withdraw without caring about rewards. EMERGENCY ONLY. + function emergencyWithdraw(address _lpToken) public { + uint256 pid = getPid(_lpToken); + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + IERC20(pool.lpToken).safeTransfer(address(msg.sender), user.amount); + user.amount = 0; + user.rewardDebt = 0; + } + + function claim(address _lpToken) public { + uint256 pid = getPid(_lpToken); + if (userInfo[pid][msg.sender].amount == 0) { + return; // save gas + } + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + updatePool(pid); + uint256 pending = DecimalMath.mul(user.amount, pool.accDODOPerShare).sub(user.rewardDebt); + user.rewardDebt = DecimalMath.divFloor(user.amount, pool.accDODOPerShare); + safeDODOTransfer(msg.sender, pending); + } + + function claimAll() public { + uint256 length = poolInfos.length; + uint256 pending = 0; + for (uint256 pid = 0; pid < length; ++pid) { + if (userInfo[pid][msg.sender].amount == 0) { + continue; // save gas + } + PoolInfo storage pool = poolInfos[pid]; + UserInfo storage user = userInfo[pid][msg.sender]; + updatePool(pid); + pending = pending.add( + DecimalMath.mul(user.amount, pool.accDODOPerShare).sub(user.rewardDebt) + ); + user.rewardDebt = DecimalMath.divFloor(user.amount, pool.accDODOPerShare); + } + safeDODOTransfer(msg.sender, pending); + } + + // Safe DODO transfer function, just in case if rounding error causes pool to not have enough DODOs. + function safeDODOTransfer(address _to, uint256 _amount) internal { + IERC20(dodoToken).safeTransfer(_to, _amount); + realizedReward[_to] = realizedReward[_to].add(_amount); + } +} diff --git a/contracts/token/DODOToken.sol b/contracts/token/DODOToken.sol new file mode 100644 index 0000000..672611f --- /dev/null +++ b/contracts/token/DODOToken.sol @@ -0,0 +1,106 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../lib/SafeMath.sol"; + + +/** + * @title DODO Token + * @author DODO Breeder + */ +contract DODOToken { + using SafeMath for uint256; + + string public symbol = "DODO"; + string public name = "DODO bird food"; + + uint256 public decimals = 18; + uint256 public totalSupply = 1000000000 * 10**18; // 1 Billion + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + + // ============ Events ============ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + // ============ Functions ============ + + constructor() public { + balances[msg.sender] = totalSupply; + } + + /** + * @dev transfer token for a specified address + * @param to The address to transfer to. + * @param amount The amount to be transferred. + */ + function transfer(address to, uint256 amount) public returns (bool) { + require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); + + balances[msg.sender] = balances[msg.sender].sub(amount); + balances[to] = balances[to].add(amount); + emit Transfer(msg.sender, to, amount); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param owner The address to query the the balance of. + * @return balance An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address owner) external view returns (uint256 balance) { + return balances[owner]; + } + + /** + * @dev Transfer tokens from one address to another + * @param from address The address which you want to send tokens from + * @param to address The address which you want to transfer to + * @param amount uint256 the amount of tokens to be transferred + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public returns (bool) { + require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); + require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); + + balances[from] = balances[from].sub(amount); + balances[to] = balances[to].add(amount); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); + emit Transfer(from, to, amount); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * @param spender The address which will spend the funds. + * @param amount The amount of tokens to be spent. + */ + function approve(address spender, uint256 amount) public returns (bool) { + allowed[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param owner address The address which owns the funds. + * @param spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance(address owner, address spender) public view returns (uint256) { + return allowed[owner][spender]; + } +} diff --git a/contracts/token/LockedTokenVault.sol b/contracts/token/LockedTokenVault.sol new file mode 100644 index 0000000..c25c868 --- /dev/null +++ b/contracts/token/LockedTokenVault.sol @@ -0,0 +1,192 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../lib/SafeMath.sol"; +import {DecimalMath} from "../lib/DecimalMath.sol"; +import {Ownable} from "../lib/Ownable.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {IERC20} from "../intf/IERC20.sol"; + + +/** + * @title LockedTokenVault + * @author DODO Breeder + * + * @notice Lock Token and release it linearly + */ + +contract LockedTokenVault is Ownable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + address _TOKEN_; + + mapping(address => uint256) internal originBalances; + mapping(address => uint256) internal remainingBalances; + + mapping(address => bool) internal confirmOriginBalance; + mapping(address => address) internal holderTransferRequest; + + uint256 public _START_RELEASE_TIME_; + uint256 public _RELEASE_DURATION_; + + // ============ Modifiers ============ + + modifier beforeStartRelease() { + require(block.timestamp < _START_RELEASE_TIME_, "RELEASE START"); + _; + } + + modifier afterStartRelease() { + require(block.timestamp > _START_RELEASE_TIME_, "RELEASE NOT START"); + _; + } + + modifier holderConfirmed(address holder) { + require(confirmOriginBalance[holder], "HOLDER NOT CONFIRMED"); + _; + } + + modifier holderNotConfirmed(address holder) { + require(!confirmOriginBalance[holder], "HOLDER CONFIRMED"); + _; + } + + // ============ Init Functions ============ + + constructor( + address _token, + uint256 _startReleaseTime, + uint256 _releaseDuration + ) public { + _TOKEN_ = _token; + _START_RELEASE_TIME_ = _startReleaseTime; + _RELEASE_DURATION_ = _releaseDuration; + } + + function deposit(uint256 amount) external onlyOwner beforeStartRelease { + _tokenTransferIn(_OWNER_, amount); + originBalances[_OWNER_] = originBalances[_OWNER_].add(amount); + remainingBalances[_OWNER_] = remainingBalances[_OWNER_].add(amount); + } + + function withdraw(uint256 amount) external onlyOwner beforeStartRelease { + originBalances[_OWNER_] = originBalances[_OWNER_].sub(amount); + remainingBalances[_OWNER_] = remainingBalances[_OWNER_].sub(amount); + _tokenTransferOut(_OWNER_, amount); + } + + // ============ For Owner ============ + + function grant(address holder, uint256 amount) + external + onlyOwner + beforeStartRelease + holderNotConfirmed(holder) + { + originBalances[holder] = originBalances[holder].add(amount); + remainingBalances[holder] = remainingBalances[holder].add(amount); + + originBalances[_OWNER_] = originBalances[_OWNER_].sub(amount); + remainingBalances[_OWNER_] = remainingBalances[_OWNER_].sub(amount); + } + + function recall(address holder) + external + onlyOwner + beforeStartRelease + holderNotConfirmed(holder) + { + uint256 amount = originBalances[holder]; + + originBalances[holder] = 0; + remainingBalances[holder] = 0; + + originBalances[_OWNER_] = originBalances[_OWNER_].add(amount); + remainingBalances[_OWNER_] = remainingBalances[_OWNER_].add(amount); + } + + function executeHolderTransfer(address holder) external onlyOwner { + address newHolder = holderTransferRequest[holder]; + require(newHolder != address(0), "INVALID NEW HOLDER"); + require(originBalances[newHolder] == 0, "NOT NEW HOLDER"); + + originBalances[newHolder] = originBalances[holder]; + remainingBalances[newHolder] = remainingBalances[holder]; + + originBalances[holder] = 0; + remainingBalances[holder] = 0; + + holderTransferRequest[holder] = address(0); + } + + // ============ For Holder ============ + + function confirm() external { + confirmOriginBalance[msg.sender] = true; + } + + function cancelConfirm() external { + confirmOriginBalance[msg.sender] = false; + } + + function requestTransfer(address newHolder) external holderConfirmed(msg.sender) { + require(originBalances[newHolder] == 0, "NOT NEW HOLDER"); + holderTransferRequest[msg.sender] = newHolder; + } + + function claimToken() external afterStartRelease { + uint256 unLocked = getUnlockedBalance(msg.sender); + + _tokenTransferOut(msg.sender, unLocked); + remainingBalances[msg.sender] = remainingBalances[msg.sender].sub(unLocked); + } + + // ============ View ============ + + function getOriginBalance(address holder) external view returns (uint256) { + return originBalances[holder]; + } + + function getRemainingBalance(address holder) external view returns (uint256) { + return remainingBalances[holder]; + } + + function isConfirmed(address holder) external view returns (bool) { + return confirmOriginBalance[holder]; + } + + function getHolderTransferRequest(address holder) external view returns (address) { + return holderTransferRequest[holder]; + } + + function getUnlockedBalance(address holder) public view returns (uint256) { + if (block.timestamp < _START_RELEASE_TIME_) { + return 0; + } + uint256 newRemaining = 0; + uint256 timePast = block.timestamp.sub(_START_RELEASE_TIME_); + if (timePast < _RELEASE_DURATION_) { + uint256 remainingTime = _RELEASE_DURATION_.sub(timePast); + newRemaining = originBalances[holder].mul(remainingTime).div(_RELEASE_DURATION_); + } + return remainingBalances[msg.sender].sub(newRemaining); + } + + // ============ Internal Helper ============ + + function _tokenTransferIn(address from, uint256 amount) internal { + IERC20(_TOKEN_).safeTransferFrom(from, address(this), amount); + } + + function _tokenTransferOut(address to, uint256 amount) internal { + IERC20(_TOKEN_).safeTransfer(to, amount); + } +}