// File: contracts/usdb/bonds/TradFiBondDepository.sol



// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity 0.7.5;

interface IOwnable {
    function policy() external view returns (address);

    function renounceManagement() external;

    function pushManagement( address newOwner_ ) external;

    function pullManagement() external;
}

contract Ownable is IOwnable {

    address internal _owner;
    address internal _newOwner;

    event OwnershipPushed(address indexed previousOwner, address indexed newOwner);
    event OwnershipPulled(address indexed previousOwner, address indexed newOwner);

    constructor () {
        _owner = msg.sender;
        emit OwnershipPushed( address(0), _owner );
    }

    function policy() public view override returns (address) {
        return _owner;
    }

    modifier onlyPolicy() {
        require( _owner == msg.sender, "Ownable: caller is not the owner" );
        _;
    }

    modifier onlyDepositor(address _depositor) {
        require( _depositor == msg.sender, "caller is not the depositor" );
        _;
    }

    function renounceManagement() public virtual override onlyPolicy() {
        emit OwnershipPushed( _owner, address(0) );
        _owner = address(0);
    }

    function pushManagement( address newOwner_ ) public virtual override onlyPolicy() {
        require( newOwner_ != address(0), "Ownable: new owner is the zero address");
        emit OwnershipPushed( _owner, newOwner_ );
        _newOwner = newOwner_;
    }

    function pullManagement() public virtual override {
        require( msg.sender == _newOwner, "Ownable: must be new owner to pull");
        emit OwnershipPulled( _owner, _newOwner );
        _owner = _newOwner;
    }
}

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }
}


library SafeMath {

    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }

    function sqrrt(uint256 a) internal pure returns (uint c) {
        if (a > 3) {
            c = a;
            uint b = add( div( a, 2), 1 );
            while (b < c) {
                c = b;
                b = div( add( div( a, b ), b), 2 );
            }
        } else if (a != 0) {
            c = 1;
        }
    }
}

library Address {

    function isContract(address account) internal view returns (bool) {

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

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

    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        return _functionCallWithValue(target, data, 0, errorMessage);
    }

    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: value }(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }

    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }

    function addressToString(address _address) internal pure returns(string memory) {
        bytes32 _bytes = bytes32(uint256(_address));
        bytes memory HEX = "0123456789abcdef";
        bytes memory _addr = new bytes(42);

        _addr[0] = '0';
        _addr[1] = 'x';

        for(uint256 i = 0; i < 20; i++) {
            _addr[2+i*2] = HEX[uint8(_bytes[i + 12] >> 4)];
            _addr[3+i*2] = HEX[uint8(_bytes[i + 12] & 0x0f)];
        }

        return string(_addr);

    }
}

interface IERC20 {
    function decimals() external view returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}

abstract contract ERC20 is IERC20 {

    using SafeMath for uint256;

    // TODO comment actual hash value.
    bytes32 constant private ERC20TOKEN_ERC1820_INTERFACE_ID = keccak256( "ERC20Token" );

    mapping (address => uint256) internal _balances;

    mapping (address => mapping (address => uint256)) internal _allowances;

    uint256 internal _totalSupply;

    string internal _name;

    string internal _symbol;

    uint8 internal _decimals;

    constructor (string memory name_, string memory symbol_, uint8 decimals_) {
        _name = name_;
        _symbol = symbol_;
        _decimals = decimals_;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(sender, recipient, amount);

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    function _mint(address account_, uint256 ammount_) internal virtual {
        require(account_ != address(0), "ERC20: mint to the zero address");
        _beforeTokenTransfer(address( this ), account_, ammount_);
        _totalSupply = _totalSupply.add(ammount_);
        _balances[account_] = _balances[account_].add(ammount_);
        emit Transfer(address( this ), account_, ammount_);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _beforeTokenTransfer( address from_, address to_, uint256 amount_ ) internal virtual { }
}

interface IERC2612Permit {

    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function nonces(address owner) external view returns (uint256);
}

library Counters {
    using SafeMath for uint256;

    struct Counter {

        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        counter._value += 1;
    }

    function decrement(Counter storage counter) internal {
        counter._value = counter._value.sub(1);
    }
}

abstract contract ERC20Permit is ERC20, IERC2612Permit {
    using Counters for Counters.Counter;

    mapping(address => Counters.Counter) private _nonces;

    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    bytes32 public DOMAIN_SEPARATOR;

    constructor() {
        uint256 chainID;
        assembly {
            chainID := chainid()
        }

        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes(name())),
                keccak256(bytes("1")), // Version
                chainID,
                address(this)
            )
        );
    }

    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "Permit: expired deadline");

        bytes32 hashStruct =
        keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner].current(), deadline));

        bytes32 _hash = keccak256(abi.encodePacked(uint16(0x1901), DOMAIN_SEPARATOR, hashStruct));

        address signer = ecrecover(_hash, v, r, s);
        require(signer != address(0) && signer == owner, "ZeroSwapPermit: Invalid signature");

        _nonces[owner].increment();
        _approve(owner, spender, amount);
    }

    function nonces(address owner) public view override returns (uint256) {
        return _nonces[owner].current();
    }
}

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 safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function _callOptionalReturn(IERC20 token, bytes memory data) private {

        bytes memory returndata = address(token).functionCall(data, "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");
        }
    }
}

library FullMath {
    function fullMul(uint256 x, uint256 y) private pure returns (uint256 l, uint256 h) {
        uint256 mm = mulmod(x, y, uint256(-1));
        l = x * y;
        h = mm - l;
        if (mm < l) h -= 1;
    }

    function fullDiv(
        uint256 l,
        uint256 h,
        uint256 d
    ) private pure returns (uint256) {
        uint256 pow2 = d & -d;
        d /= pow2;
        l /= pow2;
        l += h * ((-pow2) / pow2 + 1);
        uint256 r = 1;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        r *= 2 - d * r;
        return l * r;
    }

    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 d
    ) internal pure returns (uint256) {
        (uint256 l, uint256 h) = fullMul(x, y);
        uint256 mm = mulmod(x, y, d);
        if (mm > l) h -= 1;
        l -= mm;
        require(h < d, 'FullMath::mulDiv: overflow');
        return fullDiv(l, h, d);
    }
}

library FixedPoint {

    struct uq112x112 {
        uint224 _x;
    }

    struct uq144x112 {
        uint256 _x;
    }

    uint8 private constant RESOLUTION = 112;
    uint256 private constant Q112 = 0x10000000000000000000000000000;
    uint256 private constant Q224 = 0x100000000000000000000000000000000000000000000000000000000;
    uint256 private constant LOWER_MASK = 0xffffffffffffffffffffffffffff; // decimal of UQ*x112 (lower 112 bits)

    function decode(uq112x112 memory self) internal pure returns (uint112) {
        return uint112(self._x >> RESOLUTION);
    }

    function decode112with18(uq112x112 memory self) internal pure returns (uint) {

        return uint(self._x) / 5192296858534827;
    }

    function fraction(uint256 numerator, uint256 denominator) internal pure returns (uq112x112 memory) {
        require(denominator > 0, 'FixedPoint::fraction: division by zero');
        if (numerator == 0) return FixedPoint.uq112x112(0);

        if (numerator <= uint144(-1)) {
            uint256 result = (numerator << RESOLUTION) / denominator;
            require(result <= uint224(-1), 'FixedPoint::fraction: overflow');
            return uq112x112(uint224(result));
        } else {
            uint256 result = FullMath.mulDiv(numerator, Q112, denominator);
            require(result <= uint224(-1), 'FixedPoint::fraction: overflow');
            return uq112x112(uint224(result));
        }
    }
}

// Info for bond holder
struct Bond {
    uint payout; // USDB to be paid
    uint vesting; // Blocks left to vest
    uint lastBlock; // Last interaction
    uint pricePaid; // In DAI, for front end viewing
    uint vestingSeconds; // Blocks left to vest
    uint lastTimestamp; // Last interaction
}

library IterableMapping {


    // Iterable mapping from address to uint;
    struct Map {
        address[] keys;
        mapping(address => Bond[]) values;
        mapping(address => uint) indexOf;
        mapping(address => bool) inserted;
    }

    function get(Map storage map, address key, uint index) public view returns (Bond storage) {
        return map.values[key][index];
    }

    function getKeyAtIndex(Map storage map, uint index) public view returns (address) {
        return map.keys[index];
    }

    function size(Map storage map) public view returns (uint) {
        return map.keys.length;
    }

    function set(
        Map storage map,
        address key,
        Bond storage val
    ) public {
        if (map.inserted[key]) {
            map.values[key].push(val);

        } else {
            map.inserted[key] = true;

            map.values[key].push(val);
            map.indexOf[key] = map.keys.length;
            map.keys.push(key);
        }
    }

    function remove(Map storage map, address key) public {
        if (!map.inserted[key]) {
            return;
        }

        delete map.inserted[key];
        delete map.values[key];

        uint index = map.indexOf[key];
        uint lastIndex = map.keys.length - 1;
        address lastKey = map.keys[lastIndex];

        map.indexOf[lastKey] = index;
        delete map.indexOf[key];

        map.keys[index] = lastKey;
        map.keys.pop();
    }
}

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor () internal {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

interface ITreasury {
    function deposit( uint _amount, address _token, uint _profit ) external returns ( uint send_ );
    function valueOf( address _token, uint _amount ) external view returns ( uint value_ );
    function mintRewards( address _recipient, uint _amount ) external;
}

interface IBondCalculator {
    function valuation( address _LP, uint _amount ) external view returns ( uint );
    function markdown( address _LP ) external view returns ( uint );
}

interface IStaking {
    function stake( uint _amount, address _recipient ) external returns ( bool );
}

interface IStakingHelper {
    function stake( uint _amount, address _recipient ) external;
}

interface IMintable {
    function mint(address to, uint256 amount) external;
}

interface IBurnable {
    function burn(uint256 amount) external;
}

interface IUsdbMinter {
    function getMarketPrice() external view returns (uint);
}

/// @notice Traditional Finance bond
/// @dev this is ISO bond - x% discount with 6 weeks vesting
contract TradFiBondDepository is Ownable, ReentrancyGuard {

    using FixedPoint for *;
    using SafeERC20 for IERC20;
    using SafeMath for uint;
    using IterableMapping for IterableMapping.Map;



    /* ======== EVENTS ======== */

    event BondCreated( uint deposit, uint indexed payout, uint indexed expiresTimestamp, uint expiresBlock, uint indexed priceInUSD );
    event BondRedeemed( address indexed recipient, uint payout, uint remainingSeconds, uint remainingBlocks );
    event BondCancelled( address indexed recipient, uint index, uint deposit, uint possiblePayout, uint forfeited);




    /* ======== STATE VARIABLES ======== */

    address public immutable FHM; // token given as payment for bond
    address public immutable USDB; // USDB
    address public immutable principle; // token used to create bond
    address public immutable treasury; // mints FHM when receives principle
    address public immutable DAO; // receives profit share from bond
    address public immutable usdbMinter; // receives profit share from bond

    Terms public terms; // stores terms for new bonds

    IterableMapping.Map private depositors; // stores depositors bond information

    uint public totalDebt; // total value of outstanding bonds; used for pricing
    uint public lastDecay; // reference block for debt decay

    bool public useWhitelist;
    bool public useCircuitBreaker;
    mapping(address => bool) public whitelist;
    SoldBonds[] public soldBondsInHour;
    Bond public _bondInfo;
    uint public usersCount;
    uint public limitBondCount; //limit bond counts users can deposit

    /* ======== STRUCTS ======== */

    // Info for creating new bonds
    struct Terms {
        uint vestingTermSeconds; // in seconds
        uint vestingTerm; // safeguard, use some vestingTermSeconds/2 in blocks
        uint discount; // discount in in thousandths of a % i.e. 5000 = 5%
        uint maxPayout; // in thousandths of a %. i.e. 500 = 0.5%
        uint fee; // as % of bond payout, in hundreds. ( 500 = 5% = 0.05 for every 1 paid)
        uint maxDebt; // 9 decimal debt ratio, max % total supply created as debt
        uint soldBondsLimitUsd; //
        uint prematureReturnRate; // as % of premature return rate, in hundreds. ( 9500 = 95% = 0.95)
    }

    struct SoldBonds {
        uint timestampFrom;
        uint timestampTo;
        uint payoutInUsd;
    }

    /* ======== INITIALIZATION ======== */

    constructor (
        address _FHM,
        address _USDB,
        address _principle,
        address _treasury,
        address _DAO,
        address _usdbMinter
    ) {
        require( _FHM != address(0) );
        FHM = _FHM;
        require( _USDB != address(0) );
        USDB = _USDB;
        require( _principle != address(0) );
        principle = _principle;
        require( _treasury != address(0) );
        treasury = _treasury;
        require( _DAO != address(0) );
        DAO = _DAO;
        require( _usdbMinter != address(0) );
        usdbMinter = _usdbMinter;
        useWhitelist = true;
        whitelist[msg.sender] = true;
        usersCount = 0;
    }

    /**
     *  @notice initializes bond parameters
     *  @param _vestingTermSeconds uint
     *  @param _vestingTerm uint
     *  @param _discount uint
     *  @param _maxPayout uint
     *  @param _fee uint
     *  @param _maxDebt uint
     *  @param _initialDebt uint
     *  @param _soldBondsLimitUsd uint
     *  @param _useWhitelist bool
     *  @param _useCircuitBreaker bool
     *  @param _prematureReturnRate uint
     *  @param _limitBondCount uint
     */
    function initializeBondTerms(
        uint _vestingTermSeconds,
        uint _vestingTerm,
        uint _discount,
        uint _maxPayout,
        uint _fee,
        uint _maxDebt,
        uint _initialDebt,
        uint _soldBondsLimitUsd,
        bool _useWhitelist,
        bool _useCircuitBreaker,
        uint _prematureReturnRate,
        uint _limitBondCount
    ) external onlyPolicy() {
        terms = Terms ({
        vestingTermSeconds: _vestingTermSeconds,
        vestingTerm: _vestingTerm,
        discount: _discount,
        maxPayout: _maxPayout,
        fee: _fee,
        maxDebt: _maxDebt,
        soldBondsLimitUsd: _soldBondsLimitUsd,
        prematureReturnRate: _prematureReturnRate
        });
        totalDebt = _initialDebt;
        lastDecay = block.number;
        useWhitelist = _useWhitelist;
        useCircuitBreaker = _useCircuitBreaker;
        limitBondCount = _limitBondCount;
    }




    /* ======== POLICY FUNCTIONS ======== */

    enum PARAMETER { VESTING, PAYOUT, FEE, DEBT, VESTING_SECONDS }
    /**
     *  @notice set parameters for new bonds
     *  @param _parameter PARAMETER
     *  @param _input uint
     */
    function setBondTerms ( PARAMETER _parameter, uint _input ) external onlyPolicy() {
        if ( _parameter == PARAMETER.VESTING ) { // 0
            require( _input >= 10000, "Vesting must be longer than 10000 blocks" );
            terms.vestingTerm = _input;
        } else if ( _parameter == PARAMETER.PAYOUT ) { // 1
            require( _input <= 1000, "Payout cannot be above 1 percent" );
            terms.maxPayout = _input;
        } else if ( _parameter == PARAMETER.FEE ) { // 2
            require( _input <= 10000, "DAO fee cannot exceed payout" );
            terms.fee = _input;
        } else if ( _parameter == PARAMETER.DEBT ) { // 3
            terms.maxDebt = _input;
        } else if ( _parameter == PARAMETER.VESTING_SECONDS ) { // 4
            terms.vestingTermSeconds = _input;
        }
    }

    /* ======== USER FUNCTIONS ======== */

    /**
     *  @notice deposit bond
     *  @param _amount uint
     *  @param _maxPrice uint
     *  @param _depositor address
     *  @return uint
     */
    function deposit(
        uint _amount,
        uint _maxPrice,
        address _depositor
    ) external nonReentrant returns ( uint ) {
        require( _depositor != address(0), "Invalid address" );
        //check the user's bond count
        Bond[] memory _userBondInfo = depositors.values[_depositor];
        require(_userBondInfo.length < limitBondCount, "Exceed the limit amounts of bond");
        // allow only whitelisted contracts
        if (useWhitelist) require(whitelist[msg.sender], "SENDER_IS_NOT_IN_WHITELIST");

        decayDebt();
        require( totalDebt <= terms.maxDebt, "Max capacity reached" );

        uint priceInUSD = bondPriceInUSD(); // Stored in bond info
        uint nativePrice = bondPrice();

        require( _maxPrice >= nativePrice, "Slippage limit: more than max price" ); // slippage protection

        uint value = ITreasury( treasury ).valueOf( principle, _amount ).mul(10 ** 9);
        uint payout = payoutFor( value ); // payout to bonder is computed

        require( payout >= 10_000_000_000_000_000, "Bond too small" ); // must be > 0.01 USDB ( underflow protection )
        require( payout <= maxPayout(), "Bond too large"); // size protection because there is no slippage
        require( !circuitBreakerActivated(payout), "CIRCUIT_BREAKER_ACTIVE"); //

        uint payoutInFhm = payoutInFhmFor(payout);

        // profits are calculated
        uint fee = payoutInFhm.mul( terms.fee ).div( 10000 );

        IERC20( principle ).safeTransferFrom( msg.sender, address( DAO ), _amount );
        ITreasury( treasury ).mintRewards( address(this), payoutInFhm.add(fee));


        // mint USDB with guaranteed discount
        IMintable(USDB).mint( address(this), payout);

        // burn whatever FHM got from treasury in current market price
        IBurnable( FHM ).burn( payoutInFhm ) ;

        if ( fee != 0 ) { // fee is transferred to dao
            IERC20( FHM ).safeTransfer( DAO, fee );
        }

        // total debt is increased
        totalDebt = totalDebt.add( value );

        // update sold bonds
        if (useCircuitBreaker) updateSoldBonds(payout);

        // depositor info is stored
        _bondInfo = Bond({
            payout: payout,
            vestingSeconds: terms.vestingTermSeconds,
            lastTimestamp: block.timestamp,
            vesting: terms.vestingTerm,
            lastBlock: block.number,
            pricePaid: priceInUSD
        });

        // new user bonding
        if(!depositors.inserted[_depositor]) {
            usersCount ++;
        }
        depositors.set(_depositor, _bondInfo );

        // indexed events are emitted
        emit BondCreated( _amount, payout, block.timestamp.add(terms.vestingTermSeconds), block.number.add( terms.vestingTerm ), priceInUSD );

        return payout;
    }

    function redeemAll(uint _from, uint _to) public returns (uint[] memory) {
        require(_from >= 0 && _from < depositors.size(), "`from` is invalid");
        require(_to >= 0 && _to <= depositors.size(), "`to` is invalid");
        require(_from < _to, "`to` should be equal and greater than `from`");

        uint[] memory _removedIndices;
        uint _counter = 0;
        for (uint i = _from; i < _to; i++) {
            address _recipient = depositors.getKeyAtIndex(i);
            if (i > depositors.size()) {
                break;
            }
            if (redeemOne(_recipient)) {
                _removedIndices[_counter++] = i;
                depositors.remove(_recipient);
            }
        }
        return _removedIndices;
    }

    function redeem(uint[] memory _newIndices) public returns (uint[] memory) {
        require(_newIndices.length > 0, "Redeems unavailable");

        uint[] memory _removedIndices;
        uint _counter = 0;
        for (uint i = 0; i <= _newIndices.length; i ++) {
            address _recipient = depositors.getKeyAtIndex(_newIndices[i]);
            if (redeemOne(_recipient)) {
                _removedIndices[_counter++] = i;
                depositors.remove(_recipient);
            }
        }
        return _removedIndices;
    }

    function redeemOne(address _depositor) public returns (bool _toDelete) {
        Bond[] storage _userBondInfo = depositors.values[_depositor];
        require(_userBondInfo.length > 0, "There is no bonding");

        uint _finalAmount = 0;
        uint _length = _userBondInfo.length;

        uint index = 0;
        while(index < _length) {
            uint percentVested = percentVestedFor(_depositor, index); // (seconds since last interaction / vesting term remaining)
            uint percentVestedBlocks = percentVestedBlocksFor(_depositor, index); // (blocks since last interaction / vesting term remaining)

            if (percentVested >= 10000 && percentVestedBlocks >= 10000) {
                Bond memory removeMe;
                removeMe = _userBondInfo[index];
                _userBondInfo[index] = _userBondInfo[_length - 1];
                _userBondInfo[_length - 1] = removeMe;
                _userBondInfo.pop();
                _finalAmount = _finalAmount.add(removeMe.payout);
                _length --;
            } else {
                index ++;
            }
        }
        if (_length == 0) {
            _toDelete = true;
        }

        if (_finalAmount > 0) {
            IERC20(USDB).transfer(_depositor, _finalAmount);
            emit BondRedeemed(_depositor, _finalAmount, 0, 0);
        }

        return _toDelete;
    }

    /**
     *  @notice returns asset when depositor wants to cancel bond
     *  @param _depositor address
     *  @param index uint
     *  @return forfeited uint
     */
    function cancelBond( address _depositor, uint index ) public onlyDepositor(_depositor) returns (uint forfeited) {
        Bond[] storage infos = depositors.values[_depositor];
        uint _length = infos.length;
        require(index < _length, "INDEX_OUT_OF_BOUNDS");

        uint percentVested = percentVestedFor( _depositor, index );
        require( percentVested < 10000, "Current bond is already finished." );

        Bond storage info = infos[index];
        uint possiblePayout = info.payout;
        uint _deposit = possiblePayout.mul(info.pricePaid).div(1e18);
        forfeited = _deposit.mul(terms.prematureReturnRate).div(10000);

        require( _deposit >= forfeited, "prematureReturnRate is too big." );

        uint remainingBalance = possiblePayout.sub(forfeited);

        if (_length >= 2 && index != _length - 1) {
            infos[index] = infos[_length - 1];
            infos[_length - 1] = info;
            infos.pop();
        } else if (_length >= 2 && index == _length - 1) {
            infos.pop();
        } else {
            depositors.remove(_depositor);
        }

        IERC20( USDB ).transfer(_depositor, forfeited);
        IERC20( USDB ).transfer(DAO, remainingBalance);

        emit BondCancelled(_depositor, index, _deposit, possiblePayout, forfeited);
    }


    /**
     *  @notice return bond info
     *  @param _depositor address
     *  @param index uint
     *  @return payout uint
     *  @return vestingSeconds uint
     *  @return lastTimestamp uint
     *  @return vesting uint
     *  @return lastBlock uint
     *  @return pricePaid uint
     */
    function bondInfo(address _depositor, uint index) public view returns ( uint payout, uint vestingSeconds, uint lastTimestamp, uint vesting,uint lastBlock,uint pricePaid ) {
        Bond memory info = depositors.get(_depositor, index);
        payout = info.payout;
        vestingSeconds = info.vestingSeconds;
        lastTimestamp = info.lastTimestamp;
        vesting = info.vesting;
        lastBlock = info.lastBlock;
        pricePaid = info.pricePaid;
    }

    /* ======== INTERNAL HELPER FUNCTIONS ======== */

    function modifyWhitelist(address user, bool add) external onlyPolicy {
        if (add) {
            require(!whitelist[user], "ALREADY_IN_WHITELIST");
            whitelist[user] = true;
        } else {
            require(whitelist[user], "NOT_IN_WHITELIST");
            delete whitelist[user];
        }
    }

    function updateSoldBonds(uint _payout) internal {
        uint length = soldBondsInHour.length;
        if (length == 0) {
            soldBondsInHour.push(SoldBonds({
            timestampFrom: block.timestamp,
            timestampTo: block.timestamp + 1 hours,
            payoutInUsd: _payout
            }));
            return;
        }

        SoldBonds storage soldBonds = soldBondsInHour[length - 1];
        // update in existing interval
        if (soldBonds.timestampFrom < block.timestamp && soldBonds.timestampTo >= block.timestamp) {
            soldBonds.payoutInUsd = soldBonds.payoutInUsd.add(_payout);
        } else {
            // create next interval if its continuous
            uint nextTo = soldBonds.timestampTo + 1 hours;
            if (block.timestamp <= nextTo) {
                soldBondsInHour.push(SoldBonds({
                timestampFrom: soldBonds.timestampTo,
                timestampTo: nextTo,
                payoutInUsd: _payout
                }));
            } else {
                soldBondsInHour.push(SoldBonds({
                timestampFrom: block.timestamp,
                timestampTo: block.timestamp + 1 hours,
                payoutInUsd: _payout
                }));
            }
        }
    }

    function circuitBreakerCurrentPayout() public view returns (uint _amount) {
        if (soldBondsInHour.length == 0) return 0;

        uint max = 0;
        if (soldBondsInHour.length >= 24) max = soldBondsInHour.length - 24;

        uint to = block.timestamp;
        uint from = to - 24 hours;
        for (uint i = max; i < soldBondsInHour.length; i++) {
            SoldBonds memory soldBonds = soldBondsInHour[i];
            if (soldBonds.timestampFrom >= from && soldBonds.timestampFrom <= to) {
                _amount = _amount.add(soldBonds.payoutInUsd);
            }
        }

        return _amount;
    }

    function circuitBreakerActivated(uint payout) public view returns (bool) {
        if (!useCircuitBreaker) return false;
        payout = payout.add(circuitBreakerCurrentPayout());
        return payout > terms.soldBondsLimitUsd;
    }

    function getMarketPrice() public view returns (uint _marketPrice) {
        _marketPrice = IUsdbMinter(usdbMinter).getMarketPrice();
    }

    /**
     *  @notice reduce total debt
     */
    function decayDebt() internal {
        totalDebt = totalDebt.sub( debtDecay() );
        lastDecay = block.number;
    }




    /* ======== VIEW FUNCTIONS ======== */

    /**
     *  @notice determine maximum bond size
     *  @return uint
     */
    function maxPayout() public view returns ( uint ) {
        return IERC20(USDB).totalSupply().mul( terms.maxPayout ).div( 100000 );
    }

    /**
     *  @notice calculate interest due for new bond
     *  @param _value uint
     *  @return uint
     */
    function payoutFor( uint _value ) public view returns ( uint ) {
        return FixedPoint.fraction( _value, bondPriceInUSD() ).decode112with18();
    }

    function payoutInFhmFor( uint _usdbValue) public view returns ( uint ) {
        return FixedPoint.fraction( _usdbValue, getMarketPrice()).decode112with18().div( 1e16 ).div(1e9);
    }

    /**
    *  @notice converts bond price to DAI value
     *  @return price_ uint
     */
    function bondPriceInUSD() public view returns ( uint price_ ) {
        uint _originalPrice = 1;
        _originalPrice = _originalPrice.mul( 10 ** 18 );

        uint _discount = _originalPrice.mul(terms.discount).div(10 ** 5);
        price_ = _originalPrice.sub(_discount);
    }


    /**
     *  @notice calculate current bond premium
     *  @return price_ uint
     */
    function bondPrice() public view returns ( uint price_ ) {
       return bondPriceInUSD().div(1e16);
    }

    /**
     *  @notice calculate current ratio of debt to USDB supply
     *  @return debtRatio_ uint
     */
    function debtRatio() public view returns ( uint debtRatio_ ) {
        uint supply = IERC20(USDB).totalSupply();
        debtRatio_ = FixedPoint.fraction(
            currentDebt().mul( 1e9 ),
            supply
        ).decode112with18().div( 1e18 );
    }

    /**
     *  @notice debt ratio in same terms for reserve or liquidity bonds
     *  @return uint
     */
    function standardizedDebtRatio() external view returns ( uint ) {
        return debtRatio();
    }

    /**
     *  @notice calculate debt factoring in decay
     *  @return uint
     */
    function currentDebt() public view returns ( uint ) {
        return totalDebt.sub( debtDecay() );
    }

    /**
     *  @notice amount to decay total debt by
     *  @return decay_ uint
     */
    function debtDecay() public view returns ( uint decay_ ) {
        uint blocksSinceLast = block.number.sub( lastDecay );
        decay_ = totalDebt.mul( blocksSinceLast ).div( terms.vestingTerm );
        if ( decay_ > totalDebt ) {
            decay_ = totalDebt;
        }
    }


    /**
     *  @notice calculate how far into vesting a depositor is
     *  @param _depositor address
     *  @param index uint
     *  @return percentVested_ uint
     */
    function percentVestedFor( address _depositor, uint index ) public view returns ( uint percentVested_ ) {
        Bond memory bond = depositors.get(_depositor, index);
        uint secondsSinceLast = block.timestamp.sub( bond.lastTimestamp );
        uint vestingSeconds = bond.vestingSeconds;

        if ( vestingSeconds > 0 ) {
            percentVested_ = secondsSinceLast.mul( 10000 ).div(vestingSeconds);
        } else {
            percentVested_ = 0;
        }
    }

    function percentVestedBlocksFor( address _depositor, uint index ) public view returns ( uint percentVested_ ) {
        Bond memory bond = depositors.get(_depositor, index);
        uint blocksSinceLast = block.number.sub( bond.lastBlock );
        uint vesting = bond.vesting;

        if ( vesting > 0 ) {
            percentVested_ = blocksSinceLast.mul( 10000 ).div( vesting );
        } else {
            percentVested_ = 0;
        }
    }

    /**
     *  @notice users' bond length
     *  @param _depositor address
     *  @return  uint
     */
     function bondlength( address _depositor) external view returns (uint) {
        return depositors.values[_depositor].length;
     }
    /**
     *  @notice calculate amount of FHM available for claim by depositor
     *  @param _depositor address
     *  @param index uint
     *  @return pendingPayout_ uint
     */
    function pendingPayoutFor( address _depositor, uint index ) external view returns ( uint pendingPayout_ ) {
        uint percentVested = percentVestedFor( _depositor, index );
        uint percentVestedBlocks = percentVestedBlocksFor( _depositor, index );
        uint payout = depositors.get(_depositor, index).payout;

        if ( percentVested >= 10000 && percentVestedBlocks >= 10000) {
            pendingPayout_ = payout;
        } else {
            pendingPayout_ = 0;
        }
    }

    /* ======= AUXILLIARY ======= */

    /**
     *  @notice allow anyone to send lost tokens (excluding principle or FHM) to the DAO
     *  @return bool
     */
    function recoverLostToken( address _token ) external returns ( bool ) {
        require( _token != FHM );
        require( _token != USDB);
        require( _token != principle );
        IERC20( _token ).safeTransfer( DAO, IERC20( _token ).balanceOf( address(this) ) );
        return true;
    }
}