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);
}