/* Copyright 2020 DODO ZOO. SPDX-License-Identifier: Apache-2.0 */ pragma solidity 0.6.9; import {SafeMath} from "../lib/SafeMath.sol"; import {DecimalMath} from "../lib/DecimalMath.sol"; import {IERC20} from "../intf/IERC20.sol"; import {SafeERC20} from "../lib/SafeERC20.sol"; import {Ownable} from "../lib/Ownable.sol"; contract FeeDistributor { using SafeMath for uint256; using SafeERC20 for IERC20; // ============ Storage ============ address public _BASE_TOKEN_; address public _QUOTE_TOKEN_; uint256 public _BASE_RESERVE_; uint256 public _QUOTE_RESERVE_; address public _STAKE_VAULT_; address public _STAKE_TOKEN_; uint256 public _STAKE_RESERVE_; uint256 public _BASE_REWARD_RATIO_; mapping(address => uint256) internal _USER_BASE_REWARDS_; mapping(address => uint256) internal _USER_BASE_PER_SHARE_; uint256 public _QUOTE_REWARD_RATIO_; mapping(address => uint256) internal _USER_QUOTE_REWARDS_; mapping(address => uint256) internal _USER_QUOTE_PER_SHARE_; mapping(address => uint256) internal _SHARES_; bool internal _FEE_INITIALIZED_; // ============ Event ============ event Stake(address sender, uint256 amount); event UnStake(address sender, uint256 amount); event Claim(address sender, uint256 baseAmount, uint256 quoteAmount); function init( address baseToken, address quoteToken, address stakeToken ) external { require(!_FEE_INITIALIZED_, "ALREADY_INITIALIZED"); _FEE_INITIALIZED_ = true; _BASE_TOKEN_ = baseToken; _QUOTE_TOKEN_ = quoteToken; _STAKE_TOKEN_ = stakeToken; _STAKE_VAULT_ = address(new StakeVault()); } function stake(address to) external { _updateGlobalState(); _updateUserReward(to); uint256 stakeVault = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_); uint256 stakeInput = stakeVault.sub(_STAKE_RESERVE_); _addShares(stakeInput, to); emit Stake(to, stakeInput); } function claim(address to) external { _updateGlobalState(); _updateUserReward(msg.sender); _claim(msg.sender, to); } function unstake(uint256 amount, address to, bool withClaim) external { require(_SHARES_[msg.sender]>=amount, "STAKE BALANCE ONT ENOUGH"); _updateGlobalState(); _updateUserReward(msg.sender); if (withClaim) { _claim(msg.sender, to); } _removeShares(amount, msg.sender); StakeVault(_STAKE_VAULT_).transferOut(_STAKE_TOKEN_, amount, to); emit UnStake(msg.sender, amount); } // ============ Internal ============ function _claim(address sender, address to) internal { uint256 allBase = _USER_BASE_REWARDS_[sender]; uint256 allQuote = _USER_QUOTE_REWARDS_[sender]; IERC20(_BASE_TOKEN_).safeTransfer(to, allBase); IERC20(_QUOTE_TOKEN_).safeTransfer(to, allQuote); _USER_BASE_REWARDS_[sender] = 0; _USER_BASE_REWARDS_[sender] = 0; emit Claim(sender, allBase, allQuote); } function _updateGlobalState() internal { uint256 baseInput = IERC20(_BASE_TOKEN_).balanceOf(address(this)).sub(_BASE_RESERVE_); uint256 quoteInput = IERC20(_QUOTE_TOKEN_).balanceOf(address(this)).sub(_QUOTE_RESERVE_); _BASE_REWARD_RATIO_ = _BASE_REWARD_RATIO_.add(DecimalMath.divFloor(baseInput, _STAKE_RESERVE_)); _QUOTE_REWARD_RATIO_ = _QUOTE_REWARD_RATIO_.add(DecimalMath.divFloor(quoteInput, _STAKE_RESERVE_)); _BASE_RESERVE_ = _BASE_RESERVE_.add(baseInput); _QUOTE_RESERVE_ = _QUOTE_RESERVE_.add(quoteInput); } function _updateUserReward(address user) internal { _USER_BASE_REWARDS_[user] = DecimalMath.mulFloor( _SHARES_[user], _BASE_REWARD_RATIO_.sub(_USER_BASE_PER_SHARE_[user]) ).add(_USER_BASE_REWARDS_[user]); _USER_BASE_PER_SHARE_[user] = _BASE_REWARD_RATIO_; _USER_QUOTE_REWARDS_[user] = DecimalMath.mulFloor( _SHARES_[user], _QUOTE_REWARD_RATIO_.sub(_USER_QUOTE_PER_SHARE_[user]) ).add(_USER_QUOTE_REWARDS_[user]); _USER_QUOTE_PER_SHARE_[user] = _QUOTE_REWARD_RATIO_; } function _addShares(uint256 amount, address to) internal { _SHARES_[to] = _SHARES_[to].add(amount); _STAKE_RESERVE_ = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_); } function _removeShares(uint256 amount, address from) internal { _SHARES_[from] = _SHARES_[from].sub(amount); _STAKE_RESERVE_ = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_); } } contract StakeVault is Ownable { using SafeERC20 for IERC20; function transferOut( address token, uint256 amount, address to ) external onlyOwner { IERC20(token).safeTransfer(to, amount); } }