Sonic Blaze Testnet
    /

    Contract Diff Checker

    Contract Name:
    MultiFeeDistribution

    Contract Source Code:

    // SPDX-License-Identifier: agpl-3.0
    pragma solidity 0.7.6;
    
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
      /**
       * @dev Returns true if `account` is a contract.
       *
       * [IMPORTANT]
       * ====
       * It is unsafe to assume that an address for which this function returns
       * false is an externally-owned account (EOA) and not a contract.
       *
       * Among others, `isContract` will return false for the following
       * types of addresses:
       *
       *  - an externally-owned account
       *  - a contract in construction
       *  - an address where a contract will be created
       *  - an address where a contract lived, but was destroyed
       * ====
       */
      function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly {
          codehash := extcodehash(account)
        }
        return (codehash != accountHash && codehash != 0x0);
      }
    
      /**
       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
       * `recipient`, forwarding all available gas and reverting on errors.
       *
       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
       * of certain opcodes, possibly making contracts go over the 2300 gas limit
       * imposed by `transfer`, making them unable to receive funds via
       * `transfer`. {sendValue} removes this limitation.
       *
       * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
       *
       * IMPORTANT: because control is transferred to `recipient`, care must be
       * taken to not create reentrancy vulnerabilities. Consider using
       * {ReentrancyGuard} or the
       * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
       */
      function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, 'Address: insufficient balance');
    
        // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
        (bool success, ) = recipient.call{value: amount}('');
        require(success, 'Address: unable to send value, recipient may have reverted');
      }
    }

    // SPDX-License-Identifier: MIT
    pragma solidity 0.7.6;
    
    /*
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with GSN meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
      function _msgSender() internal view virtual returns (address payable) {
        return msg.sender;
      }
    
      function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
      }
    }

    // SPDX-License-Identifier: agpl-3.0
    pragma solidity 0.7.6;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
      /**
       * @dev Returns the amount of tokens in existence.
       */
      function totalSupply() external view returns (uint256);
    
      /**
       * @dev Returns the amount of tokens owned by `account`.
       */
      function balanceOf(address account) external view returns (uint256);
    
      /**
       * @dev Moves `amount` tokens from the caller's account to `recipient`.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a {Transfer} event.
       */
      function transfer(address recipient, uint256 amount) external returns (bool);
    
      /**
       * @dev Returns the remaining number of tokens that `spender` will be
       * allowed to spend on behalf of `owner` through {transferFrom}. This is
       * zero by default.
       *
       * This value changes when {approve} or {transferFrom} are called.
       */
      function allowance(address owner, address spender) external view returns (uint256);
    
      /**
       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * IMPORTANT: Beware that changing an allowance with this method brings the risk
       * that someone may use both the old and the new allowance by unfortunate
       * transaction ordering. One possible solution to mitigate this race
       * condition is to first reduce the spender's allowance to 0 and set the
       * desired value afterwards:
       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
       *
       * Emits an {Approval} event.
       */
      function approve(address spender, uint256 amount) external returns (bool);
    
      /**
       * @dev Moves `amount` tokens from `sender` to `recipient` using the
       * allowance mechanism. `amount` is then deducted from the caller's
       * allowance.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a {Transfer} event.
       */
      function transferFrom(
        address sender,
        address recipient,
        uint256 amount
      ) external returns (bool);
    
      /**
       * @dev Emitted when `value` tokens are moved from one account (`from`) to
       * another (`to`).
       *
       * Note that `value` may be zero.
       */
      event Transfer(address indexed from, address indexed to, uint256 value);
    
      /**
       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
       * a call to {approve}. `value` is the new allowance.
       */
      event Approval(address indexed owner, address indexed spender, uint256 value);
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.7.6;
    
    import './Context.sol';
    
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    contract Ownable is Context {
      address private _owner;
    
      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
      /**
       * @dev Initializes the contract setting the deployer as the initial owner.
       */
      constructor() {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
      }
    
      /**
       * @dev Returns the address of the current owner.
       */
      function owner() public view returns (address) {
        return _owner;
      }
    
      /**
       * @dev Throws if called by any account other than the owner.
       */
      modifier onlyOwner() {
        require(_owner == _msgSender(), 'Ownable: caller is not the owner');
        _;
      }
    
      /**
       * @dev Leaves the contract without owner. It will not be possible to call
       * `onlyOwner` functions anymore. Can only be called by the current owner.
       *
       * NOTE: Renouncing ownership will leave the contract without an owner,
       * thereby removing any functionality that is only available to the owner.
       */
      function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
      }
    
      /**
       * @dev Transfers ownership of the contract to a new account (`newOwner`).
       * Can only be called by the current owner.
       */
      function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), 'Ownable: new owner is the zero address');
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
      }
    }

    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.7.6;
    
    import {IERC20} from './IERC20.sol';
    import {SafeMath} from './SafeMath.sol';
    import {Address} from './Address.sol';
    
    /**
     * @title SafeERC20
     * @dev Wrappers around ERC20 operations that throw on failure (when the token
     * contract returns false). Tokens that return no value (and instead revert or
     * throw on failure) are also supported, non-reverting calls are assumed to be
     * successful.
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
     */
    library SafeERC20 {
      using SafeMath for uint256;
      using Address for address;
    
      function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
      ) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
      }
    
      function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
      ) internal {
        callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
      }
    
      function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
      ) internal {
        require(
          (value == 0) || (token.allowance(address(this), spender) == 0),
          'SafeERC20: approve from non-zero to non-zero allowance'
        );
        callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
      }
    
      function callOptionalReturn(IERC20 token, bytes memory data) private {
        require(address(token).isContract(), 'SafeERC20: call to non-contract');
    
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = address(token).call(data);
        require(success, 'SafeERC20: low-level call failed');
    
        if (returndata.length > 0) {
          // Return data is optional
          // solhint-disable-next-line max-line-length
          require(abi.decode(returndata, (bool)), 'SafeERC20: ERC20 operation did not succeed');
        }
      }
    }

    // SPDX-License-Identifier: agpl-3.0
    pragma solidity 0.7.6;
    
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
      /**
       * @dev Returns the addition of two unsigned integers, reverting on
       * overflow.
       *
       * Counterpart to Solidity's `+` operator.
       *
       * Requirements:
       * - Addition cannot overflow.
       */
      function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, 'SafeMath: addition overflow');
    
        return c;
      }
    
      /**
       * @dev Returns the subtraction of two unsigned integers, reverting on
       * overflow (when the result is negative).
       *
       * Counterpart to Solidity's `-` operator.
       *
       * Requirements:
       * - Subtraction cannot overflow.
       */
      function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, 'SafeMath: subtraction overflow');
      }
    
      /**
       * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
       * overflow (when the result is negative).
       *
       * Counterpart to Solidity's `-` operator.
       *
       * Requirements:
       * - Subtraction cannot overflow.
       */
      function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
      ) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
    
        return c;
      }
    
      /**
       * @dev Returns the multiplication of two unsigned integers, reverting on
       * overflow.
       *
       * Counterpart to Solidity's `*` operator.
       *
       * Requirements:
       * - Multiplication cannot overflow.
       */
      function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
          return 0;
        }
    
        uint256 c = a * b;
        require(c / a == b, 'SafeMath: multiplication overflow');
    
        return c;
      }
    
      /**
       * @dev Returns the integer division of two unsigned integers. Reverts on
       * division by zero. The result is rounded towards zero.
       *
       * Counterpart to Solidity's `/` operator. Note: this function uses a
       * `revert` opcode (which leaves remaining gas untouched) while Solidity
       * uses an invalid opcode to revert (consuming all remaining gas).
       *
       * Requirements:
       * - The divisor cannot be zero.
       */
      function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, 'SafeMath: division by zero');
      }
    
      /**
       * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
       * division by zero. The result is rounded towards zero.
       *
       * Counterpart to Solidity's `/` operator. Note: this function uses a
       * `revert` opcode (which leaves remaining gas untouched) while Solidity
       * uses an invalid opcode to revert (consuming all remaining gas).
       *
       * Requirements:
       * - The divisor cannot be zero.
       */
      function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
      ) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
        return c;
      }
    
      /**
       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
       * Reverts when dividing by zero.
       *
       * Counterpart to Solidity's `%` operator. This function uses a `revert`
       * opcode (which leaves remaining gas untouched) while Solidity uses an
       * invalid opcode to revert (consuming all remaining gas).
       *
       * Requirements:
       * - The divisor cannot be zero.
       */
      function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, 'SafeMath: modulo by zero');
      }
    
      /**
       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
       * Reverts with custom message when dividing by zero.
       *
       * Counterpart to Solidity's `%` operator. This function uses a `revert`
       * opcode (which leaves remaining gas untouched) while Solidity uses an
       * invalid opcode to revert (consuming all remaining gas).
       *
       * Requirements:
       * - The divisor cannot be zero.
       */
      function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
      ) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
      }
    }

    // SPDX-License-Identifier: agpl-3.0
    pragma solidity 0.7.6;
    pragma experimental ABIEncoderV2;
    
    interface IChefIncentivesController {
    
      /**
       * @dev Called by the corresponding asset on any update that affects the rewards distribution
       * @param user The address of the user
       * @param userBalance The balance of the user of the asset in the lending pool
       * @param totalSupply The total supply of the asset in the lending pool
       **/
      function handleAction(
        address user,
        uint256 userBalance,
        uint256 totalSupply
      ) external;
    
        function addPool(address _token, uint256 _allocPoint) external;
    
        function claim(address _user, address[] calldata _tokens) external;
    
        function setClaimReceiver(address _user, address _receiver) external;
    
    }

    pragma solidity 0.7.6;
    
    interface IMultiFeeDistribution {
    
        function addReward(address rewardsToken) external;
        function mint(address user, uint256 amount, bool withPenalty) external;
    
    }

    pragma solidity 0.7.6;
    pragma abicoder v2;
    
    import "../interfaces/IChefIncentivesController.sol";
    import "../interfaces/IMultiFeeDistribution.sol";
    import "../dependencies/openzeppelin/contracts/IERC20.sol";
    import "../dependencies/openzeppelin/contracts/SafeERC20.sol";
    import "../dependencies/openzeppelin/contracts/SafeMath.sol";
    import "../dependencies/openzeppelin/contracts/Ownable.sol";
    
    
    interface IMintableToken is IERC20 {
        function mint(address _receiver, uint256 _amount) external returns (bool);
        function setMinter(address _minter) external returns (bool);
    }
    
    // Based on Ellipsis EPS Staker
    // https://github.com/ellipsis-finance/ellipsis/blob/master/contracts/EpsStaker.sol
    contract MultiFeeDistribution is IMultiFeeDistribution, Ownable {
    
        using SafeMath for uint256;
        using SafeERC20 for IERC20;
        using SafeERC20 for IMintableToken;
    
        /* ========== STATE VARIABLES ========== */
    
        struct Reward {
            uint256 periodFinish;
            uint256 rewardRate;
            uint256 lastUpdateTime;
            uint256 rewardPerTokenStored;
            // tracks already-added balances to handle accrued interest in aToken rewards
            // for the stakingToken this value is unused and will always be 0
            uint256 balance;
        }
        struct Balances {
            uint256 total;
            uint256 unlocked;
            uint256 locked;
            uint256 earned;
        }
        struct LockedBalance {
            uint256 amount;
            uint256 unlockTime;
        }
        struct RewardData {
            address token;
            uint256 amount;
        }
    
        IChefIncentivesController public incentivesController;
        IMintableToken public immutable stakingToken;
        address[] public rewardTokens;
        mapping(address => Reward) public rewardData;
    
        // Duration that rewards are streamed over
        uint256 public constant rewardsDuration = 86400 * 7;
    
        // Duration of lock/earned penalty period
        uint256 public constant lockDuration = rewardsDuration * 13;
    
        // Addresses approved to call mint
        mapping(address => bool) public minters;
        bool public mintersAreSet;
    
        // user -> reward token -> amount
        mapping(address => mapping(address => uint256)) public userRewardPerTokenPaid;
        mapping(address => mapping(address => uint256)) public rewards;
    
        uint256 public totalSupply;
        uint256 public lockedSupply;
    
        // Private mappings for balance data
        mapping(address => Balances) private balances;
        mapping(address => LockedBalance[]) private userLocks;
        mapping(address => LockedBalance[]) private userEarnings;
    
        /* ========== CONSTRUCTOR ========== */
    
        constructor(address _stakingToken) Ownable() {
            stakingToken = IMintableToken(_stakingToken);
            IMintableToken(_stakingToken).setMinter(address(this));
            // First reward MUST be the staking token or things will break
            // related to the 50% penalty and distribution to locked balances
            rewardTokens.push(_stakingToken);
            rewardData[_stakingToken].lastUpdateTime = block.timestamp;
        }
    
        /* ========== ADMIN CONFIGURATION ========== */
    
        function setMinters(address[] memory _minters) external onlyOwner {
            require(!mintersAreSet);
            for (uint i; i < _minters.length; i++) {
                minters[_minters[i]] = true;
            }
            mintersAreSet = true;
        }
    
        function setIncentivesController(IChefIncentivesController _controller) external onlyOwner {
            incentivesController = _controller;
        }
    
        // Add a new reward token to be distributed to stakers
        function addReward(address _rewardsToken) external override onlyOwner {
            require(rewardData[_rewardsToken].lastUpdateTime == 0);
            rewardTokens.push(_rewardsToken);
            rewardData[_rewardsToken].lastUpdateTime = block.timestamp;
            rewardData[_rewardsToken].periodFinish = block.timestamp;
        }
    
        /* ========== VIEWS ========== */
    
        function _rewardPerToken(address _rewardsToken, uint256 _supply) internal view returns (uint256) {
            if (_supply == 0) {
                return rewardData[_rewardsToken].rewardPerTokenStored;
            }
            return
                rewardData[_rewardsToken].rewardPerTokenStored.add(
                    lastTimeRewardApplicable(_rewardsToken).sub(
                        rewardData[_rewardsToken].lastUpdateTime).mul(
                            rewardData[_rewardsToken].rewardRate).mul(1e18).div(_supply)
                );
        }
    
        function _earned(
            address _user,
            address _rewardsToken,
            uint256 _balance,
            uint256 _currentRewardPerToken
        ) internal view returns (uint256) {
            return _balance.mul(
                _currentRewardPerToken.sub(userRewardPerTokenPaid[_user][_rewardsToken])
            ).div(1e18).add(rewards[_user][_rewardsToken]);
        }
    
        function lastTimeRewardApplicable(address _rewardsToken) public view returns (uint256) {
            uint periodFinish = rewardData[_rewardsToken].periodFinish;
            return block.timestamp < periodFinish ? block.timestamp : periodFinish;
        }
    
        function rewardPerToken(address _rewardsToken) external view returns (uint256) {
            uint256 supply = _rewardsToken == address(stakingToken) ? lockedSupply : totalSupply;
            return _rewardPerToken(_rewardsToken, supply);
    
        }
    
        function getRewardForDuration(address _rewardsToken) external view returns (uint256) {
            return rewardData[_rewardsToken].rewardRate.mul(rewardsDuration).div(1e12);
        }
    
        // Address and claimable amount of all reward tokens for the given account
        function claimableRewards(address account) external view returns (RewardData[] memory rewards) {
            rewards = new RewardData[](rewardTokens.length);
            for (uint256 i = 0; i < rewards.length; i++) {
                // If i == 0 this is the stakingReward, distribution is based on locked balances
                uint256 balance = i == 0 ? balances[account].locked : balances[account].total;
                uint256 supply = i == 0 ? lockedSupply : totalSupply;
                rewards[i].token = rewardTokens[i];
                rewards[i].amount = _earned(account, rewards[i].token, balance, _rewardPerToken(rewardTokens[i], supply)).div(1e12);
            }
            return rewards;
        }
    
        // Total balance of an account, including unlocked, locked and earned tokens
        function totalBalance(address user) view external returns (uint256 amount) {
            return balances[user].total;
        }
    
        // Total withdrawable balance for an account to which no penalty is applied
        function unlockedBalance(address user) view external returns (uint256 amount) {
            amount = balances[user].unlocked;
            LockedBalance[] storage earnings = userEarnings[msg.sender];
            for (uint i = 0; i < earnings.length; i++) {
                if (earnings[i].unlockTime > block.timestamp) {
                    break;
                }
                amount = amount.add(earnings[i].amount);
            }
            return amount;
        }
    
        // Information on the "earned" balances of a user
        // Earned balances may be withdrawn immediately for a 50% penalty
        function earnedBalances(
            address user
        ) view external returns (
            uint256 total,
            LockedBalance[] memory earningsData
        ) {
            LockedBalance[] storage earnings = userEarnings[user];
            uint256 idx;
            for (uint i = 0; i < earnings.length; i++) {
                if (earnings[i].unlockTime > block.timestamp) {
                    if (idx == 0) {
                        earningsData = new LockedBalance[](earnings.length - i);
                    }
                    earningsData[idx] = earnings[i];
                    idx++;
                    total = total.add(earnings[i].amount);
                }
            }
            return (total, earningsData);
        }
    
        // Information on a user's locked balances
        function lockedBalances(
            address user
        ) view external returns (
            uint256 total,
            uint256 unlockable,
            uint256 locked,
            LockedBalance[] memory lockData
        ) {
            LockedBalance[] storage locks = userLocks[user];
            uint256 idx;
            for (uint i = 0; i < locks.length; i++) {
                if (locks[i].unlockTime > block.timestamp) {
                    if (idx == 0) {
                        lockData = new LockedBalance[](locks.length - i);
                    }
                    lockData[idx] = locks[i];
                    idx++;
                    locked = locked.add(locks[i].amount);
                } else {
                    unlockable = unlockable.add(locks[i].amount);
                }
            }
            return (balances[user].locked, unlockable, locked, lockData);
        }
    
        // Final balance received and penalty balance paid by user upon calling exit
        function withdrawableBalance(
            address user
        ) view public returns (
            uint256 amount,
            uint256 penaltyAmount
        ) {
            Balances storage bal = balances[user];
            uint256 earned = bal.earned;
            if (earned > 0) {
                uint256 amountWithoutPenalty;
                uint256 length = userEarnings[user].length;
                for (uint i = 0; i < length; i++) {
                    uint256 earnedAmount = userEarnings[user][i].amount;
                    if (earnedAmount == 0) continue;
                    if (userEarnings[user][i].unlockTime > block.timestamp) {
                        break;
                    }
                    amountWithoutPenalty = amountWithoutPenalty.add(earnedAmount);
                }
    
                penaltyAmount = earned.sub(amountWithoutPenalty).div(2);
            }
            amount = bal.unlocked.add(earned).sub(penaltyAmount);
            return (amount, penaltyAmount);
        }
    
        /* ========== MUTATIVE FUNCTIONS ========== */
    
        // Stake tokens to receive rewards
        // Locked tokens cannot be withdrawn for lockDuration and are eligible to receive stakingReward rewards
        function stake(uint256 amount, bool lock) external {
            require(amount > 0, "Cannot stake 0");
            _updateReward(msg.sender);
            totalSupply = totalSupply.add(amount);
            Balances storage bal = balances[msg.sender];
            bal.total = bal.total.add(amount);
            if (lock) {
                lockedSupply = lockedSupply.add(amount);
                bal.locked = bal.locked.add(amount);
                uint256 unlockTime = block.timestamp.div(rewardsDuration).mul(rewardsDuration).add(lockDuration);
                uint256 idx = userLocks[msg.sender].length;
                if (idx == 0 || userLocks[msg.sender][idx-1].unlockTime < unlockTime) {
                    userLocks[msg.sender].push(LockedBalance({amount: amount, unlockTime: unlockTime}));
                } else {
                    userLocks[msg.sender][idx-1].amount = userLocks[msg.sender][idx-1].amount.add(amount);
                }
            } else {
                bal.unlocked = bal.unlocked.add(amount);
            }
            stakingToken.safeTransferFrom(msg.sender, address(this), amount);
            emit Staked(msg.sender, amount, lock);
        }
    
        // Mint new tokens
        // Minted tokens receive rewards normally but incur a 50% penalty when
        // withdrawn before lockDuration has passed.
        function mint(address user, uint256 amount, bool withPenalty) external override {
            require(minters[msg.sender]);
            if (amount == 0) return;
            _updateReward(user);
            stakingToken.mint(address(this), amount);
            if (user == address(this)) {
                // minting to this contract adds the new tokens as incentives for lockers
                _notifyReward(address(stakingToken), amount);
                return;
            }
            totalSupply = totalSupply.add(amount);
            Balances storage bal = balances[user];
            bal.total = bal.total.add(amount);
            if (withPenalty) {
                bal.earned = bal.earned.add(amount);
                uint256 unlockTime = block.timestamp.div(rewardsDuration).mul(rewardsDuration).add(lockDuration);
                LockedBalance[] storage earnings = userEarnings[user];
                uint256 idx = earnings.length;
                if (idx == 0 || earnings[idx-1].unlockTime < unlockTime) {
                    earnings.push(LockedBalance({amount: amount, unlockTime: unlockTime}));
                } else {
                    earnings[idx-1].amount = earnings[idx-1].amount.add(amount);
                }
            } else {
                bal.unlocked = bal.unlocked.add(amount);
            }
            emit Staked(user, amount, false);
        }
    
        // Withdraw staked tokens
        // First withdraws unlocked tokens, then earned tokens. Withdrawing earned tokens
        // incurs a 50% penalty which is distributed based on locked balances.
        function withdraw(uint256 amount) public {
            require(amount > 0, "Cannot withdraw 0");
            _updateReward(msg.sender);
            Balances storage bal = balances[msg.sender];
            uint256 penaltyAmount;
    
            if (amount <= bal.unlocked) {
                bal.unlocked = bal.unlocked.sub(amount);
            } else {
                uint256 remaining = amount.sub(bal.unlocked);
                require(bal.earned >= remaining, "Insufficient unlocked balance");
                bal.unlocked = 0;
                bal.earned = bal.earned.sub(remaining);
                for (uint i = 0; ; i++) {
                    uint256 earnedAmount = userEarnings[msg.sender][i].amount;
                    if (earnedAmount == 0) continue;
                    if (penaltyAmount == 0 && userEarnings[msg.sender][i].unlockTime > block.timestamp) {
                        penaltyAmount = remaining;
                        require(bal.earned >= remaining, "Insufficient balance after penalty");
                        bal.earned = bal.earned.sub(remaining);
                        if (bal.earned == 0) {
                            delete userEarnings[msg.sender];
                            break;
                        }
                        remaining = remaining.mul(2);
                    }
                    if (remaining <= earnedAmount) {
                        userEarnings[msg.sender][i].amount = earnedAmount.sub(remaining);
                        break;
                    } else {
                        delete userEarnings[msg.sender][i];
                        remaining = remaining.sub(earnedAmount);
                    }
                }
            }
    
            uint256 adjustedAmount = amount.add(penaltyAmount);
            bal.total = bal.total.sub(adjustedAmount);
            totalSupply = totalSupply.sub(adjustedAmount);
            stakingToken.safeTransfer(msg.sender, amount);
            if (penaltyAmount > 0) {
                incentivesController.claim(address(this), new address[](0));
                _notifyReward(address(stakingToken), penaltyAmount);
            }
            emit Withdrawn(msg.sender, amount, penaltyAmount);
        }
    
        function _getReward(address[] memory _rewardTokens) internal {
            uint256 length = _rewardTokens.length;
            for (uint i; i < length; i++) {
                address token = _rewardTokens[i];
                uint256 reward = rewards[msg.sender][token].div(1e12);
                if (token != address(stakingToken)) {
                    // for rewards other than stakingToken, every 24 hours we check if new
                    // rewards were sent to the contract or accrued via aToken interest
                    Reward storage r = rewardData[token];
                    uint256 periodFinish = r.periodFinish;
                    require(periodFinish > 0, "Unknown reward token");
                    uint256 balance = r.balance;
                    if (periodFinish < block.timestamp.add(rewardsDuration - 86400)) {
                        uint256 unseen = IERC20(token).balanceOf(address(this)).sub(balance);
                        if (unseen > 0) {
                            _notifyReward(token, unseen);
                            balance = balance.add(unseen);
                        }
                    }
                    r.balance = balance.sub(reward);
                }
                if (reward == 0) continue;
                rewards[msg.sender][token] = 0;
                IERC20(token).safeTransfer(msg.sender, reward);
                emit RewardPaid(msg.sender, token, reward);
            }
        }
    
        // Claim all pending staking rewards
        function getReward(address[] memory _rewardTokens) public {
            _updateReward(msg.sender);
            _getReward(_rewardTokens);
        }
    
        // Withdraw full unlocked balance and optionally claim pending rewards
        function exit(bool claimRewards) external {
            _updateReward(msg.sender);
            (uint256 amount, uint256 penaltyAmount) = withdrawableBalance(msg.sender);
            delete userEarnings[msg.sender];
            Balances storage bal = balances[msg.sender];
            bal.total = bal.total.sub(bal.unlocked).sub(bal.earned);
            bal.unlocked = 0;
            bal.earned = 0;
    
            totalSupply = totalSupply.sub(amount.add(penaltyAmount));
            stakingToken.safeTransfer(msg.sender, amount);
            if (penaltyAmount > 0) {
                incentivesController.claim(address(this), new address[](0));
                _notifyReward(address(stakingToken), penaltyAmount);
            }
            if (claimRewards) {
                _getReward(rewardTokens);
            }
            emit Withdrawn(msg.sender, amount, penaltyAmount);
        }
    
        // Withdraw all currently locked tokens where the unlock time has passed
        function withdrawExpiredLocks() external {
            _updateReward(msg.sender);
            LockedBalance[] storage locks = userLocks[msg.sender];
            Balances storage bal = balances[msg.sender];
            uint256 amount;
            uint256 length = locks.length;
            if (locks[length-1].unlockTime <= block.timestamp) {
                amount = bal.locked;
                delete userLocks[msg.sender];
            } else {
                for (uint i = 0; i < length; i++) {
                    if (locks[i].unlockTime > block.timestamp) break;
                    amount = amount.add(locks[i].amount);
                    delete locks[i];
                }
            }
            bal.locked = bal.locked.sub(amount);
            bal.total = bal.total.sub(amount);
            totalSupply = totalSupply.sub(amount);
            lockedSupply = lockedSupply.sub(amount);
            stakingToken.safeTransfer(msg.sender, amount);
            emit Withdrawn(msg.sender, amount, 0);
        }
    
        /* ========== RESTRICTED FUNCTIONS ========== */
    
        function _notifyReward(address _rewardsToken, uint256 reward) internal {
            Reward storage r = rewardData[_rewardsToken];
            if (block.timestamp >= r.periodFinish) {
                r.rewardRate = reward.mul(1e12).div(rewardsDuration);
            } else {
                uint256 remaining = r.periodFinish.sub(block.timestamp);
                uint256 leftover = remaining.mul(r.rewardRate).div(1e12);
                r.rewardRate = reward.add(leftover).mul(1e12).div(rewardsDuration);
            }
    
            r.lastUpdateTime = block.timestamp;
            r.periodFinish = block.timestamp.add(rewardsDuration);
    
        }
    
        // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
        function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
            require(tokenAddress != address(stakingToken), "Cannot withdraw staking token");
            require(rewardData[tokenAddress].lastUpdateTime == 0, "Cannot withdraw reward token");
            IERC20(tokenAddress).safeTransfer(owner(), tokenAmount);
            emit Recovered(tokenAddress, tokenAmount);
        }
    
        function _updateReward(address account) internal {
            address token = address(stakingToken);
            uint256 balance;
            Reward storage r = rewardData[token];
            uint256 rpt = _rewardPerToken(token, lockedSupply);
            r.rewardPerTokenStored = rpt;
            r.lastUpdateTime = lastTimeRewardApplicable(token);
            if (account != address(this)) {
                // Special case, use the locked balances and supply for stakingReward rewards
                rewards[account][token] = _earned(account, token, balances[account].locked, rpt);
                userRewardPerTokenPaid[account][token] = rpt;
                balance = balances[account].total;
            }
    
            uint256 supply = totalSupply;
            uint256 length = rewardTokens.length;
            for (uint i = 1; i < length; i++) {
                token = rewardTokens[i];
                r = rewardData[token];
                rpt = _rewardPerToken(token, supply);
                r.rewardPerTokenStored = rpt;
                r.lastUpdateTime = lastTimeRewardApplicable(token);
                if (account != address(this)) {
                    rewards[account][token] = _earned(account, token, balance, rpt);
                    userRewardPerTokenPaid[account][token] = rpt;
                }
            }
        }
    
        /* ========== EVENTS ========== */
    
        event RewardAdded(uint256 reward);
        event Staked(address indexed user, uint256 amount, bool locked);
        event Withdrawn(address indexed user, uint256 receivedAmount, uint256 penaltyPaid);
        event RewardPaid(address indexed user, address indexed rewardsToken, uint256 reward);
        event RewardsDurationUpdated(address token, uint256 newDuration);
        event Recovered(address token, uint256 amount);
    }

    Please enter a contract address above to load the contract details and source code.

    Context size (optional):