| /** | | |
| *Submitted for verification at Etherscan.io on 2020-08-15 | | |
| */ | | |
| | | |
| /** | | |
| *Submitted for verification at Etherscan.io on 2020-08-15 | | |
| */ | | |
| | | |
| /** | | /** |
| *Submitted for verification at Etherscan.io on 2020-07-11 | | *Submitted for verification at Etherscan.io on 2020-07-11 |
| */ | | */ |
| | | |
| // File: openzeppelin-solidity/contracts/math/SafeMath.sol | | // File: openzeppelin-solidity/contracts/math/SafeMath.sol |
| | | |
| pragma solidity ^0.5.0; | | pragma solidity ^0.5.0; |
| | | |
| /** | | /** |
| * @dev Wrappers over Solidity's arithmetic operations with added overflow | | * @dev Wrappers over Solidity's arithmetic operations with added overflow |
| * checks. | | * checks. |
| * | | * |
| * Arithmetic operations in Solidity wrap on overflow. This can easily result | | * Arithmetic operations in Solidity wrap on overflow. This can easily result |
| * in bugs, because programmers usually assume that an overflow raises an | | * in bugs, because programmers usually assume that an overflow raises an |
| * error, which is the standard behavior in high level programming languages. | | * error, which is the standard behavior in high level programming languages. |
| * `SafeMath` restores this intuition by reverting the transaction when an | | * `SafeMath` restores this intuition by reverting the transaction when an |
| * operation overflows. | | * operation overflows. |
| * | | * |
| * Using this library instead of the unchecked operations eliminates an entire | | * Using this library instead of the unchecked operations eliminates an entire |
| * class of bugs, so it's recommended to use it always. | | * class of bugs, so it's recommended to use it always. |
| */ | | */ |
| library SafeMath { | | library SafeMath { |
| /** | | /** |
| * @dev Returns the addition of two unsigned integers, reverting on | | * @dev Returns the addition of two unsigned integers, reverting on |
| * overflow. | | * overflow. |
| * | | * |
| * Counterpart to Solidity's `+` operator. | | * Counterpart to Solidity's `+` operator. |
| * | | * |
| * Requirements: | | * Requirements: |
| * - Addition cannot overflow. | | * - Addition cannot overflow. |
| */ | | */ |
| function add(uint256 a, uint256 b) internal pure returns (uint256) { | | function add(uint256 a, uint256 b) internal pure returns (uint256) { |
| uint256 c = a + b; | | uint256 c = a + b; |
| require(c >= a, "SafeMath: addition overflow"); | | require(c >= a, "SafeMath: addition overflow"); |
| | | |
| return c; | | return c; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the subtraction of two unsigned integers, reverting on | | * @dev Returns the subtraction of two unsigned integers, reverting on |
| * overflow (when the result is negative). | | * overflow (when the result is negative). |
| * | | * |
| * Counterpart to Solidity's `-` operator. | | * Counterpart to Solidity's `-` operator. |
| * | | * |
| * Requirements: | | * Requirements: |
| * - Subtraction cannot overflow. | | * - Subtraction cannot overflow. |
| */ | | */ |
| function sub(uint256 a, uint256 b) internal pure returns (uint256) { | | function sub(uint256 a, uint256 b) internal pure returns (uint256) { |
| return sub(a, b, "SafeMath: subtraction overflow"); | | return sub(a, b, "SafeMath: subtraction overflow"); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the subtraction of two unsigned integers, reverting with custom message on | | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on |
| * overflow (when the result is negative). | | * overflow (when the result is negative). |
| * | | * |
| * Counterpart to Solidity's `-` operator. | | * Counterpart to Solidity's `-` operator. |
| * | | * |
| * Requirements: | | * Requirements: |
| * - Subtraction cannot overflow. | | * - Subtraction cannot overflow. |
| * | | * |
| * _Available since v2.4.0._ | | * _Available since v2.4.0._ |
| */ | | */ |
| function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { |
| require(b <= a, errorMessage); | | require(b <= a, errorMessage); |
| uint256 c = a - b; | | uint256 c = a - b; |
| | | |
| return c; | | return c; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the multiplication of two unsigned integers, reverting on | | * @dev Returns the multiplication of two unsigned integers, reverting on |
| * overflow. | | * overflow. |
| * | | * |
| * Counterpart to Solidity's `*` operator. | | * Counterpart to Solidity's `*` operator. |
| * | | * |
| * Requirements: | | * Requirements: |
| * - Multiplication cannot overflow. | | * - Multiplication cannot overflow. |
| */ | | */ |
| function mul(uint256 a, uint256 b) internal pure returns (uint256) { | | function mul(uint256 a, uint256 b) internal pure returns (uint256) { |
| // Gas optimization: this is cheaper than requiring 'a' not being zero, but the | | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the |
| // benefit is lost if 'b' is also tested. | | // benefit is lost if 'b' is also tested. |
| // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 | | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 |
| if (a == 0) { | | if (a == 0) { |
| return 0; | | return 0; |
| } | | } |
| | | |
| uint256 c = a * b; | | uint256 c = a * b; |
| require(c / a == b, "SafeMath: multiplication overflow"); | | require(c / a == b, "SafeMath: multiplication overflow"); |
| | | |
| return c; | | return c; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the integer division of two unsigned integers. Reverts on | | * @dev Returns the integer division of two unsigned integers. Reverts on |
| * division by zero. The result is rounded towards zero. | | * division by zero. The result is rounded towards zero. |
| * | | * |
| * Counterpart to Solidity's `/` operator. Note: this function uses a | | * Counterpart to Solidity's `/` operator. Note: this function uses a |
| * `revert` opcode (which leaves remaining gas untouched) while Solidity | | * `revert` opcode (which leaves remaining gas untouched) while Solidity |
| * uses an invalid opcode to revert (consuming all remaining gas). | | * uses an invalid opcode to revert (consuming all remaining gas). |
| * | | * |
| * Requirements: | | * Requirements: |
| * - The divisor cannot be zero. | | * - The divisor cannot be zero. |
| */ | | */ |
| function div(uint256 a, uint256 b) internal pure returns (uint256) { | | function div(uint256 a, uint256 b) internal pure returns (uint256) { |
| return div(a, b, "SafeMath: division by zero"); | | return div(a, b, "SafeMath: division by zero"); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the integer division of two unsigned integers. Reverts with custom message on | | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on |
| * division by zero. The result is rounded towards zero. | | * division by zero. The result is rounded towards zero. |
| * | | * |
| * Counterpart to Solidity's `/` operator. Note: this function uses a | | * Counterpart to Solidity's `/` operator. Note: this function uses a |
| * `revert` opcode (which leaves remaining gas untouched) while Solidity | | * `revert` opcode (which leaves remaining gas untouched) while Solidity |
| * uses an invalid opcode to revert (consuming all remaining gas). | | * uses an invalid opcode to revert (consuming all remaining gas). |
| * | | * |
| * Requirements: | | * Requirements: |
| * - The divisor cannot be zero. | | * - The divisor cannot be zero. |
| * | | * |
| * _Available since v2.4.0._ | | * _Available since v2.4.0._ |
| */ | | */ |
| function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { |
| // Solidity only automatically asserts when dividing by 0 | | // Solidity only automatically asserts when dividing by 0 |
| require(b > 0, errorMessage); | | require(b > 0, errorMessage); |
| uint256 c = a / b; | | uint256 c = a / b; |
| // assert(a == b * c + a % b); // There is no case in which this doesn't hold | | // assert(a == b * c + a % b); // There is no case in which this doesn't hold |
| | | |
| return c; | | return c; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), | | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), |
| * Reverts when dividing by zero. | | * Reverts when dividing by zero. |
| * | | * |
| * Counterpart to Solidity's `%` operator. This function uses a `revert` | | * Counterpart to Solidity's `%` operator. This function uses a `revert` |
| * opcode (which leaves remaining gas untouched) while Solidity uses an | | * opcode (which leaves remaining gas untouched) while Solidity uses an |
| * invalid opcode to revert (consuming all remaining gas). | | * invalid opcode to revert (consuming all remaining gas). |
| * | | * |
| * Requirements: | | * Requirements: |
| * - The divisor cannot be zero. | | * - The divisor cannot be zero. |
| */ | | */ |
| function mod(uint256 a, uint256 b) internal pure returns (uint256) { | | function mod(uint256 a, uint256 b) internal pure returns (uint256) { |
| return mod(a, b, "SafeMath: modulo by zero"); | | return mod(a, b, "SafeMath: modulo by zero"); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), | | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), |
| * Reverts with custom message when dividing by zero. | | * Reverts with custom message when dividing by zero. |
| * | | * |
| * Counterpart to Solidity's `%` operator. This function uses a `revert` | | * Counterpart to Solidity's `%` operator. This function uses a `revert` |
| * opcode (which leaves remaining gas untouched) while Solidity uses an | | * opcode (which leaves remaining gas untouched) while Solidity uses an |
| * invalid opcode to revert (consuming all remaining gas). | | * invalid opcode to revert (consuming all remaining gas). |
| * | | * |
| * Requirements: | | * Requirements: |
| * - The divisor cannot be zero. | | * - The divisor cannot be zero. |
| * | | * |
| * _Available since v2.4.0._ | | * _Available since v2.4.0._ |
| */ | | */ |
| function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { | | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { |
| require(b != 0, errorMessage); | | require(b != 0, errorMessage); |
| return a % b; | | return a % b; |
| } | | } |
| } | | } |
| | | |
| // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol | | // File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol |
| | | |
| pragma solidity ^0.5.0; | | pragma solidity ^0.5.0; |
| | | |
| /** | | /** |
| * @dev Interface of the ERC20 standard as defined in the EIP. Does not include | | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include |
| * the optional functions; to access them see {ERC20Detailed}. | | * the optional functions; to access them see {ERC20Detailed}. |
| */ | | */ |
| interface IERC20 { | | interface IERC20 { |
| /** | | /** |
| * @dev Returns the amount of tokens in existence. | | * @dev Returns the amount of tokens in existence. |
| */ | | */ |
| function totalSupply() external view returns (uint256); | | function totalSupply() external view returns (uint256); |
| | | |
| /** | | /** |
| * @dev Returns the amount of tokens owned by `account`. | | * @dev Returns the amount of tokens owned by `account`. |
| */ | | */ |
| function balanceOf(address account) external view returns (uint256); | | function balanceOf(address account) external view returns (uint256); |
| | | |
| /** | | /** |
| * @dev Moves `amount` tokens from the caller's account to `recipient`. | | * @dev Moves `amount` tokens from the caller's account to `recipient`. |
| * | | * |
| * Returns a boolean value indicating whether the operation succeeded. | | * Returns a boolean value indicating whether the operation succeeded. |
| * | | * |
| * Emits a {Transfer} event. | | * Emits a {Transfer} event. |
| */ | | */ |
| function transfer(address recipient, uint256 amount) external returns (bool); | | function transfer(address recipient, uint256 amount) external returns (bool); |
| | | |
| /** | | /** |
| * @dev Returns the remaining number of tokens that `spender` will be | | * @dev Returns the remaining number of tokens that `spender` will be |
| * allowed to spend on behalf of `owner` through {transferFrom}. This is | | * allowed to spend on behalf of `owner` through {transferFrom}. This is |
| * zero by default. | | * zero by default. |
| * | | * |
| * This value changes when {approve} or {transferFrom} are called. | | * This value changes when {approve} or {transferFrom} are called. |
| */ | | */ |
| function allowance(address owner, address spender) external view returns (uint256); | | function allowance(address owner, address spender) external view returns (uint256); |
| | | |
| /** | | /** |
| * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. | | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. |
| * | | * |
| * Returns a boolean value indicating whether the operation succeeded. | | * Returns a boolean value indicating whether the operation succeeded. |
| * | | * |
| * IMPORTANT: Beware that changing an allowance with this method brings the risk | | * 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 | | * that someone may use both the old and the new allowance by unfortunate |
| * transaction ordering. One possible solution to mitigate this race | | * transaction ordering. One possible solution to mitigate this race |
| * condition is to first reduce the spender's allowance to 0 and set the | | * condition is to first reduce the spender's allowance to 0 and set the |
| * desired value afterwards: | | * desired value afterwards: |
| * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 |
| * | | * |
| * Emits an {Approval} event. | | * Emits an {Approval} event. |
| */ | | */ |
| function approve(address spender, uint256 amount) external returns (bool); | | function approve(address spender, uint256 amount) external returns (bool); |
| | | |
| /** | | /** |
| * @dev Moves `amount` tokens from `sender` to `recipient` using the | | * @dev Moves `amount` tokens from `sender` to `recipient` using the |
| * allowance mechanism. `amount` is then deducted from the caller's | | * allowance mechanism. `amount` is then deducted from the caller's |
| * allowance. | | * allowance. |
| * | | * |
| * Returns a boolean value indicating whether the operation succeeded. | | * Returns a boolean value indicating whether the operation succeeded. |
| * | | * |
| * Emits a {Transfer} event. | | * Emits a {Transfer} event. |
| */ | | */ |
| function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); |
| | | |
| /** | | /** |
| * @dev Emitted when `value` tokens are moved from one account (`from`) to | | * @dev Emitted when `value` tokens are moved from one account (`from`) to |
| * another (`to`). | | * another (`to`). |
| * | | * |
| * Note that `value` may be zero. | | * Note that `value` may be zero. |
| */ | | */ |
| event Transfer(address indexed from, address indexed to, uint256 value); | | event Transfer(address indexed from, address indexed to, uint256 value); |
| | | |
| /** | | /** |
| * @dev Emitted when the allowance of a `spender` for an `owner` is set by | | * @dev Emitted when the allowance of a `spender` for an `owner` is set by |
| * a call to {approve}. `value` is the new allowance. | | * a call to {approve}. `value` is the new allowance. |
| */ | | */ |
| event Approval(address indexed owner, address indexed spender, uint256 value); | | event Approval(address indexed owner, address indexed spender, uint256 value); |
| } | | } |
| | | |
| // File: openzeppelin-solidity/contracts/GSN/Context.sol | | // File: openzeppelin-solidity/contracts/GSN/Context.sol |
| | | |
| pragma solidity ^0.5.0; | | pragma solidity ^0.5.0; |
| | | |
| /* | | /* |
| * @dev Provides information about the current execution context, including the | | * @dev Provides information about the current execution context, including the |
| * sender of the transaction and its data. While these are generally available | | * 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 | | * 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 | | * 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 | | * paying for execution may not be the actual sender (as far as an application |
| * is concerned). | | * is concerned). |
| * | | * |
| * This contract is only required for intermediate, library-like contracts. | | * This contract is only required for intermediate, library-like contracts. |
| */ | | */ |
| contract Context { | | contract Context { |
| // Empty internal constructor, to prevent people from mistakenly deploying | | // Empty internal constructor, to prevent people from mistakenly deploying |
| // an instance of this contract, which should be used via inheritance. | | // an instance of this contract, which should be used via inheritance. |
| constructor () internal { } | | constructor () internal { } |
| // solhint-disable-previous-line no-empty-blocks | | // solhint-disable-previous-line no-empty-blocks |
| | | |
| function _msgSender() internal view returns (address payable) { | | function _msgSender() internal view returns (address payable) { |
| return msg.sender; | | return msg.sender; |
| } | | } |
| | | |
| function _msgData() internal view returns (bytes memory) { | | function _msgData() internal view returns (bytes memory) { |
| this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 | | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 |
| return msg.data; | | return msg.data; |
| } | | } |
| } | | } |
| | | |
| // File: openzeppelin-solidity/contracts/ownership/Ownable.sol | | // File: openzeppelin-solidity/contracts/ownership/Ownable.sol |
| | | |
| pragma solidity ^0.5.0; | | pragma solidity ^0.5.0; |
| | | |
| /** | | /** |
| * @dev Contract module which provides a basic access control mechanism, where | | * @dev Contract module which provides a basic access control mechanism, where |
| * there is an account (an owner) that can be granted exclusive access to | | * there is an account (an owner) that can be granted exclusive access to |
| * specific functions. | | * specific functions. |
| * | | * |
| * This module is used through inheritance. It will make available the modifier | | * 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 | | * `onlyOwner`, which can be applied to your functions to restrict their use to |
| * the owner. | | * the owner. |
| */ | | */ |
| contract Ownable is Context { | | contract Ownable is Context { |
| address private _owner; | | address private _owner; |
| | | |
| event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); |
| | | |
| /** | | /** |
| * @dev Initializes the contract setting the deployer as the initial owner. | | * @dev Initializes the contract setting the deployer as the initial owner. |
| */ | | */ |
| constructor () internal { | | constructor () internal { |
| _owner = _msgSender(); | | _owner = _msgSender(); |
| emit OwnershipTransferred(address(0), _owner); | | emit OwnershipTransferred(address(0), _owner); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the address of the current owner. | | * @dev Returns the address of the current owner. |
| */ | | */ |
| function owner() public view returns (address) { | | function owner() public view returns (address) { |
| return _owner; | | return _owner; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Throws if called by any account other than the owner. | | * @dev Throws if called by any account other than the owner. |
| */ | | */ |
| modifier onlyOwner() { | | modifier onlyOwner() { |
| require(isOwner(), "Ownable: caller is not the owner"); | | require(isOwner(), "Ownable: caller is not the owner"); |
| _; | | _; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns true if the caller is the current owner. | | * @dev Returns true if the caller is the current owner. |
| */ | | */ |
| function isOwner() public view returns (bool) { | | function isOwner() public view returns (bool) { |
| return _msgSender() == _owner; | | return _msgSender() == _owner; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Leaves the contract without owner. It will not be possible to call | | * @dev Leaves the contract without owner. It will not be possible to call |
| * `onlyOwner` functions anymore. Can only be called by the current owner. | | * `onlyOwner` functions anymore. Can only be called by the current owner. |
| * | | * |
| * NOTE: Renouncing ownership will leave the contract without an owner, | | * NOTE: Renouncing ownership will leave the contract without an owner, |
| * thereby removing any functionality that is only available to the owner. | | * thereby removing any functionality that is only available to the owner. |
| */ | | */ |
| function renounceOwnership() public onlyOwner { | | function renounceOwnership() public onlyOwner { |
| emit OwnershipTransferred(_owner, address(0)); | | emit OwnershipTransferred(_owner, address(0)); |
| _owner = address(0); | | _owner = address(0); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Transfers ownership of the contract to a new account (`newOwner`). | | * @dev Transfers ownership of the contract to a new account (`newOwner`). |
| * Can only be called by the current owner. | | * Can only be called by the current owner. |
| */ | | */ |
| function transferOwnership(address newOwner) public onlyOwner { | | function transferOwnership(address newOwner) public onlyOwner { |
| _transferOwnership(newOwner); | | _transferOwnership(newOwner); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Transfers ownership of the contract to a new account (`newOwner`). | | * @dev Transfers ownership of the contract to a new account (`newOwner`). |
| */ | | */ |
| function _transferOwnership(address newOwner) internal { | | function _transferOwnership(address newOwner) internal { |
| require(newOwner != address(0), "Ownable: new owner is the zero address"); | | require(newOwner != address(0), "Ownable: new owner is the zero address"); |
| emit OwnershipTransferred(_owner, newOwner); | | emit OwnershipTransferred(_owner, newOwner); |
| _owner = newOwner; | | _owner = newOwner; |
| } | | } |
| } | | } |
| | | |
| // File: contracts/IStaking.sol | | // File: contracts/IStaking.sol |
| | | |
| pragma solidity 0.5.0; | | pragma solidity 0.5.0; |
| | | |
| /** | | /** |
| * @title Staking interface, as defined by EIP-900. | | * @title Staking interface, as defined by EIP-900. |
| * @dev https://github.com/ethereum/EIPs/blob/master/EIPS/eip-900.md | | * @dev https://github.com/ethereum/EIPs/blob/master/EIPS/eip-900.md |
| */ | | */ |
| contract IStaking { | | contract IStaking { |
| event Staked(address indexed user, uint256 amount, uint256 total, bytes data); | | event Staked(address indexed user, uint256 amount, uint256 total, bytes data); |
| event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); | | event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); |
| | | |
| function stake(uint256 amount, bytes calldata data) external; | | function stake(uint256 amount, bytes calldata data) external; |
| function stakeFor(address user, uint256 amount, bytes calldata data) external; | | function stakeFor(address user, uint256 amount, bytes calldata data) external; |
| function unstake(uint256 amount, bytes calldata data) external; | | function unstake(uint256 amount, bytes calldata data) external; |
| function totalStakedFor(address addr) public view returns (uint256); | | function totalStakedFor(address addr) public view returns (uint256); |
| function totalStaked() public view returns (uint256); | | function totalStaked() public view returns (uint256); |
| function token() external view returns (address); | | function token() external view returns (address); |
| | | |
| /** | | /** |
| * @return False. This application does not support staking history. | | * @return False. This application does not support staking history. |
| */ | | */ |
| function supportsHistory() external pure returns (bool) { | | function supportsHistory() external pure returns (bool) { |
| return false; | | return false; |
| } | | } |
| } | | } |
| | | |
| // File: contracts/TokenPool.sol | | // File: contracts/TokenPool.sol |
| | | |
| pragma solidity 0.5.0; | | pragma solidity 0.5.0; |
| | | |
| | | |
| | | |
| /** | | /** |
| * @title A simple holder of tokens. | | * @title A simple holder of tokens. |
| * This is a simple contract to hold tokens. It's useful in the case where a separate contract | | * This is a simple contract to hold tokens. It's useful in the case where a separate contract |
| * needs to hold multiple distinct pools of the same token. | | * needs to hold multiple distinct pools of the same token. |
| */ | | */ |
| contract TokenPool is Ownable { | | contract TokenPool is Ownable { |
| IERC20 public token; | | IERC20 public token; |
| | | |
| constructor(IERC20 _token) public { | | constructor(IERC20 _token) public { |
| token = _token; | | token = _token; |
| } | | } |
| | | |
| function balance() public view returns (uint256) { | | function balance() public view returns (uint256) { |
| return token.balanceOf(address(this)); | | return token.balanceOf(address(this)); |
| } | | } |
| | | |
| function transfer(address to, uint256 value) external onlyOwner returns (bool) { | | function transfer(address to, uint256 value) external onlyOwner returns (bool) { |
| return token.transfer(to, value); | | return token.transfer(to, value); |
| } | | } |
| } | | } |
| | | |
| // File: contracts/TokenGeyser.sol | | // File: contracts/TokenGeyser.sol |
| | | |
| pragma solidity 0.5.0; | | pragma solidity 0.5.0; |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| /** | | /** |
| * @title Token Geyser | | * @title Token Geyser |
| * @dev A smart-contract based mechanism to distribute tokens over time, inspired loosely by | | * @dev A smart-contract based mechanism to distribute tokens over time, inspired loosely by |
| * Compound and Uniswap. | | * Compound and Uniswap. |
| * | | * |
| * Distribution tokens are added to a locked pool in the contract and become unlocked over time | | * Distribution tokens are added to a locked pool in the contract and become unlocked over time |
| * according to a once-configurable unlock schedule. Once unlocked, they are available to be | | * according to a once-configurable unlock schedule. Once unlocked, they are available to be |
| * claimed by users. | | * claimed by users. |
| * | | * |
| * A user may deposit tokens to accrue ownership share over the unlocked pool. This owner share | | * A user may deposit tokens to accrue ownership share over the unlocked pool. This owner share |
| * is a function of the number of tokens deposited as well as the length of time deposited. | | * is a function of the number of tokens deposited as well as the length of time deposited. |
| * Specifically, a user's share of the currently-unlocked pool equals their "deposit-seconds" | | * Specifically, a user's share of the currently-unlocked pool equals their "deposit-seconds" |
| * divided by the global "deposit-seconds". This aligns the new token distribution with long | | * divided by the global "deposit-seconds". This aligns the new token distribution with long |
| * term supporters of the project, addressing one of the major drawbacks of simple airdrops. | | * term supporters of the project, addressing one of the major drawbacks of simple airdrops. |
| * | | * |
| * More background and motivation available at: | | * More background and motivation available at: |
| * https://github.com/ampleforth/RFCs/blob/master/RFCs/rfc-1.md | | * https://github.com/ampleforth/RFCs/blob/master/RFCs/rfc-1.md |
| */ | | */ |
| contract TokenGeyser is IStaking, Ownable { | | contract TokenGeyser is IStaking, Ownable { |
| using SafeMath for uint256; | | using SafeMath for uint256; |
| | | |
| event Staked(address indexed user, uint256 amount, uint256 total, bytes data); | | event Staked(address indexed user, uint256 amount, uint256 total, bytes data); |
| event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); | | event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); |
| event TokensClaimed(address indexed user, uint256 amount); | | event TokensClaimed(address indexed user, uint256 amount); |
| event TokensLocked(uint256 amount, uint256 durationSec, uint256 total); | | event TokensLocked(uint256 amount, uint256 durationSec, uint256 total); |
| // amount: Unlocked tokens, total: Total locked tokens | | // amount: Unlocked tokens, total: Total locked tokens |
| event TokensUnlocked(uint256 amount, uint256 total); | | event TokensUnlocked(uint256 amount, uint256 total); |
| | | |
| TokenPool private _stakingPool; | | TokenPool private _stakingPool; |
| TokenPool private _unlockedPool; | | TokenPool private _unlockedPool; |
| TokenPool private _lockedPool; | | TokenPool private _lockedPool; |
| | | |
| // | | // |
| // Time-bonus params | | // Time-bonus params |
| // | | // |
| uint256 public constant BONUS_DECIMALS = 2; | | uint256 public constant BONUS_DECIMALS = 2; |
| uint256 public startBonus = 0; | | uint256 public startBonus = 0; |
| uint256 public bonusPeriodSec = 0; | | uint256 public bonusPeriodSec = 0; |
| | | |
| // | | // |
| // Global accounting state | | // Global accounting state |
| // | | // |
| uint256 public totalLockedShares = 0; | | uint256 public totalLockedShares = 0; |
| uint256 public totalStakingShares = 0; | | uint256 public totalStakingShares = 0; |
| uint256 private _totalStakingShareSeconds = 0; | | uint256 private _totalStakingShareSeconds = 0; |
| uint256 private _lastAccountingTimestampSec = now; | | uint256 private _lastAccountingTimestampSec = now; |
| uint256 private _maxUnlockSchedules = 0; | | uint256 private _maxUnlockSchedules = 0; |
| uint256 private _initialSharesPerToken = 0; | | uint256 private _initialSharesPerToken = 0; |
| | | |
| // | | // |
| // User accounting state | | // User accounting state |
| // | | // |
| // Represents a single stake for a user. A user may have multiple. | | // Represents a single stake for a user. A user may have multiple. |
| struct Stake { | | struct Stake { |
| uint256 stakingShares; | | uint256 stakingShares; |
| uint256 timestampSec; | | uint256 timestampSec; |
| } | | } |
| | | |
| // Caches aggregated values from the User->Stake[] map to save computation. | | // Caches aggregated values from the User->Stake[] map to save computation. |
| // If lastAccountingTimestampSec is 0, there's no entry for that user. | | // If lastAccountingTimestampSec is 0, there's no entry for that user. |
| struct UserTotals { | | struct UserTotals { |
| uint256 stakingShares; | | uint256 stakingShares; |
| uint256 stakingShareSeconds; | | uint256 stakingShareSeconds; |
| uint256 lastAccountingTimestampSec; | | uint256 lastAccountingTimestampSec; |
| } | | } |
| | | |
| // Aggregated staking values per user | | // Aggregated staking values per user |
| mapping(address => UserTotals) private _userTotals; | | mapping(address => UserTotals) private _userTotals; |
| | | |
| // The collection of stakes for each user. Ordered by timestamp, earliest to latest. | | // The collection of stakes for each user. Ordered by timestamp, earliest to latest. |
| mapping(address => Stake[]) private _userStakes; | | mapping(address => Stake[]) private _userStakes; |
| | | |
| // | | // |
| // Locked/Unlocked Accounting state | | // Locked/Unlocked Accounting state |
| // | | // |
| struct UnlockSchedule { | | struct UnlockSchedule { |
| uint256 initialLockedShares; | | uint256 initialLockedShares; |
| uint256 unlockedShares; | | uint256 unlockedShares; |
| uint256 lastUnlockTimestampSec; | | uint256 lastUnlockTimestampSec; |
| uint256 endAtSec; | | uint256 endAtSec; |
| uint256 durationSec; | | uint256 durationSec; |
| } | | } |
| | | |
| UnlockSchedule[] public unlockSchedules; | | UnlockSchedule[] public unlockSchedules; |
| | | |
| /** | | /** |
| * @param stakingToken The token users deposit as stake. | | * @param stakingToken The token users deposit as stake. |
| * @param distributionToken The token users receive as they unstake. | | * @param distributionToken The token users receive as they unstake. |
| * @param maxUnlockSchedules Max number of unlock stages, to guard against hitting gas limit. | | * @param maxUnlockSchedules Max number of unlock stages, to guard against hitting gas limit. |
| * @param startBonus_ Starting time bonus, BONUS_DECIMALS fixed point. | | * @param startBonus_ Starting time bonus, BONUS_DECIMALS fixed point. |
| * e.g. 25% means user gets 25% of max distribution tokens. | | * e.g. 25% means user gets 25% of max distribution tokens. |
| * @param bonusPeriodSec_ Length of time for bonus to increase linearly to max. | | * @param bonusPeriodSec_ Length of time for bonus to increase linearly to max. |
| * @param initialSharesPerToken Number of shares to mint per staking token on first stake. | | * @param initialSharesPerToken Number of shares to mint per staking token on first stake. |
| */ | | */ |
| constructor(IERC20 stakingToken, IERC20 distributionToken, uint256 maxUnlockSchedules, | | constructor(IERC20 stakingToken, IERC20 distributionToken, uint256 maxUnlockSchedules, |
| uint256 startBonus_, uint256 bonusPeriodSec_, uint256 initialSharesPerToken) public { | | uint256 startBonus_, uint256 bonusPeriodSec_, uint256 initialSharesPerToken) public { |
| // The start bonus must be some fraction of the max. (i.e. <= 100%) | | // The start bonus must be some fraction of the max. (i.e. <= 100%) |
| require(startBonus_ <= 10**BONUS_DECIMALS, 'TokenGeyser: start bonus too high'); | | require(startBonus_ <= 10**BONUS_DECIMALS, 'TokenGeyser: start bonus too high'); |
| // If no period is desired, instead set startBonus = 100% | | // If no period is desired, instead set startBonus = 100% |
| // and bonusPeriod to a small value like 1sec. | | // and bonusPeriod to a small value like 1sec. |
| require(bonusPeriodSec_ != 0, 'TokenGeyser: bonus period is zero'); | | require(bonusPeriodSec_ != 0, 'TokenGeyser: bonus period is zero'); |
| require(initialSharesPerToken > 0, 'TokenGeyser: initialSharesPerToken is zero'); | | require(initialSharesPerToken > 0, 'TokenGeyser: initialSharesPerToken is zero'); |
| | | |
| _stakingPool = new TokenPool(stakingToken); | | _stakingPool = new TokenPool(stakingToken); |
| _unlockedPool = new TokenPool(distributionToken); | | _unlockedPool = new TokenPool(distributionToken); |
| _lockedPool = new TokenPool(distributionToken); | | _lockedPool = new TokenPool(distributionToken); |
| startBonus = startBonus_; | | startBonus = startBonus_; |
| bonusPeriodSec = bonusPeriodSec_; | | bonusPeriodSec = bonusPeriodSec_; |
| _maxUnlockSchedules = maxUnlockSchedules; | | _maxUnlockSchedules = maxUnlockSchedules; |
| _initialSharesPerToken = initialSharesPerToken; | | _initialSharesPerToken = initialSharesPerToken; |
| } | | } |
| | | |
| /** | | /** |
| * @return The token users deposit as stake. | | * @return The token users deposit as stake. |
| */ | | */ |
| function getStakingToken() public view returns (IERC20) { | | function getStakingToken() public view returns (IERC20) { |
| return _stakingPool.token(); | | return _stakingPool.token(); |
| } | | } |
| | | |
| /** | | /** |
| * @return The token users receive as they unstake. | | * @return The token users receive as they unstake. |
| */ | | */ |
| function getDistributionToken() public view returns (IERC20) { | | function getDistributionToken() public view returns (IERC20) { |
| assert(_unlockedPool.token() == _lockedPool.token()); | | assert(_unlockedPool.token() == _lockedPool.token()); |
| return _unlockedPool.token(); | | return _unlockedPool.token(); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Transfers amount of deposit tokens from the user. | | * @dev Transfers amount of deposit tokens from the user. |
| * @param amount Number of deposit tokens to stake. | | * @param amount Number of deposit tokens to stake. |
| * @param data Not used. | | * @param data Not used. |
| */ | | */ |
| function stake(uint256 amount, bytes calldata data) external { | | function stake(uint256 amount, bytes calldata data) external { |
| _stakeFor(msg.sender, msg.sender, amount); | | _stakeFor(msg.sender, msg.sender, amount); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Transfers amount of deposit tokens from the caller on behalf of user. | | * @dev Transfers amount of deposit tokens from the caller on behalf of user. |
| * @param user User address who gains credit for this stake operation. | | * @param user User address who gains credit for this stake operation. |
| * @param amount Number of deposit tokens to stake. | | * @param amount Number of deposit tokens to stake. |
| * @param data Not used. | | * @param data Not used. |
| */ | | */ |
| function stakeFor(address user, uint256 amount, bytes calldata data) external onlyOwner { | | function stakeFor(address user, uint256 amount, bytes calldata data) external onlyOwner { |
| _stakeFor(msg.sender, user, amount); | | _stakeFor(msg.sender, user, amount); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Private implementation of staking methods. | | * @dev Private implementation of staking methods. |
| * @param staker User address who deposits tokens to stake. | | * @param staker User address who deposits tokens to stake. |
| * @param beneficiary User address who gains credit for this stake operation. | | * @param beneficiary User address who gains credit for this stake operation. |
| * @param amount Number of deposit tokens to stake. | | * @param amount Number of deposit tokens to stake. |
| */ | | */ |
| function _stakeFor(address staker, address beneficiary, uint256 amount) private { | | function _stakeFor(address staker, address beneficiary, uint256 amount) private { |
| require(amount > 0, 'TokenGeyser: stake amount is zero'); | | require(amount > 0, 'TokenGeyser: stake amount is zero'); |
| require(beneficiary != address(0), 'TokenGeyser: beneficiary is zero address'); | | require(beneficiary != address(0), 'TokenGeyser: beneficiary is zero address'); |
| require(totalStakingShares == 0 || totalStaked() > 0, | | require(totalStakingShares == 0 || totalStaked() > 0, |
| 'TokenGeyser: Invalid state. Staking shares exist, but no staking tokens do'); | | 'TokenGeyser: Invalid state. Staking shares exist, but no staking tokens do'); |
| | | |
| uint256 mintedStakingShares = (totalStakingShares > 0) | | uint256 mintedStakingShares = (totalStakingShares > 0) |
| ? totalStakingShares.mul(amount).div(totalStaked()) | | ? totalStakingShares.mul(amount).div(totalStaked()) |
| : amount.mul(_initialSharesPerToken); | | : amount.mul(_initialSharesPerToken); |
| require(mintedStakingShares > 0, 'TokenGeyser: Stake amount is too small'); | | require(mintedStakingShares > 0, 'TokenGeyser: Stake amount is too small'); |
| | | |
| updateAccounting(); | | updateAccounting(); |
| | | |
| // 1. User Accounting | | // 1. User Accounting |
| UserTotals storage totals = _userTotals[beneficiary]; | | UserTotals storage totals = _userTotals[beneficiary]; |
| totals.stakingShares = totals.stakingShares.add(mintedStakingShares); | | totals.stakingShares = totals.stakingShares.add(mintedStakingShares); |
| totals.lastAccountingTimestampSec = now; | | totals.lastAccountingTimestampSec = now; |
| | | |
| Stake memory newStake = Stake(mintedStakingShares, now); | | Stake memory newStake = Stake(mintedStakingShares, now); |
| _userStakes[beneficiary].push(newStake); | | _userStakes[beneficiary].push(newStake); |
| | | |
| // 2. Global Accounting | | // 2. Global Accounting |
| totalStakingShares = totalStakingShares.add(mintedStakingShares); | | totalStakingShares = totalStakingShares.add(mintedStakingShares); |
| // Already set in updateAccounting() | | // Already set in updateAccounting() |
| // _lastAccountingTimestampSec = now; | | // _lastAccountingTimestampSec = now; |
| | | |
| // interactions | | // interactions |
| require(_stakingPool.token().transferFrom(staker, address(_stakingPool), amount), | | require(_stakingPool.token().transferFrom(staker, address(_stakingPool), amount), |
| 'TokenGeyser: transfer into staking pool failed'); | | 'TokenGeyser: transfer into staking pool failed'); |
| | | |
| emit Staked(beneficiary, amount, totalStakedFor(beneficiary), ""); | | emit Staked(beneficiary, amount, totalStakedFor(beneficiary), ""); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Unstakes a certain amount of previously deposited tokens. User also receives their | | * @dev Unstakes a certain amount of previously deposited tokens. User also receives their |
| * alotted number of distribution tokens. | | * alotted number of distribution tokens. |
| * @param amount Number of deposit tokens to unstake / withdraw. | | * @param amount Number of deposit tokens to unstake / withdraw. |
| * @param data Not used. | | * @param data Not used. |
| */ | | */ |
| function unstake(uint256 amount, bytes calldata data) external { | | function unstake(uint256 amount, bytes calldata data) external { |
| _unstake(amount); | | _unstake(amount); |
| } | | } |
| | | |
| /** | | /** |
| * @param amount Number of deposit tokens to unstake / withdraw. | | * @param amount Number of deposit tokens to unstake / withdraw. |
| * @return The total number of distribution tokens that would be rewarded. | | * @return The total number of distribution tokens that would be rewarded. |
| */ | | */ |
| function unstakeQuery(uint256 amount) public returns (uint256) { | | function unstakeQuery(uint256 amount) public returns (uint256) { |
| return _unstake(amount); | | return _unstake(amount); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Unstakes a certain amount of previously deposited tokens. User also receives their | | * @dev Unstakes a certain amount of previously deposited tokens. User also receives their |
| * alotted number of distribution tokens. | | * alotted number of distribution tokens. |
| * @param amount Number of deposit tokens to unstake / withdraw. | | * @param amount Number of deposit tokens to unstake / withdraw. |
| * @return The total number of distribution tokens rewarded. | | * @return The total number of distribution tokens rewarded. |
| */ | | */ |
| function _unstake(uint256 amount) private returns (uint256) { | | function _unstake(uint256 amount) private returns (uint256) { |
| updateAccounting(); | | updateAccounting(); |
| | | |
| // checks | | // checks |
| require(amount > 0, 'TokenGeyser: unstake amount is zero'); | | require(amount > 0, 'TokenGeyser: unstake amount is zero'); |
| require(totalStakedFor(msg.sender) >= amount, | | require(totalStakedFor(msg.sender) >= amount, |
| 'TokenGeyser: unstake amount is greater than total user stakes'); | | 'TokenGeyser: unstake amount is greater than total user stakes'); |
| uint256 stakingSharesToBurn = totalStakingShares.mul(amount).div(totalStaked()); | | uint256 stakingSharesToBurn = totalStakingShares.mul(amount).div(totalStaked()); |
| require(stakingSharesToBurn > 0, 'TokenGeyser: Unable to unstake amount this small'); | | require(stakingSharesToBurn > 0, 'TokenGeyser: Unable to unstake amount this small'); |
| | | |
| // 1. User Accounting | | // 1. User Accounting |
| UserTotals storage totals = _userTotals[msg.sender]; | | UserTotals storage totals = _userTotals[msg.sender]; |
| Stake[] storage accountStakes = _userStakes[msg.sender]; | | Stake[] storage accountStakes = _userStakes[msg.sender]; |
| | | |
| // Redeem from most recent stake and go backwards in time. | | // Redeem from most recent stake and go backwards in time. |
| uint256 stakingShareSecondsToBurn = 0; | | uint256 stakingShareSecondsToBurn = 0; |
| uint256 sharesLeftToBurn = stakingSharesToBurn; | | uint256 sharesLeftToBurn = stakingSharesToBurn; |
| uint256 rewardAmount = 0; | | uint256 rewardAmount = 0; |
| while (sharesLeftToBurn > 0) { | | while (sharesLeftToBurn > 0) { |
| Stake storage lastStake = accountStakes[accountStakes.length - 1]; | | Stake storage lastStake = accountStakes[accountStakes.length - 1]; |
| uint256 stakeTimeSec = now.sub(lastStake.timestampSec); | | uint256 stakeTimeSec = now.sub(lastStake.timestampSec); |
| uint256 newStakingShareSecondsToBurn = 0; | | uint256 newStakingShareSecondsToBurn = 0; |
| if (lastStake.stakingShares <= sharesLeftToBurn) { | | if (lastStake.stakingShares <= sharesLeftToBurn) { |
| // fully redeem a past stake | | // fully redeem a past stake |
| newStakingShareSecondsToBurn = lastStake.stakingShares.mul(stakeTimeSec); | | newStakingShareSecondsToBurn = lastStake.stakingShares.mul(stakeTimeSec); |
| rewardAmount = computeNewReward(rewardAmount, newStakingShareSecondsToBurn, stakeTimeSec); | | rewardAmount = computeNewReward(rewardAmount, newStakingShareSecondsToBurn, stakeTimeSec); |
| stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(newStakingShareSecondsToBurn); | | stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(newStakingShareSecondsToBurn); |
| sharesLeftToBurn = sharesLeftToBurn.sub(lastStake.stakingShares); | | sharesLeftToBurn = sharesLeftToBurn.sub(lastStake.stakingShares); |
| accountStakes.length--; | | accountStakes.length--; |
| } else { | | } else { |
| // partially redeem a past stake | | // partially redeem a past stake |
| newStakingShareSecondsToBurn = sharesLeftToBurn.mul(stakeTimeSec); | | newStakingShareSecondsToBurn = sharesLeftToBurn.mul(stakeTimeSec); |
| rewardAmount = computeNewReward(rewardAmount, newStakingShareSecondsToBurn, stakeTimeSec); | | rewardAmount = computeNewReward(rewardAmount, newStakingShareSecondsToBurn, stakeTimeSec); |
| stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(newStakingShareSecondsToBurn); | | stakingShareSecondsToBurn = stakingShareSecondsToBurn.add(newStakingShareSecondsToBurn); |
| lastStake.stakingShares = lastStake.stakingShares.sub(sharesLeftToBurn); | | lastStake.stakingShares = lastStake.stakingShares.sub(sharesLeftToBurn); |
| sharesLeftToBurn = 0; | | sharesLeftToBurn = 0; |
| } | | } |
| } | | } |
| totals.stakingShareSeconds = totals.stakingShareSeconds.sub(stakingShareSecondsToBurn); | | totals.stakingShareSeconds = totals.stakingShareSeconds.sub(stakingShareSecondsToBurn); |
| totals.stakingShares = totals.stakingShares.sub(stakingSharesToBurn); | | totals.stakingShares = totals.stakingShares.sub(stakingSharesToBurn); |
| // Already set in updateAccounting | | // Already set in updateAccounting |
| // totals.lastAccountingTimestampSec = now; | | // totals.lastAccountingTimestampSec = now; |
| | | |
| // 2. Global Accounting | | // 2. Global Accounting |
| _totalStakingShareSeconds = _totalStakingShareSeconds.sub(stakingShareSecondsToBurn); | | _totalStakingShareSeconds = _totalStakingShareSeconds.sub(stakingShareSecondsToBurn); |
| totalStakingShares = totalStakingShares.sub(stakingSharesToBurn); | | totalStakingShares = totalStakingShares.sub(stakingSharesToBurn); |
| // Already set in updateAccounting | | // Already set in updateAccounting |
| // _lastAccountingTimestampSec = now; | | // _lastAccountingTimestampSec = now; |
| | | |
| // interactions | | // interactions |
| require(_stakingPool.transfer(msg.sender, amount), | | require(_stakingPool.transfer(msg.sender, amount), |
| 'TokenGeyser: transfer out of staking pool failed'); | | 'TokenGeyser: transfer out of staking pool failed'); |
| require(_unlockedPool.transfer(msg.sender, rewardAmount), | | require(_unlockedPool.transfer(msg.sender, rewardAmount), |
| 'TokenGeyser: transfer out of unlocked pool failed'); | | 'TokenGeyser: transfer out of unlocked pool failed'); |
| | | |
| emit Unstaked(msg.sender, amount, totalStakedFor(msg.sender), ""); | | emit Unstaked(msg.sender, amount, totalStakedFor(msg.sender), ""); |
| emit TokensClaimed(msg.sender, rewardAmount); | | emit TokensClaimed(msg.sender, rewardAmount); |
| | | |
| require(totalStakingShares == 0 || totalStaked() > 0, | | require(totalStakingShares == 0 || totalStaked() > 0, |
| "TokenGeyser: Error unstaking. Staking shares exist, but no staking tokens do"); | | "TokenGeyser: Error unstaking. Staking shares exist, but no staking tokens do"); |
| return rewardAmount; | | return rewardAmount; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Applies an additional time-bonus to a distribution amount. This is necessary to | | * @dev Applies an additional time-bonus to a distribution amount. This is necessary to |
| * encourage long-term deposits instead of constant unstake/restakes. | | * encourage long-term deposits instead of constant unstake/restakes. |
| * The bonus-multiplier is the result of a linear function that starts at startBonus and | | * The bonus-multiplier is the result of a linear function that starts at startBonus and |
| * ends at 100% over bonusPeriodSec, then stays at 100% thereafter. | | * ends at 100% over bonusPeriodSec, then stays at 100% thereafter. |
| * @param currentRewardTokens The current number of distribution tokens already alotted for this | | * @param currentRewardTokens The current number of distribution tokens already alotted for this |
| * unstake op. Any bonuses are already applied. | | * unstake op. Any bonuses are already applied. |
| * @param stakingShareSeconds The stakingShare-seconds that are being burned for new | | * @param stakingShareSeconds The stakingShare-seconds that are being burned for new |
| * distribution tokens. | | * distribution tokens. |
| * @param stakeTimeSec Length of time for which the tokens were staked. Needed to calculate | | * @param stakeTimeSec Length of time for which the tokens were staked. Needed to calculate |
| * the time-bonus. | | * the time-bonus. |
| * @return Updated amount of distribution tokens to award, with any bonus included on the | | * @return Updated amount of distribution tokens to award, with any bonus included on the |
| * newly added tokens. | | * newly added tokens. |
| */ | | */ |
| function computeNewReward(uint256 currentRewardTokens, | | function computeNewReward(uint256 currentRewardTokens, |
| uint256 stakingShareSeconds, | | uint256 stakingShareSeconds, |
| uint256 stakeTimeSec) private view returns (uint256) { | | uint256 stakeTimeSec) private view returns (uint256) { |
| | | |
| uint256 newRewardTokens = | | uint256 newRewardTokens = |
| totalUnlocked() | | totalUnlocked() |
| .mul(stakingShareSeconds) | | .mul(stakingShareSeconds) |
| .div(_totalStakingShareSeconds); | | .div(_totalStakingShareSeconds); |
| | | |
| if (stakeTimeSec >= bonusPeriodSec) { | | if (stakeTimeSec >= bonusPeriodSec) { |
| return currentRewardTokens.add(newRewardTokens); | | return currentRewardTokens.add(newRewardTokens); |
| } | | } |
| | | |
| uint256 oneHundredPct = 10**BONUS_DECIMALS; | | uint256 oneHundredPct = 10**BONUS_DECIMALS; |
| uint256 bonusedReward = | | uint256 bonusedReward = |
| startBonus | | startBonus |
| .add(oneHundredPct.sub(startBonus).mul(stakeTimeSec).div(bonusPeriodSec)) | | .add(oneHundredPct.sub(startBonus).mul(stakeTimeSec).div(bonusPeriodSec)) |
| .mul(newRewardTokens) | | .mul(newRewardTokens) |
| .div(oneHundredPct); | | .div(oneHundredPct); |
| return currentRewardTokens.add(bonusedReward); | | return currentRewardTokens.add(bonusedReward); |
| } | | } |
| | | |
| /** | | /** |
| * @param addr The user to look up staking information for. | | * @param addr The user to look up staking information for. |
| * @return The number of staking tokens deposited for addr. | | * @return The number of staking tokens deposited for addr. |
| */ | | */ |
| function totalStakedFor(address addr) public view returns (uint256) { | | function totalStakedFor(address addr) public view returns (uint256) { |
| return totalStakingShares > 0 ? | | return totalStakingShares > 0 ? |
| totalStaked().mul(_userTotals[addr].stakingShares).div(totalStakingShares) : 0; | | totalStaked().mul(_userTotals[addr].stakingShares).div(totalStakingShares) : 0; |
| } | | } |
| | | |
| /** | | /** |
| * @return The total number of deposit tokens staked globally, by all users. | | * @return The total number of deposit tokens staked globally, by all users. |
| */ | | */ |
| function totalStaked() public view returns (uint256) { | | function totalStaked() public view returns (uint256) { |
| return _stakingPool.balance(); | | return _stakingPool.balance(); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Note that this application has a staking token as well as a distribution token, which | | * @dev Note that this application has a staking token as well as a distribution token, which |
| * may be different. This function is required by EIP-900. | | * may be different. This function is required by EIP-900. |
| * @return The deposit token used for staking. | | * @return The deposit token used for staking. |
| */ | | */ |
| function token() external view returns (address) { | | function token() external view returns (address) { |
| return address(getStakingToken()); | | return address(getStakingToken()); |
| } | | } |
| | | |
| /** | | /** |
| * @dev A globally callable function to update the accounting state of the system. | | * @dev A globally callable function to update the accounting state of the system. |
| * Global state and state for the caller are updated. | | * Global state and state for the caller are updated. |
| * @return [0] balance of the locked pool | | * @return [0] balance of the locked pool |
| * @return [1] balance of the unlocked pool | | * @return [1] balance of the unlocked pool |
| * @return [2] caller's staking share seconds | | * @return [2] caller's staking share seconds |
| * @return [3] global staking share seconds | | * @return [3] global staking share seconds |
| * @return [4] Rewards caller has accumulated, optimistically assumes max time-bonus. | | * @return [4] Rewards caller has accumulated, optimistically assumes max time-bonus. |
| * @return [5] block timestamp | | * @return [5] block timestamp |
| */ | | */ |
| function updateAccounting() public returns ( | | function updateAccounting() public returns ( |
| uint256, uint256, uint256, uint256, uint256, uint256) { | | uint256, uint256, uint256, uint256, uint256, uint256) { |
| | | |
| unlockTokens(); | | unlockTokens(); |
| | | |
| // Global accounting | | // Global accounting |
| uint256 newStakingShareSeconds = | | uint256 newStakingShareSeconds = |
| now | | now |
| .sub(_lastAccountingTimestampSec) | | .sub(_lastAccountingTimestampSec) |
| .mul(totalStakingShares); | | .mul(totalStakingShares); |
| _totalStakingShareSeconds = _totalStakingShareSeconds.add(newStakingShareSeconds); | | _totalStakingShareSeconds = _totalStakingShareSeconds.add(newStakingShareSeconds); |
| _lastAccountingTimestampSec = now; | | _lastAccountingTimestampSec = now; |
| | | |
| // User Accounting | | // User Accounting |
| UserTotals storage totals = _userTotals[msg.sender]; | | UserTotals storage totals = _userTotals[msg.sender]; |
| uint256 newUserStakingShareSeconds = | | uint256 newUserStakingShareSeconds = |
| now | | now |
| .sub(totals.lastAccountingTimestampSec) | | .sub(totals.lastAccountingTimestampSec) |
| .mul(totals.stakingShares); | | .mul(totals.stakingShares); |
| totals.stakingShareSeconds = | | totals.stakingShareSeconds = |
| totals.stakingShareSeconds | | totals.stakingShareSeconds |
| .add(newUserStakingShareSeconds); | | .add(newUserStakingShareSeconds); |
| totals.lastAccountingTimestampSec = now; | | totals.lastAccountingTimestampSec = now; |
| | | |
| uint256 totalUserRewards = (_totalStakingShareSeconds > 0) | | uint256 totalUserRewards = (_totalStakingShareSeconds > 0) |
| ? totalUnlocked().mul(totals.stakingShareSeconds).div(_totalStakingShareSeconds) | | ? totalUnlocked().mul(totals.stakingShareSeconds).div(_totalStakingShareSeconds) |
| : 0; | | : 0; |
| | | |
| return ( | | return ( |
| totalLocked(), | | totalLocked(), |
| totalUnlocked(), | | totalUnlocked(), |
| totals.stakingShareSeconds, | | totals.stakingShareSeconds, |
| _totalStakingShareSeconds, | | _totalStakingShareSeconds, |
| totalUserRewards, | | totalUserRewards, |
| now | | now |
| ); | | ); |
| } | | } |
| | | |
| /** | | /** |
| * @return Total number of locked distribution tokens. | | * @return Total number of locked distribution tokens. |
| */ | | */ |
| function totalLocked() public view returns (uint256) { | | function totalLocked() public view returns (uint256) { |
| return _lockedPool.balance(); | | return _lockedPool.balance(); |
| } | | } |
| | | |
| /** | | /** |
| * @return Total number of unlocked distribution tokens. | | * @return Total number of unlocked distribution tokens. |
| */ | | */ |
| function totalUnlocked() public view returns (uint256) { | | function totalUnlocked() public view returns (uint256) { |
| return _unlockedPool.balance(); | | return _unlockedPool.balance(); |
| } | | } |
| | | |
| /** | | /** |
| * @return Number of unlock schedules. | | * @return Number of unlock schedules. |
| */ | | */ |
| function unlockScheduleCount() public view returns (uint256) { | | function unlockScheduleCount() public view returns (uint256) { |
| return unlockSchedules.length; | | return unlockSchedules.length; |
| } | | } |
| | | |
| /** | | /** |
| * @dev This funcion allows the contract owner to add more locked distribution tokens, along | | * @dev This funcion allows the contract owner to add more locked distribution tokens, along |
| * with the associated "unlock schedule". These locked tokens immediately begin unlocking | | * with the associated "unlock schedule". These locked tokens immediately begin unlocking |
| * linearly over the duraction of durationSec timeframe. | | * linearly over the duraction of durationSec timeframe. |
| * @param amount Number of distribution tokens to lock. These are transferred from the caller. | | * @param amount Number of distribution tokens to lock. These are transferred from the caller. |
| * @param durationSec Length of time to linear unlock the tokens. | | * @param durationSec Length of time to linear unlock the tokens. |
| */ | | */ |
| function lockTokens(uint256 amount, uint256 durationSec) external onlyOwner { | | function lockTokens(uint256 amount, uint256 durationSec) external onlyOwner { |
| require(unlockSchedules.length < _maxUnlockSchedules, | | require(unlockSchedules.length < _maxUnlockSchedules, |
| 'TokenGeyser: reached maximum unlock schedules'); | | 'TokenGeyser: reached maximum unlock schedules'); |
| | | |
| // Update lockedTokens amount before using it in computations after. | | // Update lockedTokens amount before using it in computations after. |
| updateAccounting(); | | updateAccounting(); |
| | | |
| uint256 lockedTokens = totalLocked(); | | uint256 lockedTokens = totalLocked(); |
| uint256 mintedLockedShares = (lockedTokens > 0) | | uint256 mintedLockedShares = (lockedTokens > 0) |
| ? totalLockedShares.mul(amount).div(lockedTokens) | | ? totalLockedShares.mul(amount).div(lockedTokens) |
| : amount.mul(_initialSharesPerToken); | | : amount.mul(_initialSharesPerToken); |
| | | |
| UnlockSchedule memory schedule; | | UnlockSchedule memory schedule; |
| schedule.initialLockedShares = mintedLockedShares; | | schedule.initialLockedShares = mintedLockedShares; |
| schedule.lastUnlockTimestampSec = now; | | schedule.lastUnlockTimestampSec = now; |
| schedule.endAtSec = now.add(durationSec); | | schedule.endAtSec = now.add(durationSec); |
| schedule.durationSec = durationSec; | | schedule.durationSec = durationSec; |
| unlockSchedules.push(schedule); | | unlockSchedules.push(schedule); |
| | | |
| totalLockedShares = totalLockedShares.add(mintedLockedShares); | | totalLockedShares = totalLockedShares.add(mintedLockedShares); |
| | | |
| require(_lockedPool.token().transferFrom(msg.sender, address(_lockedPool), amount), | | require(_lockedPool.token().transferFrom(msg.sender, address(_lockedPool), amount), |
| 'TokenGeyser: transfer into locked pool failed'); | | 'TokenGeyser: transfer into locked pool failed'); |
| emit TokensLocked(amount, durationSec, totalLocked()); | | emit TokensLocked(amount, durationSec, totalLocked()); |
| } | | } |
| | | |
| /** | | /** |
| * @dev Moves distribution tokens from the locked pool to the unlocked pool, according to the | | * @dev Moves distribution tokens from the locked pool to the unlocked pool, according to the |
| * previously defined unlock schedules. Publicly callable. | | * previously defined unlock schedules. Publicly callable. |
| * @return Number of newly unlocked distribution tokens. | | * @return Number of newly unlocked distribution tokens. |
| */ | | */ |
| function unlockTokens() public returns (uint256) { | | function unlockTokens() public returns (uint256) { |
| uint256 unlockedTokens = 0; | | uint256 unlockedTokens = 0; |
| uint256 lockedTokens = totalLocked(); | | uint256 lockedTokens = totalLocked(); |
| | | |
| if (totalLockedShares == 0) { | | if (totalLockedShares == 0) { |
| unlockedTokens = lockedTokens; | | unlockedTokens = lockedTokens; |
| } else { | | } else { |
| uint256 unlockedShares = 0; | | uint256 unlockedShares = 0; |
| for (uint256 s = 0; s < unlockSchedules.length; s++) { | | for (uint256 s = 0; s < unlockSchedules.length; s++) { |
| unlockedShares = unlockedShares.add(unlockScheduleShares(s)); | | unlockedShares = unlockedShares.add(unlockScheduleShares(s)); |
| } | | } |
| unlockedTokens = unlockedShares.mul(lockedTokens).div(totalLockedShares); | | unlockedTokens = unlockedShares.mul(lockedTokens).div(totalLockedShares); |
| totalLockedShares = totalLockedShares.sub(unlockedShares); | | totalLockedShares = totalLockedShares.sub(unlockedShares); |
| } | | } |
| | | |
| if (unlockedTokens > 0) { | | if (unlockedTokens > 0) { |
| require(_lockedPool.transfer(address(_unlockedPool), unlockedTokens), | | require(_lockedPool.transfer(address(_unlockedPool), unlockedTokens), |
| 'TokenGeyser: transfer out of locked pool failed'); | | 'TokenGeyser: transfer out of locked pool failed'); |
| emit TokensUnlocked(unlockedTokens, totalLocked()); | | emit TokensUnlocked(unlockedTokens, totalLocked()); |
| } | | } |
| | | |
| return unlockedTokens; | | return unlockedTokens; |
| } | | } |
| | | |
| /** | | /** |
| * @dev Returns the number of unlockable shares from a given schedule. The returned value | | * @dev Returns the number of unlockable shares from a given schedule. The returned value |
| * depends on the time since the last unlock. This function updates schedule accounting, | | * depends on the time since the last unlock. This function updates schedule accounting, |
| * but does not actually transfer any tokens. | | * but does not actually transfer any tokens. |
| * @param s Index of the unlock schedule. | | * @param s Index of the unlock schedule. |
| * @return The number of unlocked shares. | | * @return The number of unlocked shares. |
| */ | | */ |
| function unlockScheduleShares(uint256 s) private returns (uint256) { | | function unlockScheduleShares(uint256 s) private returns (uint256) { |
| UnlockSchedule storage schedule = unlockSchedules[s]; | | UnlockSchedule storage schedule = unlockSchedules[s]; |
| | | |
| if(schedule.unlockedShares >= schedule.initialLockedShares) { | | if(schedule.unlockedShares >= schedule.initialLockedShares) { |
| return 0; | | return 0; |
| } | | } |
| | | |
| uint256 sharesToUnlock = 0; | | uint256 sharesToUnlock = 0; |
| // Special case to handle any leftover dust from integer division | | // Special case to handle any leftover dust from integer division |
| if (now >= schedule.endAtSec) { | | if (now >= schedule.endAtSec) { |
| sharesToUnlock = (schedule.initialLockedShares.sub(schedule.unlockedShares)); | | sharesToUnlock = (schedule.initialLockedShares.sub(schedule.unlockedShares)); |
| schedule.lastUnlockTimestampSec = schedule.endAtSec; | | schedule.lastUnlockTimestampSec = schedule.endAtSec; |
| } else { | | } else { |
| sharesToUnlock = now.sub(schedule.lastUnlockTimestampSec) | | sharesToUnlock = now.sub(schedule.lastUnlockTimestampSec) |
| .mul(schedule.initialLockedShares) | | .mul(schedule.initialLockedShares) |
| .div(schedule.durationSec); | | .div(schedule.durationSec); |
| schedule.lastUnlockTimestampSec = now; | | schedule.lastUnlockTimestampSec = now; |
| } | | } |
| | | |
| schedule.unlockedShares = schedule.unlockedShares.add(sharesToUnlock); | | schedule.unlockedShares = schedule.unlockedShares.add(sharesToUnlock); |
| return sharesToUnlock; | | return sharesToUnlock; |
| } | | } |
| } | | } |