ETH Price: $2,059.72 (+5.52%)

Transaction Decoder

Block:
10850369 at Sep-13-2020 12:29:09 AM +UTC
Transaction Fee:
0.01320752 ETH $27.20
Gas Used:
136,160 Gas / 97 Gwei

Emitted Events:

232 bZxProtocol.0x40a75ae5f7a5336e75f7c7977e12c4b46a9ac0f30de01a2d5b6c1a4f4af63587( 0x40a75ae5f7a5336e75f7c7977e12c4b46a9ac0f30de01a2d5b6c1a4f4af63587, 0x0000000000000000000000006b093998d36f2c7f0cc359441fbb24cc629d5ff0, 0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f, 00000000000000000000000000000000000000000000000001be8862cbaf8ae0 )
233 Dai.Transfer( src=bZxProtocol, dst=[Receiver] 0x6b093998d36f2c7f0cc359441fbb24cc629d5ff0, wad=1131190177665638878 )
234 bZxProtocol.0xc44aeefa68e8b9c1ad5f7be4b0dd194580f81f5c362862e72196503a320eb7a1( 0xc44aeefa68e8b9c1ad5f7be4b0dd194580f81f5c362862e72196503a320eb7a1, 0x0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f, 0x0000000000000000000000006b093998d36f2c7f0cc359441fbb24cc629d5ff0, 0000000000000000000000000000000000000000000000000fb2cb79292be1de )
235 Dai.Transfer( src=[Sender] 0x30792938dff4175000c4e9faff35894713e1447e, dst=[Receiver] 0x6b093998d36f2c7f0cc359441fbb24cc629d5ff0, wad=15000246600792447651048 )
236 0x6b093998d36f2c7f0cc359441fbb24cc629d5ff0.0xb4c03061fb5b7fed76389d5af8f2e0ddb09f8c70d1333abbb62582835e10accb( 0xb4c03061fb5b7fed76389d5af8f2e0ddb09f8c70d1333abbb62582835e10accb, 0x00000000000000000000000030792938dff4175000c4e9faff35894713e1447e, 0000000000000000000000000000000000000000000003234312e06e80475737, 00000000000000000000000000000000000000000000032d2a3d48b5468c0ce8, 0000000000000000000000000000000000000000000000000e0c83610d10ae22 )
237 0x6b093998d36f2c7f0cc359441fbb24cc629d5ff0.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000030792938dff4175000c4e9faff35894713e1447e, 0000000000000000000000000000000000000000000003234312e06e80475737 )

Account State Difference:

  Address   Before After State Difference Code
0x30792938...713e1447e
0.385241009813165593 Eth
Nonce: 12
0.372033489813165593 Eth
Nonce: 13
0.01320752
(Spark Pool)
83.293088112513628421 Eth83.306295632513628421 Eth0.01320752
0x6b093998...C629D5FF0
0x6B175474...495271d0F
0xD8Ee6965...F584F0b7f
(bZx: bZx Protocol)

Execution Trace

0x6b093998d36f2c7f0cc359441fbb24cc629d5ff0.40c10f19( )
  • LoanTokenLogicStandard.mint( receiver=0x30792938Dff4175000c4e9faff35894713e1447e, depositAmount=15000246600792447651048 ) => ( 14817568663304095094583 )
    • bZxProtocol.e81fefa0( )
      • LoanMaintenance.withdrawAccruedInterest( loanToken=0x6B175474E89094C44Da98b954EedeAC495271d0F )
        • Dai.transfer( dst=0x6b093998D36f2C7F0cc359441FBB24CC629D5FF0, wad=1131190177665638878 ) => ( True )
        • bZxProtocol.4a1e88fe( )
          • LoanSettings.getTotalPrincipal( lender=0x6b093998D36f2C7F0cc359441FBB24CC629D5FF0, loanToken=0x6B175474E89094C44Da98b954EedeAC495271d0F ) => ( 1078996385099850342806824 )
          • Dai.balanceOf( 0x6b093998D36f2C7F0cc359441FBB24CC629D5FF0 ) => ( 17855291950358123346699 )
          • Dai.transferFrom( src=0x30792938Dff4175000c4e9faff35894713e1447e, dst=0x6b093998D36f2C7F0cc359441FBB24CC629D5FF0, wad=15000246600792447651048 ) => ( True )
            File 1 of 5: bZxProtocol
            /**
             * Copyright 2017-2020, bZeroX, LLC <https://bzx.network/>. All Rights Reserved.
             * Licensed under the Apache License, Version 2.0.
             */
            
            pragma solidity 0.5.17;
            pragma experimental ABIEncoderV2;
            
            
            interface IWeth {
                function deposit() external payable;
                function withdraw(uint256 wad) external;
            }
            
            contract IERC20 {
                string public name;
                uint8 public decimals;
                string public symbol;
                function totalSupply() public view returns (uint256);
                function balanceOf(address _who) public view returns (uint256);
                function allowance(address _owner, address _spender) public view returns (uint256);
                function approve(address _spender, uint256 _value) public returns (bool);
                function transfer(address _to, uint256 _value) public returns (bool);
                function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
                event Transfer(address indexed from, address indexed to, uint256 value);
                event Approval(address indexed owner, address indexed spender, uint256 value);
            }
            
            contract IWethERC20 is IWeth, IERC20 {}
            
            contract Constants {
            
                uint256 internal constant WEI_PRECISION = 10**18;
                uint256 internal constant WEI_PERCENT_PRECISION = 10**20;
            
                uint256 internal constant DAYS_IN_A_YEAR = 365;
                uint256 internal constant ONE_MONTH = 2628000; // approx. seconds in a month
            
                IWethERC20 public constant wethToken = IWethERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
                address public constant bzrxTokenAddress = 0x56d811088235F11C8920698a204A5010a788f4b3;
                address public constant vbzrxTokenAddress = 0xB72B31907C1C95F3650b64b2469e08EdACeE5e8F;
            }
            
            /**
             * @dev Library for managing loan sets
             *
             * Sets have the following properties:
             *
             * - Elements are added, removed, and checked for existence in constant time
             * (O(1)).
             * - Elements are enumerated in O(n). No guarantees are made on the ordering.
             *
             * Include with `using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;`.
             *
             */
            library EnumerableBytes32Set {
            
                struct Bytes32Set {
                    // Position of the value in the `values` array, plus 1 because index 0
                    // means a value is not in the set.
                    mapping (bytes32 => uint256) index;
                    bytes32[] values;
                }
            
                /**
                 * @dev Add an address value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return addBytes32(set, value);
                }
            
                /**
                 * @dev Add a value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (!contains(set, value)){
                        set.index[value] = set.values.push(value);
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Removes an address value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return removeBytes32(set, value);
                }
            
                /**
                 * @dev Removes a value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (contains(set, value)){
                        uint256 toDeleteIndex = set.index[value] - 1;
                        uint256 lastIndex = set.values.length - 1;
            
                        // If the element we're deleting is the last one, we can just remove it without doing a swap
                        if (lastIndex != toDeleteIndex) {
                            bytes32 lastValue = set.values[lastIndex];
            
                            // Move the last value to the index where the deleted value is
                            set.values[toDeleteIndex] = lastValue;
                            // Update the index for the moved value
                            set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based
                        }
            
                        // Delete the index entry for the deleted value
                        delete set.index[value];
            
                        // Delete the old entry for the moved value
                        set.values.pop();
            
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(Bytes32Set storage set, bytes32 value)
                    internal
                    view
                    returns (bool)
                {
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function containsAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    view
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns an array with all values in the set. O(N).
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
            
                 * WARNING: This function may run out of gas on large sets: use {length} and
                 * {get} instead in these cases.
                 */
                function enumerate(Bytes32Set storage set, uint256 start, uint256 count)
                    internal
                    view
                    returns (bytes32[] memory output)
                {
                    uint256 end = start + count;
                    require(end >= start, "addition overflow");
                    end = set.values.length < end ? set.values.length : end;
                    if (end == 0 || start >= end) {
                        return output;
                    }
            
                    output = new bytes32[](end-start);
                    for (uint256 i = start; i < end; i++) {
                        output[i-start] = set.values[i];
                    }
                    return output;
                }
            
                /**
                 * @dev Returns the number of elements on the set. O(1).
                 */
                function length(Bytes32Set storage set)
                    internal
                    view
                    returns (uint256)
                {
                    return set.values.length;
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function get(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (bytes32)
                {
                    return set.values[index];
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function getAddress(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (address)
                {
                    bytes32 value = set.values[index];
                    address addrvalue;
                    assembly {
                        addrvalue := value
                    }
                    return addrvalue;
                }
            }
            
            /**
             * @title Helps contracts guard against reentrancy attacks.
             * @author Remco Bloemen <remco@2π.com>, Eenae <alexey@mixbytes.io>
             * @dev If you mark a function `nonReentrant`, you should also
             * mark it `external`.
             */
            contract ReentrancyGuard {
            
                /// @dev Constant for unlocked guard state - non-zero to prevent extra gas costs.
                /// See: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1056
                uint256 internal constant REENTRANCY_GUARD_FREE = 1;
            
                /// @dev Constant for locked guard state
                uint256 internal constant REENTRANCY_GUARD_LOCKED = 2;
            
                /**
                * @dev We use a single lock for the whole contract.
                */
                uint256 internal reentrancyLock = REENTRANCY_GUARD_FREE;
            
                /**
                * @dev Prevents a contract from calling itself, directly or indirectly.
                * If you mark a function `nonReentrant`, you should also
                * mark it `external`. Calling one `nonReentrant` function from
                * another is not supported. Instead, you can implement a
                * `private` function doing the actual work, and an `external`
                * wrapper marked as `nonReentrant`.
                */
                modifier nonReentrant() {
                    require(reentrancyLock == REENTRANCY_GUARD_FREE, "nonReentrant");
                    reentrancyLock = REENTRANCY_GUARD_LOCKED;
                    _;
                    reentrancyLock = REENTRANCY_GUARD_FREE;
                }
            }
            
            /*
             * @dev Provides information about the current execution context, including the
             * sender of the transaction and its data. While these are generally available
             * via msg.sender and msg.data, they should not be accessed in such a direct
             * manner, since when dealing with GSN meta-transactions the account sending and
             * paying for execution may not be the actual sender (as far as an application
             * is concerned).
             *
             * This contract is only required for intermediate, library-like contracts.
             */
            contract Context {
                // Empty internal constructor, to prevent people from mistakenly deploying
                // an instance of this contract, which should be used via inheritance.
                constructor () internal { }
                // solhint-disable-previous-line no-empty-blocks
            
                function _msgSender() internal view returns (address payable) {
                    return msg.sender;
                }
            
                function _msgData() internal view returns (bytes memory) {
                    this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                    return msg.data;
                }
            }
            
            /**
             * @dev Contract module which provides a basic access control mechanism, where
             * there is an account (an owner) that can be granted exclusive access to
             * specific functions.
             *
             * This module is used through inheritance. It will make available the modifier
             * `onlyOwner`, which can be applied to your functions to restrict their use to
             * the owner.
             */
            contract Ownable is Context {
                address private _owner;
            
                event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            
                /**
                 * @dev Initializes the contract setting the deployer as the initial owner.
                 */
                constructor () internal {
                    address msgSender = _msgSender();
                    _owner = msgSender;
                    emit OwnershipTransferred(address(0), msgSender);
                }
            
                /**
                 * @dev Returns the address of the current owner.
                 */
                function owner() public view returns (address) {
                    return _owner;
                }
            
                /**
                 * @dev Throws if called by any account other than the owner.
                 */
                modifier onlyOwner() {
                    require(isOwner(), "unauthorized");
                    _;
                }
            
                /**
                 * @dev Returns true if the caller is the current owner.
                 */
                function isOwner() public view returns (bool) {
                    return _msgSender() == _owner;
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Can only be called by the current owner.
                 */
                function transferOwnership(address newOwner) public onlyOwner {
                    _transferOwnership(newOwner);
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 */
                function _transferOwnership(address newOwner) internal {
                    require(newOwner != address(0), "Ownable: new owner is the zero address");
                    emit OwnershipTransferred(_owner, newOwner);
                    _owner = newOwner;
                }
            }
            
            /**
             * @dev Wrappers over Solidity's arithmetic operations with added overflow
             * checks.
             *
             * Arithmetic operations in Solidity wrap on overflow. This can easily result
             * in bugs, because programmers usually assume that an overflow raises an
             * error, which is the standard behavior in high level programming languages.
             * `SafeMath` restores this intuition by reverting the transaction when an
             * operation overflows.
             *
             * Using this library instead of the unchecked operations eliminates an entire
             * class of bugs, so it's recommended to use it always.
             */
            library SafeMath {
                /**
                 * @dev Returns the addition of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `+` operator.
                 *
                 * Requirements:
                 * - Addition cannot overflow.
                 */
                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                    uint256 c = a + b;
                    require(c >= a, "SafeMath: addition overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 */
                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                    return sub(a, b, "SafeMath: subtraction overflow");
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 *
                 * _Available since v2.4.0._
                 */
                function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b <= a, errorMessage);
                    uint256 c = a - b;
            
                    return c;
                }
            
                /**
                 * @dev Returns the multiplication of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `*` operator.
                 *
                 * Requirements:
                 * - Multiplication cannot overflow.
                 */
                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) {
                        return 0;
                    }
            
                    uint256 c = a * b;
                    require(c / a == b, "SafeMath: multiplication overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                    return div(a, b, "SafeMath: division by zero");
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
                    uint256 c = a / b;
                    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            
                    return c;
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
                    return divCeil(a, b, "SafeMath: division by zero");
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
            
                    if (a == 0) {
                        return 0;
                    }
                    uint256 c = ((a - 1) / b) + 1;
            
                    return c;
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                    return mod(a, b, "SafeMath: modulo by zero");
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts with custom message when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b != 0, errorMessage);
                    return a % b;
                }
            
                function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
                    return _a < _b ? _a : _b;
                }
            }
            
            /**
             * @dev Collection of functions related to the address type
             */
            library Address {
                /**
                 * @dev Returns true if `account` is a contract.
                 *
                 * [IMPORTANT]
                 * ====
                 * It is unsafe to assume that an address for which this function returns
                 * false is an externally-owned account (EOA) and not a contract.
                 *
                 * Among others, `isContract` will return false for the following 
                 * types of addresses:
                 *
                 *  - an externally-owned account
                 *  - a contract in construction
                 *  - an address where a contract will be created
                 *  - an address where a contract lived, but was destroyed
                 * ====
                 */
                function isContract(address account) internal view returns (bool) {
                    // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                    // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                    // for accounts without code, i.e. `keccak256('')`
                    bytes32 codehash;
                    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                    // solhint-disable-next-line no-inline-assembly
                    assembly { codehash := extcodehash(account) }
                    return (codehash != accountHash && codehash != 0x0);
                }
            
                /**
                 * @dev Converts an `address` into `address payable`. Note that this is
                 * simply a type cast: the actual underlying value is not changed.
                 *
                 * _Available since v2.4.0._
                 */
                function toPayable(address account) internal pure returns (address payable) {
                    return address(uint160(account));
                }
            
                /**
                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                 * `recipient`, forwarding all available gas and reverting on errors.
                 *
                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                 * imposed by `transfer`, making them unable to receive funds via
                 * `transfer`. {sendValue} removes this limitation.
                 *
                 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                 *
                 * IMPORTANT: because control is transferred to `recipient`, care must be
                 * taken to not create reentrancy vulnerabilities. Consider using
                 * {ReentrancyGuard} or the
                 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                 *
                 * _Available since v2.4.0._
                 */
                function sendValue(address recipient, uint256 amount) internal {
                    require(address(this).balance >= amount, "Address: insufficient balance");
            
                    // solhint-disable-next-line avoid-call-value
                    (bool success, ) = recipient.call.value(amount)("");
                    require(success, "Address: unable to send value, recipient may have reverted");
                }
            }
            
            /**
             * @title SafeERC20
             * @dev Wrappers around ERC20 operations that throw on failure (when the token
             * contract returns false). Tokens that return no value (and instead revert or
             * throw on failure) are also supported, non-reverting calls are assumed to be
             * successful.
             * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
             * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
             */
            library SafeERC20 {
                using SafeMath for uint256;
                using Address for address;
            
                function safeTransfer(IERC20 token, address to, uint256 value) internal {
                    callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                }
            
                function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                    callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                }
            
                function safeApprove(IERC20 token, address spender, uint256 value) internal {
                    // safeApprove should only be called when setting an initial allowance,
                    // or when resetting it to zero. To increase and decrease it, use
                    // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                    // solhint-disable-next-line max-line-length
                    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));
                }
            
                /**
                 * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                 * on the return value: the return value is optional (but if data is returned, it must not be false).
                 * @param token The token targeted by the call.
                 * @param data The call data (encoded using abi.encode or one of its variants).
                 */
                function callOptionalReturn(IERC20 token, bytes memory data) private {
                    // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                    // we're implementing it ourselves.
            
                    // A Solidity high level call has three parts:
                    //  1. The target address is checked to verify it contains contract code
                    //  2. The call itself is made, and success asserted
                    //  3. The return value is decoded, which in turn checks the size of the returned data.
                    // solhint-disable-next-line max-line-length
                    require(address(token).isContract(), "SafeERC20: call to non-contract");
            
                    // solhint-disable-next-line avoid-low-level-calls
                    (bool success, bytes memory returndata) = address(token).call(data);
                    require(success, "SafeERC20: low-level call failed");
            
                    if (returndata.length > 0) { // Return data is optional
                        // solhint-disable-next-line max-line-length
                        require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                    }
                }
            }
            
            contract LoanStruct {
                struct Loan {
                    bytes32 id;                 // id of the loan
                    bytes32 loanParamsId;       // the linked loan params id
                    bytes32 pendingTradesId;    // the linked pending trades id
                    uint256 principal;          // total borrowed amount outstanding
                    uint256 collateral;         // total collateral escrowed for the loan
                    uint256 startTimestamp;     // loan start time
                    uint256 endTimestamp;       // for active loans, this is the expected loan end time, for in-active loans, is the actual (past) end time
                    uint256 startMargin;        // initial margin when the loan opened
                    uint256 startRate;          // reference rate when the loan opened for converting collateralToken to loanToken
                    address borrower;           // borrower of this loan
                    address lender;             // lender of this loan
                    bool active;                // if false, the loan has been fully closed
                }
            }
            
            contract LoanParamsStruct {
                struct LoanParams {
                    bytes32 id;                 // id of loan params object
                    bool active;                // if false, this object has been disabled by the owner and can't be used for future loans
                    address owner;              // owner of this object
                    address loanToken;          // the token being loaned
                    address collateralToken;    // the required collateral token
                    uint256 minInitialMargin;   // the minimum allowed initial margin
                    uint256 maintenanceMargin;  // an unhealthy loan when current margin is at or below this value
                    uint256 maxLoanTerm;        // the maximum term for new loans (0 means there's no max term)
                }
            }
            
            contract OrderStruct {
                struct Order {
                    uint256 lockedAmount;           // escrowed amount waiting for a counterparty
                    uint256 interestRate;           // interest rate defined by the creator of this order
                    uint256 minLoanTerm;            // minimum loan term allowed
                    uint256 maxLoanTerm;            // maximum loan term allowed
                    uint256 createdTimestamp;       // timestamp when this order was created
                    uint256 expirationTimestamp;    // timestamp when this order expires
                }
            }
            
            contract LenderInterestStruct {
                struct LenderInterest {
                    uint256 principalTotal;     // total borrowed amount outstanding of asset
                    uint256 owedPerDay;         // interest owed per day for all loans of asset
                    uint256 owedTotal;          // total interest owed for all loans of asset (assuming they go to full term)
                    uint256 paidTotal;          // total interest paid so far for asset
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract LoanInterestStruct {
                struct LoanInterest {
                    uint256 owedPerDay;         // interest owed per day for loan
                    uint256 depositTotal;       // total escrowed interest for loan
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract Objects is
                LoanStruct,
                LoanParamsStruct,
                OrderStruct,
                LenderInterestStruct,
                LoanInterestStruct
            {}
            
            contract State is Constants, Objects, ReentrancyGuard, Ownable {
                using SafeMath for uint256;
                using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;
            
                address public priceFeeds;                                                          // handles asset reference price lookups
                address public swapsImpl;                                                           // handles asset swaps using dex liquidity
            
                mapping (bytes4 => address) public logicTargets;                                    // implementations of protocol functions
            
                mapping (bytes32 => Loan) public loans;                                             // loanId => Loan
                mapping (bytes32 => LoanParams) public loanParams;                                  // loanParamsId => LoanParams
            
                mapping (address => mapping (bytes32 => Order)) public lenderOrders;                // lender => orderParamsId => Order
                mapping (address => mapping (bytes32 => Order)) public borrowerOrders;              // borrower => orderParamsId => Order
            
                mapping (bytes32 => mapping (address => bool)) public delegatedManagers;            // loanId => delegated => approved
            
                // Interest
                mapping (address => mapping (address => LenderInterest)) public lenderInterest;     // lender => loanToken => LenderInterest object
                mapping (bytes32 => LoanInterest) public loanInterest;                              // loanId => LoanInterest object
            
                // Internals
                EnumerableBytes32Set.Bytes32Set internal logicTargetsSet;                           // implementations set
                EnumerableBytes32Set.Bytes32Set internal activeLoansSet;                            // active loans set
            
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal lenderLoanSets;       // lender loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal borrowerLoanSets;     // borrow loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal userLoanParamSets;    // user loan params set
            
                address public feesController;                                                      // address controlling fee withdrawals
            
                uint256 public lendingFeePercent = 10 ether; // 10% fee                             // fee taken from lender interest payments
                mapping (address => uint256) public lendingFeeTokensHeld;                           // total interest fees received and not withdrawn per asset
                mapping (address => uint256) public lendingFeeTokensPaid;                           // total interest fees withdraw per asset (lifetime fees = lendingFeeTokensHeld + lendingFeeTokensPaid)
            
                uint256 public tradingFeePercent = 0.15 ether; // 0.15% fee                         // fee paid for each trade
                mapping (address => uint256) public tradingFeeTokensHeld;                           // total trading fees received and not withdrawn per asset
                mapping (address => uint256) public tradingFeeTokensPaid;                           // total trading fees withdraw per asset (lifetime fees = tradingFeeTokensHeld + tradingFeeTokensPaid)
            
                uint256 public borrowingFeePercent = 0.09 ether; // 0.09% fee                       // origination fee paid for each loan
                mapping (address => uint256) public borrowingFeeTokensHeld;                         // total borrowing fees received and not withdrawn per asset
                mapping (address => uint256) public borrowingFeeTokensPaid;                         // total borrowing fees withdraw per asset (lifetime fees = borrowingFeeTokensHeld + borrowingFeeTokensPaid)
            
                uint256 public protocolTokenHeld;                                                   // current protocol token deposit balance
                uint256 public protocolTokenPaid;                                                   // lifetime total payout of protocol token
            
                uint256 public affiliateFeePercent = 30 ether; // 30% fee share                     // fee share for affiliate program
            
                mapping (address => uint256) public liquidationIncentivePercent;                    // percent discount on collateral for liquidators per collateral asset
            
                mapping (address => address) public loanPoolToUnderlying;                           // loanPool => underlying
                mapping (address => address) public underlyingToLoanPool;                           // underlying => loanPool
                EnumerableBytes32Set.Bytes32Set internal loanPoolsSet;                              // loan pools set
            
                mapping (address => bool) public supportedTokens;                                   // supported tokens for swaps
            
                uint256 public maxDisagreement = 5 ether;                                           // % disagreement between swap rate and reference rate
            
                uint256 public sourceBufferPercent = 5 ether;                                       // used to estimate kyber swap source amount
            
                uint256 public maxSwapSize = 1500 ether;                                            // maximum supported swap size in ETH
            
            
                function _setTarget(
                    bytes4 sig,
                    address target)
                    internal
                {
                    logicTargets[sig] = target;
            
                    if (target != address(0)) {
                        logicTargetsSet.addBytes32(bytes32(sig));
                    } else {
                        logicTargetsSet.removeBytes32(bytes32(sig));
                    }
                }
            }
            
            contract bZxProtocol is State {
            
                function()
                    external
                    payable
                {
                    if (gasleft() <= 2300) {
                        return;
                    }
            
                    address target = logicTargets[msg.sig];
                    require(target != address(0), "target not active");
            
                    bytes memory data = msg.data;
                    assembly {
                        let result := delegatecall(gas, target, add(data, 0x20), mload(data), 0, 0)
                        let size := returndatasize
                        let ptr := mload(0x40)
                        returndatacopy(ptr, 0, size)
                        switch result
                        case 0 { revert(ptr, size) }
                        default { return(ptr, size) }
                    }
                }
            
                function replaceContract(
                    address target)
                    external
                    onlyOwner
                {
                    (bool success,) = target.delegatecall(abi.encodeWithSignature("initialize(address)", target));
                    require(success, "setup failed");
                }
            
                function setTargets(
                    string[] calldata sigsArr,
                    address[] calldata targetsArr)
                    external
                    onlyOwner
                {
                    require(sigsArr.length == targetsArr.length, "count mismatch");
            
                    for (uint256 i = 0; i < sigsArr.length; i++) {
                        _setTarget(bytes4(keccak256(abi.encodePacked(sigsArr[i]))), targetsArr[i]);
                    }
                }
            
                function getTarget(
                    string calldata sig)
                    external
                    view
                    returns (address)
                {
                    return logicTargets[bytes4(keccak256(abi.encodePacked(sig)))];
                }
            }

            File 2 of 5: Dai
            // hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
            pragma solidity =0.5.12;
            
            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU General Public License for more details.
            
            // You should have received a copy of the GNU General Public License
            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
            
            /* pragma solidity 0.5.12; */
            
            contract LibNote {
                event LogNote(
                    bytes4   indexed  sig,
                    address  indexed  usr,
                    bytes32  indexed  arg1,
                    bytes32  indexed  arg2,
                    bytes             data
                ) anonymous;
            
                modifier note {
                    _;
                    assembly {
                        // log an 'anonymous' event with a constant 6 words of calldata
                        // and four indexed topics: selector, caller, arg1 and arg2
                        let mark := msize                         // end of memory ensures zero
                        mstore(0x40, add(mark, 288))              // update free memory pointer
                        mstore(mark, 0x20)                        // bytes type data offset
                        mstore(add(mark, 0x20), 224)              // bytes size (padded)
                        calldatacopy(add(mark, 0x40), 0, 224)     // bytes payload
                        log4(mark, 288,                           // calldata
                             shl(224, shr(224, calldataload(0))), // msg.sig
                             caller,                              // msg.sender
                             calldataload(4),                     // arg1
                             calldataload(36)                     // arg2
                            )
                    }
                }
            }
            
            ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol
            // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity 0.5.12; */
            
            /* import "./lib.sol"; */
            
            contract Dai is LibNote {
                // --- Auth ---
                mapping (address => uint) public wards;
                function rely(address guy) external note auth { wards[guy] = 1; }
                function deny(address guy) external note auth { wards[guy] = 0; }
                modifier auth {
                    require(wards[msg.sender] == 1, "Dai/not-authorized");
                    _;
                }
            
                // --- ERC20 Data ---
                string  public constant name     = "Dai Stablecoin";
                string  public constant symbol   = "DAI";
                string  public constant version  = "1";
                uint8   public constant decimals = 18;
                uint256 public totalSupply;
            
                mapping (address => uint)                      public balanceOf;
                mapping (address => mapping (address => uint)) public allowance;
                mapping (address => uint)                      public nonces;
            
                event Approval(address indexed src, address indexed guy, uint wad);
                event Transfer(address indexed src, address indexed dst, uint wad);
            
                // --- Math ---
                function add(uint x, uint y) internal pure returns (uint z) {
                    require((z = x + y) >= x);
                }
                function sub(uint x, uint y) internal pure returns (uint z) {
                    require((z = x - y) <= x);
                }
            
                // --- EIP712 niceties ---
                bytes32 public DOMAIN_SEPARATOR;
                // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
                bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
            
                constructor(uint256 chainId_) public {
                    wards[msg.sender] = 1;
                    DOMAIN_SEPARATOR = keccak256(abi.encode(
                        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                        keccak256(bytes(name)),
                        keccak256(bytes(version)),
                        chainId_,
                        address(this)
                    ));
                }
            
                // --- Token ---
                function transfer(address dst, uint wad) external returns (bool) {
                    return transferFrom(msg.sender, dst, wad);
                }
                function transferFrom(address src, address dst, uint wad)
                    public returns (bool)
                {
                    require(balanceOf[src] >= wad, "Dai/insufficient-balance");
                    if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                        require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance");
                        allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
                    }
                    balanceOf[src] = sub(balanceOf[src], wad);
                    balanceOf[dst] = add(balanceOf[dst], wad);
                    emit Transfer(src, dst, wad);
                    return true;
                }
                function mint(address usr, uint wad) external auth {
                    balanceOf[usr] = add(balanceOf[usr], wad);
                    totalSupply    = add(totalSupply, wad);
                    emit Transfer(address(0), usr, wad);
                }
                function burn(address usr, uint wad) external {
                    require(balanceOf[usr] >= wad, "Dai/insufficient-balance");
                    if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
                        require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance");
                        allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
                    }
                    balanceOf[usr] = sub(balanceOf[usr], wad);
                    totalSupply    = sub(totalSupply, wad);
                    emit Transfer(usr, address(0), wad);
                }
                function approve(address usr, uint wad) external returns (bool) {
                    allowance[msg.sender][usr] = wad;
                    emit Approval(msg.sender, usr, wad);
                    return true;
                }
            
                // --- Alias ---
                function push(address usr, uint wad) external {
                    transferFrom(msg.sender, usr, wad);
                }
                function pull(address usr, uint wad) external {
                    transferFrom(usr, msg.sender, wad);
                }
                function move(address src, address dst, uint wad) external {
                    transferFrom(src, dst, wad);
                }
            
                // --- Approve by signature ---
                function permit(address holder, address spender, uint256 nonce, uint256 expiry,
                                bool allowed, uint8 v, bytes32 r, bytes32 s) external
                {
                    bytes32 digest =
                        keccak256(abi.encodePacked(
                            "\x19\x01",
                            DOMAIN_SEPARATOR,
                            keccak256(abi.encode(PERMIT_TYPEHASH,
                                                 holder,
                                                 spender,
                                                 nonce,
                                                 expiry,
                                                 allowed))
                    ));
            
                    require(holder != address(0), "Dai/invalid-address-0");
                    require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
                    require(expiry == 0 || now <= expiry, "Dai/permit-expired");
                    require(nonce == nonces[holder]++, "Dai/invalid-nonce");
                    uint wad = allowed ? uint(-1) : 0;
                    allowance[holder][spender] = wad;
                    emit Approval(holder, spender, wad);
                }
            }

            File 3 of 5: LoanTokenLogicStandard
            /**
             * Copyright 2017-2020, bZeroX, LLC <https://bzx.network/>. All Rights Reserved.
             * Licensed under the Apache License, Version 2.0.
             */
            
            pragma solidity 0.5.17;
            pragma experimental ABIEncoderV2;
            
            
            interface IWeth {
                function deposit() external payable;
                function withdraw(uint256 wad) external;
            }
            
            contract IERC20 {
                string public name;
                uint8 public decimals;
                string public symbol;
                function totalSupply() public view returns (uint256);
                function balanceOf(address _who) public view returns (uint256);
                function allowance(address _owner, address _spender) public view returns (uint256);
                function approve(address _spender, uint256 _value) public returns (bool);
                function transfer(address _to, uint256 _value) public returns (bool);
                function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
                event Transfer(address indexed from, address indexed to, uint256 value);
                event Approval(address indexed owner, address indexed spender, uint256 value);
            }
            
            contract IWethERC20 is IWeth, IERC20 {}
            
            /**
             * @dev Wrappers over Solidity's arithmetic operations with added overflow
             * checks.
             *
             * Arithmetic operations in Solidity wrap on overflow. This can easily result
             * in bugs, because programmers usually assume that an overflow raises an
             * error, which is the standard behavior in high level programming languages.
             * `SafeMath` restores this intuition by reverting the transaction when an
             * operation overflows.
             *
             * Using this library instead of the unchecked operations eliminates an entire
             * class of bugs, so it's recommended to use it always.
             */
            library SafeMath {
                /**
                 * @dev Returns the addition of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `+` operator.
                 *
                 * Requirements:
                 * - Addition cannot overflow.
                 */
                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                    uint256 c = a + b;
                    require(c >= a, "SafeMath: addition overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 */
                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                    return sub(a, b, "SafeMath: subtraction overflow");
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 *
                 * _Available since v2.4.0._
                 */
                function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b <= a, errorMessage);
                    uint256 c = a - b;
            
                    return c;
                }
            
                /**
                 * @dev Returns the multiplication of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `*` operator.
                 *
                 * Requirements:
                 * - Multiplication cannot overflow.
                 */
                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) {
                        return 0;
                    }
            
                    uint256 c = a * b;
                    require(c / a == b, "SafeMath: multiplication overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                    return div(a, b, "SafeMath: division by zero");
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
                    uint256 c = a / b;
                    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            
                    return c;
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
                    return divCeil(a, b, "SafeMath: division by zero");
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
            
                    if (a == 0) {
                        return 0;
                    }
                    uint256 c = ((a - 1) / b) + 1;
            
                    return c;
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                    return mod(a, b, "SafeMath: modulo by zero");
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts with custom message when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b != 0, errorMessage);
                    return a % b;
                }
            
                function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
                    return _a < _b ? _a : _b;
                }
            }
            
            /**
             * @title SignedSafeMath
             * @dev Signed math operations with safety checks that revert on error.
             */
            library SignedSafeMath {
                int256 constant private _INT256_MIN = -2**255;
            
                    /**
                 * @dev Returns the multiplication of two signed integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `*` operator.
                 *
                 * Requirements:
                 *
                 * - Multiplication cannot overflow.
                 */
                function mul(int256 a, int256 b) internal pure returns (int256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) {
                        return 0;
                    }
            
                    require(!(a == -1 && b == _INT256_MIN), "SignedSafeMath: multiplication overflow");
            
                    int256 c = a * b;
                    require(c / a == b, "SignedSafeMath: multiplication overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the integer division of two signed integers. Reverts on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 *
                 * - The divisor cannot be zero.
                 */
                function div(int256 a, int256 b) internal pure returns (int256) {
                    require(b != 0, "SignedSafeMath: division by zero");
                    require(!(b == -1 && a == _INT256_MIN), "SignedSafeMath: division overflow");
            
                    int256 c = a / b;
            
                    return c;
                }
            
                /**
                 * @dev Returns the subtraction of two signed integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 *
                 * - Subtraction cannot overflow.
                 */
                function sub(int256 a, int256 b) internal pure returns (int256) {
                    int256 c = a - b;
                    require((b >= 0 && c <= a) || (b < 0 && c > a), "SignedSafeMath: subtraction overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the addition of two signed integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `+` operator.
                 *
                 * Requirements:
                 *
                 * - Addition cannot overflow.
                 */
                function add(int256 a, int256 b) internal pure returns (int256) {
                    int256 c = a + b;
                    require((b >= 0 && c >= a) || (b < 0 && c < a), "SignedSafeMath: addition overflow");
            
                    return c;
                }
            }
            
            /**
             * @title Helps contracts guard against reentrancy attacks.
             * @author Remco Bloemen <remco@2π.com>, Eenae <alexey@mixbytes.io>
             * @dev If you mark a function `nonReentrant`, you should also
             * mark it `external`.
             */
            contract ReentrancyGuard {
            
                /// @dev Constant for unlocked guard state - non-zero to prevent extra gas costs.
                /// See: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1056
                uint256 internal constant REENTRANCY_GUARD_FREE = 1;
            
                /// @dev Constant for locked guard state
                uint256 internal constant REENTRANCY_GUARD_LOCKED = 2;
            
                /**
                * @dev We use a single lock for the whole contract.
                */
                uint256 internal reentrancyLock = REENTRANCY_GUARD_FREE;
            
                /**
                * @dev Prevents a contract from calling itself, directly or indirectly.
                * If you mark a function `nonReentrant`, you should also
                * mark it `external`. Calling one `nonReentrant` function from
                * another is not supported. Instead, you can implement a
                * `private` function doing the actual work, and an `external`
                * wrapper marked as `nonReentrant`.
                */
                modifier nonReentrant() {
                    require(reentrancyLock == REENTRANCY_GUARD_FREE, "nonReentrant");
                    reentrancyLock = REENTRANCY_GUARD_LOCKED;
                    _;
                    reentrancyLock = REENTRANCY_GUARD_FREE;
                }
            }
            
            /**
             * @dev Collection of functions related to the address type
             */
            library Address {
                /**
                 * @dev Returns true if `account` is a contract.
                 *
                 * [IMPORTANT]
                 * ====
                 * It is unsafe to assume that an address for which this function returns
                 * false is an externally-owned account (EOA) and not a contract.
                 *
                 * Among others, `isContract` will return false for the following 
                 * types of addresses:
                 *
                 *  - an externally-owned account
                 *  - a contract in construction
                 *  - an address where a contract will be created
                 *  - an address where a contract lived, but was destroyed
                 * ====
                 */
                function isContract(address account) internal view returns (bool) {
                    // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                    // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                    // for accounts without code, i.e. `keccak256('')`
                    bytes32 codehash;
                    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                    // solhint-disable-next-line no-inline-assembly
                    assembly { codehash := extcodehash(account) }
                    return (codehash != accountHash && codehash != 0x0);
                }
            
                /**
                 * @dev Converts an `address` into `address payable`. Note that this is
                 * simply a type cast: the actual underlying value is not changed.
                 *
                 * _Available since v2.4.0._
                 */
                function toPayable(address account) internal pure returns (address payable) {
                    return address(uint160(account));
                }
            
                /**
                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                 * `recipient`, forwarding all available gas and reverting on errors.
                 *
                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                 * imposed by `transfer`, making them unable to receive funds via
                 * `transfer`. {sendValue} removes this limitation.
                 *
                 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                 *
                 * IMPORTANT: because control is transferred to `recipient`, care must be
                 * taken to not create reentrancy vulnerabilities. Consider using
                 * {ReentrancyGuard} or the
                 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                 *
                 * _Available since v2.4.0._
                 */
                function sendValue(address recipient, uint256 amount) internal {
                    require(address(this).balance >= amount, "Address: insufficient balance");
            
                    // solhint-disable-next-line avoid-call-value
                    (bool success, ) = recipient.call.value(amount)("");
                    require(success, "Address: unable to send value, recipient may have reverted");
                }
            }
            
            /*
             * @dev Provides information about the current execution context, including the
             * sender of the transaction and its data. While these are generally available
             * via msg.sender and msg.data, they should not be accessed in such a direct
             * manner, since when dealing with GSN meta-transactions the account sending and
             * paying for execution may not be the actual sender (as far as an application
             * is concerned).
             *
             * This contract is only required for intermediate, library-like contracts.
             */
            contract Context {
                // Empty internal constructor, to prevent people from mistakenly deploying
                // an instance of this contract, which should be used via inheritance.
                constructor () internal { }
                // solhint-disable-previous-line no-empty-blocks
            
                function _msgSender() internal view returns (address payable) {
                    return msg.sender;
                }
            
                function _msgData() internal view returns (bytes memory) {
                    this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                    return msg.data;
                }
            }
            
            /**
             * @dev Contract module which provides a basic access control mechanism, where
             * there is an account (an owner) that can be granted exclusive access to
             * specific functions.
             *
             * This module is used through inheritance. It will make available the modifier
             * `onlyOwner`, which can be applied to your functions to restrict their use to
             * the owner.
             */
            contract Ownable is Context {
                address private _owner;
            
                event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            
                /**
                 * @dev Initializes the contract setting the deployer as the initial owner.
                 */
                constructor () internal {
                    address msgSender = _msgSender();
                    _owner = msgSender;
                    emit OwnershipTransferred(address(0), msgSender);
                }
            
                /**
                 * @dev Returns the address of the current owner.
                 */
                function owner() public view returns (address) {
                    return _owner;
                }
            
                /**
                 * @dev Throws if called by any account other than the owner.
                 */
                modifier onlyOwner() {
                    require(isOwner(), "unauthorized");
                    _;
                }
            
                /**
                 * @dev Returns true if the caller is the current owner.
                 */
                function isOwner() public view returns (bool) {
                    return _msgSender() == _owner;
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Can only be called by the current owner.
                 */
                function transferOwnership(address newOwner) public onlyOwner {
                    _transferOwnership(newOwner);
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 */
                function _transferOwnership(address newOwner) internal {
                    require(newOwner != address(0), "Ownable: new owner is the zero address");
                    emit OwnershipTransferred(_owner, newOwner);
                    _owner = newOwner;
                }
            }
            
            interface ProtocolLike {
                function borrowOrTradeFromPool(
                    bytes32 loanParamsId,
                    bytes32 loanId, // if 0, start a new loan
                    bool isTorqueLoan,
                    uint256 initialMargin,
                    address[4] calldata sentAddresses,
                        // lender: must match loan if loanId provided
                        // borrower: must match loan if loanId provided
                        // receiver: receiver of funds (address(0) assumes borrower address)
                        // manager: delegated manager of loan unless address(0)
                    uint256[5] calldata sentValues,
                        // newRate: new loan interest rate
                        // newPrincipal: new loan size (borrowAmount + any borrowed interest)
                        // torqueInterest: new amount of interest to escrow for Torque loan (determines initial loan length)
                        // loanTokenReceived: total loanToken deposit (amount not sent to borrower in the case of Torque loans)
                        // collateralTokenReceived: total collateralToken deposit
                    bytes calldata loanDataBytes)
                    external
                    payable
                    returns (uint256 newPrincipal, uint256 newCollateral);
            
                function getTotalPrincipal(
                    address lender,
                    address loanToken)
                    external
                    view
                    returns (uint256);
            
                function withdrawAccruedInterest(
                    address loanToken)
                    external;
            
                function getLenderInterestData(
                    address lender,
                    address loanToken)
                    external
                    view
                    returns (
                        uint256 interestPaid,
                        uint256 interestPaidDate,
                        uint256 interestOwedPerDay,
                        uint256 interestUnPaid,
                        uint256 interestFeePercent,
                        uint256 principalTotal);
            
                function priceFeeds()
                    external
                    view
                    returns (address);
            
                function getEstimatedMarginExposure(
                    address loanToken,
                    address collateralToken,
                    uint256 loanTokenSent,
                    uint256 collateralTokenSent,
                    uint256 interestRate,
                    uint256 newPrincipal)
                    external
                    view
                    returns (uint256);
            
                function getRequiredCollateralByParams(
                    bytes32 loanParamsId,
                    address loanToken,
                    address collateralToken,
                    uint256 newPrincipal,
                    bool isTorqueLoan)
                    external
                    view
                    returns (uint256 collateralAmountRequired);
            
                function getBorrowAmountByParams(
                    bytes32 loanParamsId,
                    address loanToken,
                    address collateralToken,
                    uint256 collateralTokenAmount,
                    bool isTorqueLoan)
                    external
                    view
                    returns (uint256 borrowAmount);
            
                function isLoanPool(
                    address loanPool)
                    external
                    view
                    returns (bool);
            
                function lendingFeePercent()
                    external
                    view
                    returns (uint256);
            }
            
            interface FeedsLike {
                function queryRate(
                    address sourceTokenAddress,
                    address destTokenAddress)
                    external
                    view
                    returns (uint256 rate, uint256 precision);
            }
            
            contract ITokenHolderLike {
                function balanceOf(address _who) public view returns (uint256);
                function freeUpTo(uint256 value) public returns (uint256);
                function freeFromUpTo(address from, uint256 value) public returns (uint256);
            }
            
            contract GasTokenUser {
            
                ITokenHolderLike constant public gasToken = ITokenHolderLike(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);
                ITokenHolderLike constant public tokenHolder = ITokenHolderLike(0x55Eb3DD3f738cfdda986B8Eff3fa784477552C61);
            
                modifier usesGasToken(address holder) {
                    if (holder == address(0)) {
                        holder = address(tokenHolder);
                    }
            
                    if (gasToken.balanceOf(holder) != 0) {
                        uint256 gasCalcValue = gasleft();
            
                        _;
            
                        gasCalcValue = (_gasUsed(gasCalcValue) + 14154) / 41947;
            
                        if (holder == address(tokenHolder)) {
                            tokenHolder.freeUpTo(
                                gasCalcValue
                            );
                        } else {
                            tokenHolder.freeFromUpTo(
                                holder,
                                gasCalcValue
                            );
                        }
            
                    } else {
                        _;
                    }
                }
            
                function _gasUsed(
                    uint256 startingGas)
                    internal
                    view
                    returns (uint256)
                {
                    return 21000 +
                        startingGas -
                        gasleft() +
                        16 *
                        msg.data.length;
            
                }
            }
            
            contract Pausable {
            
                // keccak256("Pausable_FunctionPause")
                bytes32 internal constant Pausable_FunctionPause = 0xa7143c84d793a15503da6f19bf9119a2dac94448ca45d77c8bf08f57b2e91047;
            
                modifier pausable(bytes4 sig) {
                    require(!_isPaused(sig), "unauthorized");
                    _;
                }
            
                function _isPaused(
                    bytes4 sig)
                    internal
                    view
                    returns (bool isPaused)
                {
                    bytes32 slot = keccak256(abi.encodePacked(sig, Pausable_FunctionPause));
                    assembly {
                        isPaused := sload(slot)
                    }
                }
            }
            
            contract LoanTokenBase is ReentrancyGuard, Ownable, Pausable {
            
                uint256 internal constant WEI_PRECISION = 10**18;
                uint256 internal constant WEI_PERCENT_PRECISION = 10**20;
            
                int256 internal constant sWEI_PRECISION = 10**18;
            
                string public name;
                string public symbol;
                uint8 public decimals;
            
                // uint88 for tight packing -> 8 + 88 + 160 = 256
                uint88 internal lastSettleTime_;
            
                address public loanTokenAddress;
            
                uint256 public baseRate;
                uint256 public rateMultiplier;
                uint256 public lowUtilBaseRate;
                uint256 public lowUtilRateMultiplier;
            
                uint256 public targetLevel;
                uint256 public kinkLevel;
                uint256 public maxScaleRate;
            
                uint256 internal _flTotalAssetSupply;
                uint256 public checkpointSupply;
                uint256 public initialPrice;
            
                mapping (uint256 => bytes32) public loanParamsIds; // mapping of keccak256(collateralToken, isTorqueLoan) to loanParamsId
                mapping (address => uint256) internal checkpointPrices_; // price of token at last user checkpoint
            }
            
            contract AdvancedTokenStorage is LoanTokenBase {
                using SafeMath for uint256;
            
                event Transfer(
                    address indexed from,
                    address indexed to,
                    uint256 value
                );
            
                event Approval(
                    address indexed owner,
                    address indexed spender,
                    uint256 value
                );
            
                event Mint(
                    address indexed minter,
                    uint256 tokenAmount,
                    uint256 assetAmount,
                    uint256 price
                );
            
                event Burn(
                    address indexed burner,
                    uint256 tokenAmount,
                    uint256 assetAmount,
                    uint256 price
                );
            
                mapping(address => uint256) internal balances;
                mapping (address => mapping (address => uint256)) internal allowed;
                uint256 internal totalSupply_;
            
                function totalSupply()
                    public
                    view
                    returns (uint256)
                {
                    return totalSupply_;
                }
            
                function balanceOf(
                    address _owner)
                    public
                    view
                    returns (uint256)
                {
                    return balances[_owner];
                }
            
                function allowance(
                    address _owner,
                    address _spender)
                    public
                    view
                    returns (uint256)
                {
                    return allowed[_owner][_spender];
                }
            }
            
            contract AdvancedToken is AdvancedTokenStorage {
                using SafeMath for uint256;
            
                function approve(
                    address _spender,
                    uint256 _value)
                    public
                    returns (bool)
                {
                    allowed[msg.sender][_spender] = _value;
                    emit Approval(msg.sender, _spender, _value);
                    return true;
                }
            
                function increaseApproval(
                    address _spender,
                    uint256 _addedValue)
                    public
                    returns (bool)
                {
                    uint256 _allowed = allowed[msg.sender][_spender]
                        .add(_addedValue);
                    allowed[msg.sender][_spender] = _allowed;
            
                    emit Approval(msg.sender, _spender, _allowed);
                    return true;
                }
            
                function decreaseApproval(
                    address _spender,
                    uint256 _subtractedValue)
                    public
                    returns (bool)
                {
                    uint256 _allowed = allowed[msg.sender][_spender];
                    if (_subtractedValue >= _allowed) {
                        _allowed = 0;
                    } else {
                        _allowed -= _subtractedValue;
                    }
                    allowed[msg.sender][_spender] = _allowed;
            
                    emit Approval(msg.sender, _spender, _allowed);
                    return true;
                }
            
                function _mint(
                    address _to,
                    uint256 _tokenAmount,
                    uint256 _assetAmount,
                    uint256 _price)
                    internal
                    returns (uint256)
                {
                    require(_to != address(0), "15");
            
                    uint256 _balance = balances[_to]
                        .add(_tokenAmount);
                    balances[_to] = _balance;
            
                    totalSupply_ = totalSupply_
                        .add(_tokenAmount);
            
                    emit Mint(_to, _tokenAmount, _assetAmount, _price);
                    emit Transfer(address(0), _to, _tokenAmount);
            
                    return _balance;
                }
            
                function _burn(
                    address _who,
                    uint256 _tokenAmount,
                    uint256 _assetAmount,
                    uint256 _price)
                    internal
                    returns (uint256)
                {
                    uint256 _balance = balances[_who].sub(_tokenAmount, "16");
                    
                    // a rounding error may leave dust behind, so we clear this out
                    if (_balance <= 10) {
                        _tokenAmount = _tokenAmount.add(_balance);
                        _balance = 0;
                    }
                    balances[_who] = _balance;
            
                    totalSupply_ = totalSupply_.sub(_tokenAmount);
            
                    emit Burn(_who, _tokenAmount, _assetAmount, _price);
                    emit Transfer(_who, address(0), _tokenAmount);
            
                    return _balance;
                }
            }
            
            contract LoanTokenLogicStandard is AdvancedToken, GasTokenUser {
                using SafeMath for uint256;
                using SignedSafeMath for int256;
            
                modifier settlesInterest() {
                    _settleInterest();
                    _;
                }
            
                address internal target_;
            
                uint256 public constant VERSION = 6;
                address internal constant arbitraryCaller = 0x000F400e6818158D541C3EBE45FE3AA0d47372FF;
            
                address public constant bZxContract = 0xD8Ee69652E4e4838f2531732a46d1f7F584F0b7f;
                address public constant wethToken = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
            
                bytes32 internal constant iToken_ProfitSoFar = 0x37aa2b7d583612f016e4a4de4292cb015139b3d7762663d06a53964912ea2fb6;          // keccak256("iToken_ProfitSoFar")
                bytes32 internal constant iToken_LowerAdminAddress = 0x7ad06df6a0af6bd602d90db766e0d5f253b45187c3717a0f9026ea8b10ff0d4b;    // keccak256("iToken_LowerAdminAddress")
                bytes32 internal constant iToken_LowerAdminContract = 0x34b31cff1dbd8374124bd4505521fc29cab0f9554a5386ba7d784a4e611c7e31;   // keccak256("iToken_LowerAdminContract")
            
            
                constructor(
                    address _newOwner)
                    public
                {
                    transferOwnership(_newOwner);
                }
            
                function()
                    external
                {
                    revert("fallback not allowed");
                }
            
                /* Public functions */
            
                function mint(
                    address receiver,
                    uint256 depositAmount)
                    external
                    nonReentrant
                    returns (uint256) // mintAmount
                {
                    return _mintToken(
                        receiver,
                        depositAmount
                    );
                }
            
                function burn(
                    address receiver,
                    uint256 burnAmount)
                    external
                    nonReentrant
                    returns (uint256 loanAmountPaid)
                {
                    loanAmountPaid = _burnToken(
                        burnAmount
                    );
            
                    if (loanAmountPaid != 0) {
                        _safeTransfer(loanTokenAddress, receiver, loanAmountPaid, "5");
                    }
                }
            
                function flashBorrow(
                    uint256 borrowAmount,
                    address borrower,
                    address target,
                    string calldata signature,
                    bytes calldata data)
                    external
                    payable
                    nonReentrant
                    pausable(msg.sig)
                    settlesInterest
                    returns (bytes memory)
                {
                    require(borrowAmount != 0, "38");
            
                    // save before balances
                    uint256 beforeEtherBalance = address(this).balance.sub(msg.value);
                    uint256 beforeAssetsBalance = _underlyingBalance()
                        .add(totalAssetBorrow());
            
                    // lock totalAssetSupply for duration of flash loan
                    _flTotalAssetSupply = beforeAssetsBalance;
            
                    // transfer assets to calling contract
                    _safeTransfer(loanTokenAddress, borrower, borrowAmount, "39");
            
                    bytes memory callData;
                    if (bytes(signature).length == 0) {
                        callData = data;
                    } else {
                        callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
                    }
            
                    // arbitrary call
                    (bool success, bytes memory returnData) = arbitraryCaller.call.value(msg.value)(
                        abi.encodeWithSelector(
                            0xde064e0d, // sendCall(address,bytes)
                            target,
                            callData
                        )
                    );
                    require(success, "call failed");
            
                    // unlock totalAssetSupply
                    _flTotalAssetSupply = 0;
            
                    // verifies return of flash loan
                    require(
                        address(this).balance >= beforeEtherBalance &&
                        _underlyingBalance()
                            .add(totalAssetBorrow()) >= beforeAssetsBalance,
                        "40"
                    );
            
                    return returnData;
                }
            
                // ***** NOTE: Reentrancy is allowed here to allow flashloan use cases *****
                function borrow(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 withdrawAmount,
                    uint256 initialLoanDuration,    // duration in seconds
                    uint256 collateralTokenSent,    // if 0, loanId must be provided; any ETH sent must equal this value
                    address collateralTokenAddress, // if address(0), this means ETH and ETH must be sent with the call or loanId must be provided
                    address borrower,
                    address receiver,
                    bytes memory /*loanDataBytes*/) // arbitrary order data (for future use)
                    public
                    payable
                    returns (uint256, uint256) // returns new principal and new collateral added to loan
                {
                    return _borrow(
                        loanId,
                        withdrawAmount,
                        initialLoanDuration,
                        collateralTokenSent,
                        collateralTokenAddress,
                        borrower,
                        receiver,
                        ""
                    );
                }
            
                // ***** NOTE: Reentrancy is allowed here to allow flashloan use cases *****
                function borrowWithGasToken(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 withdrawAmount,
                    uint256 initialLoanDuration,    // duration in seconds
                    uint256 collateralTokenSent,    // if 0, loanId must be provided; any ETH sent must equal this value
                    address collateralTokenAddress, // if address(0), this means ETH and ETH must be sent with the call or loanId must be provided
                    address borrower,
                    address receiver,
                    address gasTokenUser,           // specifies an address that has given spend approval for gas/chi token
                    bytes memory /*loanDataBytes*/) // arbitrary order data (for future use)
                    public
                    payable
                    usesGasToken(gasTokenUser)
                    returns (uint256, uint256) // returns new principal and new collateral added to loan
                {
                    return _borrow(
                        loanId,
                        withdrawAmount,
                        initialLoanDuration,
                        collateralTokenSent,
                        collateralTokenAddress,
                        borrower,
                        receiver,
                        ""
                    );
                }
            
                // Called to borrow and immediately get into a position
                // ***** NOTE: Reentrancy is allowed here to allow flashloan use cases *****
                function marginTrade(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 leverageAmount,
                    uint256 loanTokenSent,
                    uint256 collateralTokenSent,
                    address collateralTokenAddress,
                    address trader,
                    bytes memory loanDataBytes)     // arbitrary order data
                    public
                    payable
                    returns (uint256, uint256) // returns new principal and new collateral added to trade
                {
                    return _marginTrade(
                        loanId,
                        leverageAmount,
                        loanTokenSent,
                        collateralTokenSent,
                        collateralTokenAddress,
                        trader,
                        loanDataBytes
                    );
                }
            
                // Called to borrow and immediately get into a position
                // ***** NOTE: Reentrancy is allowed here to allow flashloan use cases *****
                function marginTradeWithGasToken(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 leverageAmount,
                    uint256 loanTokenSent,
                    uint256 collateralTokenSent,
                    address collateralTokenAddress,
                    address trader,
                    address gasTokenUser,           // specifies an address that has given spend approval for gas/chi token
                    bytes memory loanDataBytes)     // arbitrary order data
                    public
                    payable
                    usesGasToken(gasTokenUser)
                    returns (uint256, uint256) // returns new principal and new collateral added to trade
                {
                    return _marginTrade(
                        loanId,
                        leverageAmount,
                        loanTokenSent,
                        collateralTokenSent,
                        collateralTokenAddress,
                        trader,
                        loanDataBytes
                    );
                }
            
                function transfer(
                    address _to,
                    uint256 _value)
                    external
                    returns (bool)
                {
                    return _internalTransferFrom(
                        msg.sender,
                        _to,
                        _value,
                        uint256(-1)
                    );
                }
            
                function transferFrom(
                    address _from,
                    address _to,
                    uint256 _value)
                    external
                    returns (bool)
                {
                    return _internalTransferFrom(
                        _from,
                        _to,
                        _value,
                        allowed[_from][msg.sender]
                        /*ProtocolLike(bZxContract).isLoanPool(msg.sender) ?
                            uint256(-1) :
                            allowed[_from][msg.sender]*/
                    );
                }
            
                function _internalTransferFrom(
                    address _from,
                    address _to,
                    uint256 _value,
                    uint256 _allowanceAmount)
                    internal
                    returns (bool)
                {
                    if (_allowanceAmount != uint256(-1)) {
                        allowed[_from][msg.sender] = _allowanceAmount.sub(_value, "14");
                    }
            
                    uint256 _balancesFrom = balances[_from];
                    uint256 _balancesTo = balances[_to];
            
                    require(_to != address(0), "15");
            
                    uint256 _balancesFromNew = _balancesFrom
                        .sub(_value, "16");
                    balances[_from] = _balancesFromNew;
            
                    uint256 _balancesToNew = _balancesTo
                        .add(_value);
                    balances[_to] = _balancesToNew;
            
                    // handle checkpoint update
                    uint256 _currentPrice = tokenPrice();
            
                    _updateCheckpoints(
                        _from,
                        _balancesFrom,
                        _balancesFromNew,
                        _currentPrice
                    );
                    _updateCheckpoints(
                        _to,
                        _balancesTo,
                        _balancesToNew,
                        _currentPrice
                    );
            
                    emit Transfer(_from, _to, _value);
                    return true;
                }
            
                function _updateCheckpoints(
                    address _user,
                    uint256 _oldBalance,
                    uint256 _newBalance,
                    uint256 _currentPrice)
                    internal
                {
                    bytes32 slot = keccak256(
                        abi.encodePacked(_user, iToken_ProfitSoFar)
                    );
            
                    int256 _currentProfit;
                    if (_newBalance == 0) {
                        _currentPrice = 0;
                    } else if (_oldBalance != 0) {
                        _currentProfit = _profitOf(
                            slot,
                            _oldBalance,
                            _currentPrice,
                            checkpointPrices_[_user]
                        );
                    }
            
                    assembly {
                        sstore(slot, _currentProfit)
                    }
            
                    checkpointPrices_[_user] = _currentPrice;
                }
            
                /* Public View functions */
            
                function profitOf(
                    address user)
                    public
                    view
                    returns (int256)
                {
                    bytes32 slot = keccak256(
                        abi.encodePacked(user, iToken_ProfitSoFar)
                    );
            
                    return _profitOf(
                        slot,
                        balances[user],
                        tokenPrice(),
                        checkpointPrices_[user]
                    );
                }
            
                function _profitOf(
                    bytes32 slot,
                    uint256 _balance,
                    uint256 _currentPrice,
                    uint256 _checkpointPrice)
                    internal
                    view
                    returns (int256 profitSoFar)
                {
                    if (_checkpointPrice == 0) {
                        return 0;
                    }
            
                    assembly {
                        profitSoFar := sload(slot)
                    }
            
                    profitSoFar = int256(_currentPrice)
                        .sub(int256(_checkpointPrice))
                        .mul(int256(_balance))
                        .div(sWEI_PRECISION)
                        .add(profitSoFar);
                }
            
                function tokenPrice()
                    public
                    view
                    returns (uint256) // price
                {
                    uint256 interestUnPaid;
                    if (lastSettleTime_ != uint88(block.timestamp)) {
                        (,interestUnPaid) = _getAllInterest();
                    }
            
                    return _tokenPrice(_totalAssetSupply(interestUnPaid));
                }
            
                function checkpointPrice(
                    address _user)
                    public
                    view
                    returns (uint256) // price
                {
                    return checkpointPrices_[_user];
                }
            
                function marketLiquidity()
                    public
                    view
                    returns (uint256)
                {
                    uint256 totalSupply = _totalAssetSupply(0);
                    uint256 totalBorrow = totalAssetBorrow();
                    if (totalSupply > totalBorrow) {
                        return totalSupply - totalBorrow;
                    }
                }
            
                function avgBorrowInterestRate()
                    public
                    view
                    returns (uint256)
                {
                    return _avgBorrowInterestRate(totalAssetBorrow());
                }
            
                // the minimum rate the next base protocol borrower will receive for variable-rate loans
                function borrowInterestRate()
                    public
                    view
                    returns (uint256)
                {
                    return _nextBorrowInterestRate(0);
                }
            
                function nextBorrowInterestRate(
                    uint256 borrowAmount)
                    public
                    view
                    returns (uint256)
                {
                    return _nextBorrowInterestRate(borrowAmount);
                }
            
                // interest that lenders are currently receiving when supplying to the pool
                function supplyInterestRate()
                    public
                    view
                    returns (uint256)
                {
                    return totalSupplyInterestRate(_totalAssetSupply(0));
                }
            
                function nextSupplyInterestRate(
                    uint256 supplyAmount)
                    public
                    view
                    returns (uint256)
                {
                    return totalSupplyInterestRate(_totalAssetSupply(0).add(supplyAmount));
                }
            
                function totalSupplyInterestRate(
                    uint256 assetSupply)
                    public
                    view
                    returns (uint256)
                {
                    uint256 assetBorrow = totalAssetBorrow();
                    if (assetBorrow != 0) {
                        return _supplyInterestRate(
                            assetBorrow,
                            assetSupply
                        );
                    }
                }
            
                function totalAssetBorrow()
                    public
                    view
                    returns (uint256)
                {
                    return ProtocolLike(bZxContract).getTotalPrincipal(
                        address(this),
                        loanTokenAddress
                    );
                }
            
                function totalAssetSupply()
                    public
                    view
                    returns (uint256)
                {
                    uint256 interestUnPaid;
                    if (lastSettleTime_ != uint88(block.timestamp)) {
                        (,interestUnPaid) = _getAllInterest();
                    }
            
                    return _totalAssetSupply(interestUnPaid);
                }
            
                function getMaxEscrowAmount(
                    uint256 leverageAmount)
                    public
                    view
                    returns (uint256)
                {
                    uint256 initialMargin = SafeMath.div(WEI_PRECISION * WEI_PERCENT_PRECISION, leverageAmount);
                    return marketLiquidity()
                        .mul(initialMargin)
                        .div(_adjustValue(
                            WEI_PERCENT_PRECISION, // maximum possible interest (100%)
                            2419200, // 28 day duration for margin trades
                            initialMargin));
                }
            
                // returns the user's balance of underlying token
                function assetBalanceOf(
                    address _owner)
                    public
                    view
                    returns (uint256)
                {
                    return balanceOf(_owner)
                        .mul(tokenPrice())
                        .div(WEI_PRECISION);
                }
            
                function getEstimatedMarginDetails(
                    uint256 leverageAmount,
                    uint256 loanTokenSent,
                    uint256 collateralTokenSent,
                    address collateralTokenAddress)     // address(0) means ETH
                    public
                    view
                    returns (uint256 principal, uint256 collateral, uint256 interestRate)
                {
                    if (collateralTokenAddress == address(0)) {
                        collateralTokenAddress = wethToken;
                    }
            
                    uint256 totalDeposit = _totalDeposit(
                        collateralTokenAddress,
                        collateralTokenSent,
                        loanTokenSent
                    );
            
                    (principal, interestRate) = _getMarginBorrowAmountAndRate(
                        leverageAmount,
                        totalDeposit
                    );
                    if (principal > _underlyingBalance()) {
                        return (0, 0, 0);
                    }
            
                    loanTokenSent = loanTokenSent
                        .add(principal);
            
                    collateral = ProtocolLike(bZxContract).getEstimatedMarginExposure(
                        loanTokenAddress,
                        collateralTokenAddress,
                        loanTokenSent,
                        collateralTokenSent,
                        interestRate,
                        principal
                    );
                }
            
                function getDepositAmountForBorrow(
                    uint256 borrowAmount,
                    uint256 initialLoanDuration,        // duration in seconds
                    address collateralTokenAddress)     // address(0) means ETH
                    public
                    view
                    returns (uint256) // depositAmount
                {
                    if (borrowAmount != 0) {
                        (,,uint256 newBorrowAmount) = _getInterestRateAndBorrowAmount(
                            borrowAmount,
                            totalAssetSupply(),
                            initialLoanDuration
                        );
            
                        if (newBorrowAmount <= _underlyingBalance()) {
                            return ProtocolLike(bZxContract).getRequiredCollateralByParams(
                                loanParamsIds[uint256(keccak256(abi.encodePacked(
                                    collateralTokenAddress,
                                    true
                                )))],
                                loanTokenAddress,
                                collateralTokenAddress != address(0) ? collateralTokenAddress : wethToken,
                                newBorrowAmount,
                                true // isTorqueLoan
                            ).add(10); // some dust to compensate for rounding errors
                        }
                    }
                }
            
                function getBorrowAmountForDeposit(
                    uint256 depositAmount,
                    uint256 initialLoanDuration,        // duration in seconds
                    address collateralTokenAddress)     // address(0) means ETH
                    public
                    view
                    returns (uint256 borrowAmount)
                {
                    if (depositAmount != 0) {
                        borrowAmount = ProtocolLike(bZxContract).getBorrowAmountByParams(
                            loanParamsIds[uint256(keccak256(abi.encodePacked(
                                collateralTokenAddress,
                                true
                            )))],
                            loanTokenAddress,
                            collateralTokenAddress != address(0) ? collateralTokenAddress : wethToken,
                            depositAmount,
                            true // isTorqueLoan
                        );
            
                        (,,borrowAmount) = _getInterestRateAndBorrowAmount(
                            borrowAmount,
                            totalAssetSupply(),
                            initialLoanDuration
                        );
            
                        if (borrowAmount > _underlyingBalance()) {
                            borrowAmount = 0;
                        }
                    }
                }
            
            
                /* Internal functions */
            
                function _mintToken(
                    address receiver,
                    uint256 depositAmount)
                    internal
                    settlesInterest
                    returns (uint256 mintAmount)
                {
                    require (depositAmount != 0, "17");
            
                    uint256 currentPrice = _tokenPrice(_totalAssetSupply(0));
                    mintAmount = depositAmount
                        .mul(WEI_PRECISION)
                        .div(currentPrice);
            
                    if (msg.value == 0) {
                        _safeTransferFrom(loanTokenAddress, msg.sender, address(this), depositAmount, "18");
                    } else {
                        require(msg.value == depositAmount, "18");
                        IWeth(wethToken).deposit.value(depositAmount)();
                    }
            
                    _updateCheckpoints(
                        receiver,
                        balances[receiver],
                        _mint(receiver, mintAmount, depositAmount, currentPrice), // newBalance
                        currentPrice
                    );
                }
            
                function _burnToken(
                    uint256 burnAmount)
                    internal
                    settlesInterest
                    returns (uint256 loanAmountPaid)
                {
                    require(burnAmount != 0, "19");
            
                    if (burnAmount > balanceOf(msg.sender)) {
                        require(burnAmount == uint256(-1), "32");
                        burnAmount = balanceOf(msg.sender);
                    }
            
                    uint256 currentPrice = _tokenPrice(_totalAssetSupply(0));
            
                    uint256 loanAmountOwed = burnAmount
                        .mul(currentPrice)
                        .div(WEI_PRECISION);
                    uint256 loanAmountAvailableInContract = _underlyingBalance();
            
                    loanAmountPaid = loanAmountOwed;
                    require(loanAmountPaid <= loanAmountAvailableInContract, "37");
            
                    _updateCheckpoints(
                        msg.sender,
                        balances[msg.sender],
                        _burn(msg.sender, burnAmount, loanAmountPaid, currentPrice), // newBalance
                        currentPrice
                    );
                }
            
                function _borrow(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 withdrawAmount,
                    uint256 initialLoanDuration,    // duration in seconds
                    uint256 collateralTokenSent,    // if 0, loanId must be provided; any ETH sent must equal this value
                    address collateralTokenAddress, // if address(0), this means ETH and ETH must be sent with the call or loanId must be provided
                    address borrower,
                    address receiver,
                    bytes memory /*loanDataBytes*/) // arbitrary order data (for future use)
                    internal
                    pausable(msg.sig)
                    settlesInterest
                    returns (uint256, uint256) // returns new principal and new collateral added to loan
                {
                    require(withdrawAmount != 0, "6");
            
                    require(msg.value == 0 || msg.value == collateralTokenSent, "7");
                    require(collateralTokenSent != 0 || loanId != 0, "8");
                    require(collateralTokenAddress != address(0) || msg.value != 0 || loanId != 0, "9");
            
                    // ensures authorized use of existing loan
                    require(loanId == 0 || msg.sender == borrower, "13");
            
                    if (collateralTokenAddress == address(0)) {
                        collateralTokenAddress = wethToken;
                    }
                    require(collateralTokenAddress != loanTokenAddress, "10");
            
                    address[4] memory sentAddresses;
                    uint256[5] memory sentAmounts;
            
                    sentAddresses[0] = address(this); // lender
                    sentAddresses[1] = borrower;
                    sentAddresses[2] = receiver;
                    //sentAddresses[3] = address(0); // manager
            
                    //sentAmounts[0] = 0; // interestRate (found later)
                    //sentAmounts[1] = 0; // borrowAmount (found later)
                    //sentAmounts[2] = 0; // interestInitialAmount (found later)
                    //sentAmounts[3] = 0; // loanTokenSent
                    sentAmounts[4] = collateralTokenSent;
            
                    // interestRate, interestInitialAmount, borrowAmount (newBorrowAmount)
                    (sentAmounts[0], sentAmounts[2], sentAmounts[1]) = _getInterestRateAndBorrowAmount(
                        withdrawAmount,
                        _totalAssetSupply(0), // interest is settled above
                        initialLoanDuration
                    );
            
                    return _borrowOrTrade(
                        loanId,
                        withdrawAmount,
                        0, // leverageAmount (calculated later)
                        collateralTokenAddress,
                        sentAddresses,
                        sentAmounts,
                        "" // loanDataBytes
                    );
                }
            
                function _marginTrade(
                    bytes32 loanId,                 // 0 if new loan
                    uint256 leverageAmount,
                    uint256 loanTokenSent,
                    uint256 collateralTokenSent,
                    address collateralTokenAddress,
                    address trader,
                    bytes memory loanDataBytes)
                    internal
                    pausable(msg.sig)
                    settlesInterest
                    returns (uint256, uint256) // returns new principal and new collateral added to trade
                {
                    // ensures authorized use of existing loan
                    require(loanId == 0 || msg.sender == trader, "13");
            
                    if (collateralTokenAddress == address(0)) {
                        collateralTokenAddress = wethToken;
                    }
                    require(collateralTokenAddress != loanTokenAddress, "11");
            
                    uint256 totalDeposit = _totalDeposit(
                        collateralTokenAddress,
                        collateralTokenSent,
                        loanTokenSent
                    );
                    require(totalDeposit != 0, "12");
            
                    address[4] memory sentAddresses;
                    uint256[5] memory sentAmounts;
            
                    sentAddresses[0] = address(this); // lender
                    sentAddresses[1] = trader;
                    sentAddresses[2] = trader;
                    //sentAddresses[3] = address(0); // manager
            
                    //sentAmounts[0] = 0; // interestRate (found later)
                    //sentAmounts[1] = 0; // borrowAmount (found later)
                    //sentAmounts[2] = 0; // interestInitialAmount (interest is calculated based on fixed-term loan)
                    sentAmounts[3] = loanTokenSent;
                    sentAmounts[4] = collateralTokenSent;
            
                    (sentAmounts[1], sentAmounts[0]) = _getMarginBorrowAmountAndRate( // borrowAmount, interestRate
                        leverageAmount,
                        totalDeposit
                    );
            
                    return _borrowOrTrade(
                        loanId,
                        0, // withdrawAmount
                        leverageAmount,
                        collateralTokenAddress,
                        sentAddresses,
                        sentAmounts,
                        loanDataBytes
                    );
                }
            
                function _settleInterest()
                    internal
                {
                    uint88 ts = uint88(block.timestamp);
                    if (lastSettleTime_ != ts) {
                        ProtocolLike(bZxContract).withdrawAccruedInterest(
                            loanTokenAddress
                        );
            
                        lastSettleTime_ = ts;
                    }
                }
            
                function _totalDeposit(
                    address collateralTokenAddress,
                    uint256 collateralTokenSent,
                    uint256 loanTokenSent)
                    internal
                    view
                    returns (uint256 totalDeposit)
                {
                    totalDeposit = loanTokenSent;
                    if (collateralTokenSent != 0) {
                        (uint256 sourceToDestRate, uint256 sourceToDestPrecision) = FeedsLike(ProtocolLike(bZxContract).priceFeeds()).queryRate(
                            collateralTokenAddress,
                            loanTokenAddress
                        );
                        if (sourceToDestRate != 0) {
                            totalDeposit = collateralTokenSent
                                .mul(sourceToDestRate)
                                .div(sourceToDestPrecision)
                                .add(totalDeposit);
                        }
                    }
                }
            
                function _getInterestRateAndBorrowAmount(
                    uint256 borrowAmount,
                    uint256 assetSupply,
                    uint256 initialLoanDuration) // duration in seconds
                    internal
                    view
                    returns (uint256 interestRate, uint256 interestInitialAmount, uint256 newBorrowAmount)
                {
                    interestRate = _nextBorrowInterestRate2(
                        borrowAmount,
                        assetSupply
                    );
            
                    // newBorrowAmount = borrowAmount * 10^18 / (10^18 - (interestRate * initialLoanDuration * 10^18 / (31536000 * 10^20)))
                    newBorrowAmount = borrowAmount
                        .mul(WEI_PRECISION)
                        .div(
                            SafeMath.sub(WEI_PRECISION,
                                interestRate
                                    .mul(initialLoanDuration)
                                    .mul(WEI_PRECISION)
                                    .div(31536000 * WEI_PERCENT_PRECISION) // 365 * 86400 * WEI_PERCENT_PRECISION
                            )
                        );
            
                    interestInitialAmount = newBorrowAmount
                        .sub(borrowAmount);
                }
            
                // returns newPrincipal
                function _borrowOrTrade(
                    bytes32 loanId,
                    uint256 withdrawAmount,
                    uint256 leverageAmount,
                    address collateralTokenAddress,
                    address[4] memory sentAddresses,
                    uint256[5] memory sentAmounts,
                    bytes memory loanDataBytes)
                    internal
                    returns (uint256, uint256)
                {
                    require (sentAmounts[1] <= _underlyingBalance() && // newPrincipal
                        sentAddresses[1] != address(0), // borrower
                        "24"
                    );
            
            	    if (sentAddresses[2] == address(0)) {
                        sentAddresses[2] = sentAddresses[1]; // receiver = borrower
                    }
            
                    // handle transfers prior to adding newPrincipal to loanTokenSent
                    uint256 msgValue = _verifyTransfers(
                        collateralTokenAddress,
                        sentAddresses,
                        sentAmounts,
                        withdrawAmount
                    );
            
                    // adding the loan token portion from the lender to loanTokenSent
                    sentAmounts[3] = sentAmounts[3]
                        .add(sentAmounts[1]); // newPrincipal
            
                    if (withdrawAmount != 0) {
                        // withdrawAmount already sent to the borrower, so we aren't sending it to the protocol
                        sentAmounts[3] = sentAmounts[3]
                            .sub(withdrawAmount);
                    }
            
                    bool isTorqueLoan = withdrawAmount != 0 ?
                        true :
                        false;
            
                    bytes32 loanParamsId = loanParamsIds[uint256(keccak256(abi.encodePacked(
                        collateralTokenAddress,
                        isTorqueLoan
                    )))];
            
                    // converting to initialMargin
                    if (leverageAmount != 0) {
                        leverageAmount = SafeMath.div(WEI_PRECISION * WEI_PERCENT_PRECISION, leverageAmount);
                    }
            
                    (sentAmounts[1], sentAmounts[4]) = ProtocolLike(bZxContract).borrowOrTradeFromPool.value(msgValue)( // newPrincipal, newCollateral
                        loanParamsId,
                        loanId,
                        isTorqueLoan,
                        leverageAmount, // initialMargin
                        sentAddresses,
                        sentAmounts,
                        loanDataBytes
                    );
                    require (sentAmounts[1] != 0, "25");
            
                    return (sentAmounts[1], sentAmounts[4]); // newPrincipal, newCollateral
                }
            
                // sentAddresses[0]: lender
                // sentAddresses[1]: borrower
                // sentAddresses[2]: receiver
                // sentAddresses[3]: manager
                // sentAmounts[0]: interestRate
                // sentAmounts[1]: newPrincipal
                // sentAmounts[2]: interestInitialAmount
                // sentAmounts[3]: loanTokenSent
                // sentAmounts[4]: collateralTokenSent
                function _verifyTransfers(
                    address collateralTokenAddress,
                    address[4] memory sentAddresses,
                    uint256[5] memory sentAmounts,
                    uint256 withdrawalAmount)
                    internal
                    returns (uint256 msgValue)
                {
                    address _wethToken = wethToken;
                    address _loanTokenAddress = loanTokenAddress;
                    address receiver = sentAddresses[2];
                    uint256 newPrincipal = sentAmounts[1];
                    uint256 loanTokenSent = sentAmounts[3];
                    uint256 collateralTokenSent = sentAmounts[4];
            
                    require(_loanTokenAddress != collateralTokenAddress, "26");
            
                    msgValue = msg.value;
            
                    if (withdrawalAmount != 0) { // withdrawOnOpen == true
                        _safeTransfer(_loanTokenAddress, receiver, withdrawalAmount, "27");
                        if (newPrincipal > withdrawalAmount) {
                            _safeTransfer(_loanTokenAddress, bZxContract, newPrincipal - withdrawalAmount, "27");
                        }
                    } else {
                        _safeTransfer(_loanTokenAddress, bZxContract, newPrincipal, "27");
                    }
            
                    if (collateralTokenSent != 0) {
                        if (collateralTokenAddress == _wethToken && msgValue != 0 && msgValue >= collateralTokenSent) {
                            IWeth(_wethToken).deposit.value(collateralTokenSent)();
                            _safeTransfer(collateralTokenAddress, bZxContract, collateralTokenSent, "28");
                            msgValue -= collateralTokenSent;
                        } else {
                            _safeTransferFrom(collateralTokenAddress, msg.sender, bZxContract, collateralTokenSent, "28");
                        }
                    }
            
                    if (loanTokenSent != 0) {
                        _safeTransferFrom(_loanTokenAddress, msg.sender, bZxContract, loanTokenSent, "29");
                    }
                }
            
                function _safeTransfer(
                    address token,
                    address to,
                    uint256 amount,
                    string memory errorMsg)
                    internal
                {
                    _callOptionalReturn(
                        token,
                        abi.encodeWithSelector(IERC20(token).transfer.selector, to, amount),
                        errorMsg
                    );
                }
            
                function _safeTransferFrom(
                    address token,
                    address from,
                    address to,
                    uint256 amount,
                    string memory errorMsg)
                    internal
                {
                    _callOptionalReturn(
                        token,
                        abi.encodeWithSelector(IERC20(token).transferFrom.selector, from, to, amount),
                        errorMsg
                    );
                }
            
                function _callOptionalReturn(
                    address token,
                    bytes memory data,
                    string memory errorMsg)
                    internal
                {
                    (bool success, bytes memory returndata) = token.call(data);
                    require(success, errorMsg);
            
                    if (returndata.length != 0) {
                        require(abi.decode(returndata, (bool)), errorMsg);
                    }
                }
            
                function _underlyingBalance()
                    internal
                    view
                    returns (uint256)
                {
                    return IERC20(loanTokenAddress).balanceOf(address(this));
                }
            
                /* Internal View functions */
            
                function _tokenPrice(
                    uint256 assetSupply)
                    internal
                    view
                    returns (uint256)
                {
                    uint256 totalTokenSupply = totalSupply_;
            
                    return totalTokenSupply != 0 ?
                        assetSupply
                            .mul(WEI_PRECISION)
                            .div(totalTokenSupply) : initialPrice;
                }
            
                function _avgBorrowInterestRate(
                    uint256 assetBorrow)
                    internal
                    view
                    returns (uint256)
                {
                    if (assetBorrow != 0) {
                        (uint256 interestOwedPerDay,) = _getAllInterest();
                        return interestOwedPerDay
                            .mul(365 * WEI_PERCENT_PRECISION)
                            .div(assetBorrow);
                    }
                }
            
                // next supply interest adjustment
                function _supplyInterestRate(
                    uint256 assetBorrow,
                    uint256 assetSupply)
                    internal
                    view
                    returns (uint256)
                {
                    if (assetBorrow != 0 && assetSupply >= assetBorrow) {
                        return _avgBorrowInterestRate(assetBorrow)
                            .mul(_utilizationRate(assetBorrow, assetSupply))
                            .mul(SafeMath.sub(WEI_PERCENT_PRECISION, ProtocolLike(bZxContract).lendingFeePercent()))
                            .div(WEI_PERCENT_PRECISION * WEI_PERCENT_PRECISION);
                    }
                }
            
                function _nextBorrowInterestRate(
                    uint256 borrowAmount)
                    internal
                    view
                    returns (uint256)
                {
                    uint256 interestUnPaid;
                    if (borrowAmount != 0) {
                        if (lastSettleTime_ != uint88(block.timestamp)) {
                            (,interestUnPaid) = _getAllInterest();
                        }
            
                        uint256 balance = _underlyingBalance()
                            .add(interestUnPaid);
                        if (borrowAmount > balance) {
                            borrowAmount = balance;
                        }
                    }
            
                    return _nextBorrowInterestRate2(
                        borrowAmount,
                        _totalAssetSupply(interestUnPaid)
                    );
                }
            
                function _nextBorrowInterestRate2(
                    uint256 newBorrowAmount,
                    uint256 assetSupply)
                    internal
                    view
                    returns (uint256 nextRate)
                {
                    uint256 utilRate = _utilizationRate(
                        totalAssetBorrow().add(newBorrowAmount),
                        assetSupply
                    );
            
                    uint256 thisMinRate;
                    uint256 thisMaxRate;
                    uint256 thisBaseRate = baseRate;
                    uint256 thisRateMultiplier = rateMultiplier;
                    uint256 thisTargetLevel = targetLevel;
                    uint256 thisKinkLevel = kinkLevel;
                    uint256 thisMaxScaleRate = maxScaleRate;
            
                    if (utilRate < thisTargetLevel) {
                        // target targetLevel utilization when utilization is under targetLevel
                        utilRate = thisTargetLevel;
                    }
            
                    if (utilRate > thisKinkLevel) {
                        // scale rate proportionally up to 100%
                        uint256 thisMaxRange = WEI_PERCENT_PRECISION - thisKinkLevel; // will not overflow
            
                        utilRate -= thisKinkLevel;
                        if (utilRate > thisMaxRange)
                            utilRate = thisMaxRange;
            
                        thisMaxRate = thisRateMultiplier
                            .add(thisBaseRate)
                            .mul(thisKinkLevel)
                            .div(WEI_PERCENT_PRECISION);
            
                        nextRate = utilRate
                            .mul(SafeMath.sub(thisMaxScaleRate, thisMaxRate))
                            .div(thisMaxRange)
                            .add(thisMaxRate);
                    } else {
                        nextRate = utilRate
                            .mul(thisRateMultiplier)
                            .div(WEI_PERCENT_PRECISION)
                            .add(thisBaseRate);
            
                        thisMinRate = thisBaseRate;
                        thisMaxRate = thisRateMultiplier
                            .add(thisBaseRate);
            
                        if (nextRate < thisMinRate)
                            nextRate = thisMinRate;
                        else if (nextRate > thisMaxRate)
                            nextRate = thisMaxRate;
                    }
                }
            
                function _getAllInterest()
                    internal
                    view
                    returns (
                        uint256 interestOwedPerDay,
                        uint256 interestUnPaid)
                {
                    // interestPaid, interestPaidDate, interestOwedPerDay, interestUnPaid, interestFeePercent, principalTotal
                    uint256 interestFeePercent;
                    (,,interestOwedPerDay,interestUnPaid,interestFeePercent,) = ProtocolLike(bZxContract).getLenderInterestData(
                        address(this),
                        loanTokenAddress
                    );
            
                    interestUnPaid = interestUnPaid
                        .mul(SafeMath.sub(WEI_PERCENT_PRECISION, interestFeePercent))
                        .div(WEI_PERCENT_PRECISION);
                }
            
                function _getMarginBorrowAmountAndRate(
                    uint256 leverageAmount,
                    uint256 depositAmount)
                    internal
                    view
                    returns (uint256 borrowAmount, uint256 interestRate)
                {
                    uint256 initialMargin = SafeMath.div(WEI_PRECISION * WEI_PERCENT_PRECISION, leverageAmount);
            
                    interestRate = _nextBorrowInterestRate2(
                        depositAmount
                            .mul(WEI_PERCENT_PRECISION)
                            .div(initialMargin),
                        _totalAssetSupply(0)
                    );
            
                    // assumes that loan, collateral, and interest token are the same
                    borrowAmount = depositAmount
                        .mul(WEI_PERCENT_PRECISION * WEI_PERCENT_PRECISION)
                        .div(_adjustValue(
                            interestRate,
                            2419200, // 28 day duration for margin trades
                            initialMargin))
                        .div(initialMargin);
                }
            
                function _totalAssetSupply(
                    uint256 interestUnPaid)
                    internal
                    view
                    returns (uint256) // assetSupply
                {
                    if (totalSupply_ != 0) {
                        uint256 assetsBalance = _flTotalAssetSupply; // temporary locked totalAssetSupply during a flash loan transaction
                        if (assetsBalance == 0) {
                            assetsBalance = _underlyingBalance()
                                .add(totalAssetBorrow());
                        }
            
                        return assetsBalance
                            .add(interestUnPaid);
                    }
                }
            
                function _adjustValue(
                    uint256 interestRate,
                    uint256 maxDuration,
                    uint256 marginAmount)
                    internal
                    pure
                    returns (uint256)
                {
                    return maxDuration != 0 ?
                        interestRate
                            .mul(WEI_PERCENT_PRECISION)
                            .mul(maxDuration)
                            .div(31536000) // 86400 * 365
                            .div(marginAmount)
                            .add(WEI_PERCENT_PRECISION) :
                        WEI_PERCENT_PRECISION;
                }
            
                function _utilizationRate(
                    uint256 assetBorrow,
                    uint256 assetSupply)
                    internal
                    pure
                    returns (uint256)
                {
                    if (assetBorrow != 0 && assetSupply != 0) {
                        // U = total_borrow / total_supply
                        return assetBorrow
                            .mul(WEI_PERCENT_PRECISION)
                            .div(assetSupply);
                    }
                }
            
            
                /* Owner-Only functions */
            
                function updateSettings(
                    address settingsTarget,
                    bytes memory callData)
                    public
                {
                    if (msg.sender != owner()) {
                        address _lowerAdmin;
                        address _lowerAdminContract;
                        assembly {
                            _lowerAdmin := sload(iToken_LowerAdminAddress)
                            _lowerAdminContract := sload(iToken_LowerAdminContract)
                        }
                        require(msg.sender == _lowerAdmin && settingsTarget == _lowerAdminContract);
                    }
            
                    address currentTarget = target_;
                    target_ = settingsTarget;
            
                    (bool result,) = address(this).call(callData);
            
                    uint256 size;
                    uint256 ptr;
                    assembly {
                        size := returndatasize
                        ptr := mload(0x40)
                        returndatacopy(ptr, 0, size)
                        if eq(result, 0) { revert(ptr, size) }
                    }
            
                    target_ = currentTarget;
            
                    assembly {
                        return(ptr, size)
                    }
                }
            }

            File 4 of 5: LoanMaintenance
            /**
             * Copyright 2017-2020, bZeroX, LLC <https://bzx.network/>. All Rights Reserved.
             * Licensed under the Apache License, Version 2.0.
             */
            
            pragma solidity 0.5.17;
            pragma experimental ABIEncoderV2;
            
            
            interface IWeth {
                function deposit() external payable;
                function withdraw(uint256 wad) external;
            }
            
            contract IERC20 {
                string public name;
                uint8 public decimals;
                string public symbol;
                function totalSupply() public view returns (uint256);
                function balanceOf(address _who) public view returns (uint256);
                function allowance(address _owner, address _spender) public view returns (uint256);
                function approve(address _spender, uint256 _value) public returns (bool);
                function transfer(address _to, uint256 _value) public returns (bool);
                function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
                event Transfer(address indexed from, address indexed to, uint256 value);
                event Approval(address indexed owner, address indexed spender, uint256 value);
            }
            
            contract IWethERC20 is IWeth, IERC20 {}
            
            contract Constants {
            
                uint256 internal constant WEI_PRECISION = 10**18;
                uint256 internal constant WEI_PERCENT_PRECISION = 10**20;
            
                uint256 internal constant DAYS_IN_A_YEAR = 365;
                uint256 internal constant ONE_MONTH = 2628000; // approx. seconds in a month
            
                string internal constant UserRewardsID = "UserRewards";
            
                IWethERC20 public constant wethToken = IWethERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
                address public constant bzrxTokenAddress = 0x56d811088235F11C8920698a204A5010a788f4b3;
                address public constant vbzrxTokenAddress = 0xB72B31907C1C95F3650b64b2469e08EdACeE5e8F;
            }
            
            /**
             * @dev Library for managing loan sets
             *
             * Sets have the following properties:
             *
             * - Elements are added, removed, and checked for existence in constant time
             * (O(1)).
             * - Elements are enumerated in O(n). No guarantees are made on the ordering.
             *
             * Include with `using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;`.
             *
             */
            library EnumerableBytes32Set {
            
                struct Bytes32Set {
                    // Position of the value in the `values` array, plus 1 because index 0
                    // means a value is not in the set.
                    mapping (bytes32 => uint256) index;
                    bytes32[] values;
                }
            
                /**
                 * @dev Add an address value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return addBytes32(set, value);
                }
            
                /**
                 * @dev Add a value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (!contains(set, value)){
                        set.index[value] = set.values.push(value);
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Removes an address value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return removeBytes32(set, value);
                }
            
                /**
                 * @dev Removes a value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (contains(set, value)){
                        uint256 toDeleteIndex = set.index[value] - 1;
                        uint256 lastIndex = set.values.length - 1;
            
                        // If the element we're deleting is the last one, we can just remove it without doing a swap
                        if (lastIndex != toDeleteIndex) {
                            bytes32 lastValue = set.values[lastIndex];
            
                            // Move the last value to the index where the deleted value is
                            set.values[toDeleteIndex] = lastValue;
                            // Update the index for the moved value
                            set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based
                        }
            
                        // Delete the index entry for the deleted value
                        delete set.index[value];
            
                        // Delete the old entry for the moved value
                        set.values.pop();
            
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(Bytes32Set storage set, bytes32 value)
                    internal
                    view
                    returns (bool)
                {
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function containsAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    view
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns an array with all values in the set. O(N).
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
            
                 * WARNING: This function may run out of gas on large sets: use {length} and
                 * {get} instead in these cases.
                 */
                function enumerate(Bytes32Set storage set, uint256 start, uint256 count)
                    internal
                    view
                    returns (bytes32[] memory output)
                {
                    uint256 end = start + count;
                    require(end >= start, "addition overflow");
                    end = set.values.length < end ? set.values.length : end;
                    if (end == 0 || start >= end) {
                        return output;
                    }
            
                    output = new bytes32[](end-start);
                    for (uint256 i = start; i < end; i++) {
                        output[i-start] = set.values[i];
                    }
                    return output;
                }
            
                /**
                 * @dev Returns the number of elements on the set. O(1).
                 */
                function length(Bytes32Set storage set)
                    internal
                    view
                    returns (uint256)
                {
                    return set.values.length;
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function get(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (bytes32)
                {
                    return set.values[index];
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function getAddress(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (address)
                {
                    bytes32 value = set.values[index];
                    address addrvalue;
                    assembly {
                        addrvalue := value
                    }
                    return addrvalue;
                }
            }
            
            /**
             * @title Helps contracts guard against reentrancy attacks.
             * @author Remco Bloemen <remco@2π.com>, Eenae <alexey@mixbytes.io>
             * @dev If you mark a function `nonReentrant`, you should also
             * mark it `external`.
             */
            contract ReentrancyGuard {
            
                /// @dev Constant for unlocked guard state - non-zero to prevent extra gas costs.
                /// See: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1056
                uint256 internal constant REENTRANCY_GUARD_FREE = 1;
            
                /// @dev Constant for locked guard state
                uint256 internal constant REENTRANCY_GUARD_LOCKED = 2;
            
                /**
                * @dev We use a single lock for the whole contract.
                */
                uint256 internal reentrancyLock = REENTRANCY_GUARD_FREE;
            
                /**
                * @dev Prevents a contract from calling itself, directly or indirectly.
                * If you mark a function `nonReentrant`, you should also
                * mark it `external`. Calling one `nonReentrant` function from
                * another is not supported. Instead, you can implement a
                * `private` function doing the actual work, and an `external`
                * wrapper marked as `nonReentrant`.
                */
                modifier nonReentrant() {
                    require(reentrancyLock == REENTRANCY_GUARD_FREE, "nonReentrant");
                    reentrancyLock = REENTRANCY_GUARD_LOCKED;
                    _;
                    reentrancyLock = REENTRANCY_GUARD_FREE;
                }
            }
            
            /*
             * @dev Provides information about the current execution context, including the
             * sender of the transaction and its data. While these are generally available
             * via msg.sender and msg.data, they should not be accessed in such a direct
             * manner, since when dealing with GSN meta-transactions the account sending and
             * paying for execution may not be the actual sender (as far as an application
             * is concerned).
             *
             * This contract is only required for intermediate, library-like contracts.
             */
            contract Context {
                // Empty internal constructor, to prevent people from mistakenly deploying
                // an instance of this contract, which should be used via inheritance.
                constructor () internal { }
                // solhint-disable-previous-line no-empty-blocks
            
                function _msgSender() internal view returns (address payable) {
                    return msg.sender;
                }
            
                function _msgData() internal view returns (bytes memory) {
                    this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                    return msg.data;
                }
            }
            
            /**
             * @dev Contract module which provides a basic access control mechanism, where
             * there is an account (an owner) that can be granted exclusive access to
             * specific functions.
             *
             * This module is used through inheritance. It will make available the modifier
             * `onlyOwner`, which can be applied to your functions to restrict their use to
             * the owner.
             */
            contract Ownable is Context {
                address private _owner;
            
                event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            
                /**
                 * @dev Initializes the contract setting the deployer as the initial owner.
                 */
                constructor () internal {
                    address msgSender = _msgSender();
                    _owner = msgSender;
                    emit OwnershipTransferred(address(0), msgSender);
                }
            
                /**
                 * @dev Returns the address of the current owner.
                 */
                function owner() public view returns (address) {
                    return _owner;
                }
            
                /**
                 * @dev Throws if called by any account other than the owner.
                 */
                modifier onlyOwner() {
                    require(isOwner(), "unauthorized");
                    _;
                }
            
                /**
                 * @dev Returns true if the caller is the current owner.
                 */
                function isOwner() public view returns (bool) {
                    return _msgSender() == _owner;
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Can only be called by the current owner.
                 */
                function transferOwnership(address newOwner) public onlyOwner {
                    _transferOwnership(newOwner);
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 */
                function _transferOwnership(address newOwner) internal {
                    require(newOwner != address(0), "Ownable: new owner is the zero address");
                    emit OwnershipTransferred(_owner, newOwner);
                    _owner = newOwner;
                }
            }
            
            /**
             * @dev Wrappers over Solidity's arithmetic operations with added overflow
             * checks.
             *
             * Arithmetic operations in Solidity wrap on overflow. This can easily result
             * in bugs, because programmers usually assume that an overflow raises an
             * error, which is the standard behavior in high level programming languages.
             * `SafeMath` restores this intuition by reverting the transaction when an
             * operation overflows.
             *
             * Using this library instead of the unchecked operations eliminates an entire
             * class of bugs, so it's recommended to use it always.
             */
            library SafeMath {
                /**
                 * @dev Returns the addition of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `+` operator.
                 *
                 * Requirements:
                 * - Addition cannot overflow.
                 */
                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                    uint256 c = a + b;
                    require(c >= a, "SafeMath: addition overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 */
                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                    return sub(a, b, "SafeMath: subtraction overflow");
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 *
                 * _Available since v2.4.0._
                 */
                function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b <= a, errorMessage);
                    uint256 c = a - b;
            
                    return c;
                }
            
                /**
                 * @dev Returns the multiplication of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `*` operator.
                 *
                 * Requirements:
                 * - Multiplication cannot overflow.
                 */
                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) {
                        return 0;
                    }
            
                    uint256 c = a * b;
                    require(c / a == b, "SafeMath: multiplication overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                    return div(a, b, "SafeMath: division by zero");
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
                    uint256 c = a / b;
                    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            
                    return c;
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
                    return divCeil(a, b, "SafeMath: division by zero");
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
            
                    if (a == 0) {
                        return 0;
                    }
                    uint256 c = ((a - 1) / b) + 1;
            
                    return c;
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                    return mod(a, b, "SafeMath: modulo by zero");
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts with custom message when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b != 0, errorMessage);
                    return a % b;
                }
            
                function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
                    return _a < _b ? _a : _b;
                }
            }
            
            /**
             * @dev Collection of functions related to the address type
             */
            library Address {
                /**
                 * @dev Returns true if `account` is a contract.
                 *
                 * [IMPORTANT]
                 * ====
                 * It is unsafe to assume that an address for which this function returns
                 * false is an externally-owned account (EOA) and not a contract.
                 *
                 * Among others, `isContract` will return false for the following 
                 * types of addresses:
                 *
                 *  - an externally-owned account
                 *  - a contract in construction
                 *  - an address where a contract will be created
                 *  - an address where a contract lived, but was destroyed
                 * ====
                 */
                function isContract(address account) internal view returns (bool) {
                    // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                    // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                    // for accounts without code, i.e. `keccak256('')`
                    bytes32 codehash;
                    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                    // solhint-disable-next-line no-inline-assembly
                    assembly { codehash := extcodehash(account) }
                    return (codehash != accountHash && codehash != 0x0);
                }
            
                /**
                 * @dev Converts an `address` into `address payable`. Note that this is
                 * simply a type cast: the actual underlying value is not changed.
                 *
                 * _Available since v2.4.0._
                 */
                function toPayable(address account) internal pure returns (address payable) {
                    return address(uint160(account));
                }
            
                /**
                 * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                 * `recipient`, forwarding all available gas and reverting on errors.
                 *
                 * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                 * of certain opcodes, possibly making contracts go over the 2300 gas limit
                 * imposed by `transfer`, making them unable to receive funds via
                 * `transfer`. {sendValue} removes this limitation.
                 *
                 * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                 *
                 * IMPORTANT: because control is transferred to `recipient`, care must be
                 * taken to not create reentrancy vulnerabilities. Consider using
                 * {ReentrancyGuard} or the
                 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                 *
                 * _Available since v2.4.0._
                 */
                function sendValue(address recipient, uint256 amount) internal {
                    require(address(this).balance >= amount, "Address: insufficient balance");
            
                    // solhint-disable-next-line avoid-call-value
                    (bool success, ) = recipient.call.value(amount)("");
                    require(success, "Address: unable to send value, recipient may have reverted");
                }
            }
            
            /**
             * @title SafeERC20
             * @dev Wrappers around ERC20 operations that throw on failure (when the token
             * contract returns false). Tokens that return no value (and instead revert or
             * throw on failure) are also supported, non-reverting calls are assumed to be
             * successful.
             * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
             * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
             */
            library SafeERC20 {
                using SafeMath for uint256;
                using Address for address;
            
                function safeTransfer(IERC20 token, address to, uint256 value) internal {
                    callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                }
            
                function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                    callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                }
            
                function safeApprove(IERC20 token, address spender, uint256 value) internal {
                    // safeApprove should only be called when setting an initial allowance,
                    // or when resetting it to zero. To increase and decrease it, use
                    // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                    // solhint-disable-next-line max-line-length
                    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));
                }
            
                /**
                 * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                 * on the return value: the return value is optional (but if data is returned, it must not be false).
                 * @param token The token targeted by the call.
                 * @param data The call data (encoded using abi.encode or one of its variants).
                 */
                function callOptionalReturn(IERC20 token, bytes memory data) private {
                    // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                    // we're implementing it ourselves.
            
                    // A Solidity high level call has three parts:
                    //  1. The target address is checked to verify it contains contract code
                    //  2. The call itself is made, and success asserted
                    //  3. The return value is decoded, which in turn checks the size of the returned data.
                    // solhint-disable-next-line max-line-length
                    require(address(token).isContract(), "SafeERC20: call to non-contract");
            
                    // solhint-disable-next-line avoid-low-level-calls
                    (bool success, bytes memory returndata) = address(token).call(data);
                    require(success, "SafeERC20: low-level call failed");
            
                    if (returndata.length > 0) { // Return data is optional
                        // solhint-disable-next-line max-line-length
                        require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                    }
                }
            }
            
            contract LoanStruct {
                struct Loan {
                    bytes32 id;                 // id of the loan
                    bytes32 loanParamsId;       // the linked loan params id
                    bytes32 pendingTradesId;    // the linked pending trades id
                    uint256 principal;          // total borrowed amount outstanding
                    uint256 collateral;         // total collateral escrowed for the loan
                    uint256 startTimestamp;     // loan start time
                    uint256 endTimestamp;       // for active loans, this is the expected loan end time, for in-active loans, is the actual (past) end time
                    uint256 startMargin;        // initial margin when the loan opened
                    uint256 startRate;          // reference rate when the loan opened for converting collateralToken to loanToken
                    address borrower;           // borrower of this loan
                    address lender;             // lender of this loan
                    bool active;                // if false, the loan has been fully closed
                }
            }
            
            contract LoanParamsStruct {
                struct LoanParams {
                    bytes32 id;                 // id of loan params object
                    bool active;                // if false, this object has been disabled by the owner and can't be used for future loans
                    address owner;              // owner of this object
                    address loanToken;          // the token being loaned
                    address collateralToken;    // the required collateral token
                    uint256 minInitialMargin;   // the minimum allowed initial margin
                    uint256 maintenanceMargin;  // an unhealthy loan when current margin is at or below this value
                    uint256 maxLoanTerm;        // the maximum term for new loans (0 means there's no max term)
                }
            }
            
            contract OrderStruct {
                struct Order {
                    uint256 lockedAmount;           // escrowed amount waiting for a counterparty
                    uint256 interestRate;           // interest rate defined by the creator of this order
                    uint256 minLoanTerm;            // minimum loan term allowed
                    uint256 maxLoanTerm;            // maximum loan term allowed
                    uint256 createdTimestamp;       // timestamp when this order was created
                    uint256 expirationTimestamp;    // timestamp when this order expires
                }
            }
            
            contract LenderInterestStruct {
                struct LenderInterest {
                    uint256 principalTotal;     // total borrowed amount outstanding of asset
                    uint256 owedPerDay;         // interest owed per day for all loans of asset
                    uint256 owedTotal;          // total interest owed for all loans of asset (assuming they go to full term)
                    uint256 paidTotal;          // total interest paid so far for asset
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract LoanInterestStruct {
                struct LoanInterest {
                    uint256 owedPerDay;         // interest owed per day for loan
                    uint256 depositTotal;       // total escrowed interest for loan
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract Objects is
                LoanStruct,
                LoanParamsStruct,
                OrderStruct,
                LenderInterestStruct,
                LoanInterestStruct
            {}
            
            contract State is Constants, Objects, ReentrancyGuard, Ownable {
                using SafeMath for uint256;
                using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;
            
                address public priceFeeds;                                                              // handles asset reference price lookups
                address public swapsImpl;                                                               // handles asset swaps using dex liquidity
            
                mapping (bytes4 => address) public logicTargets;                                        // implementations of protocol functions
            
                mapping (bytes32 => Loan) public loans;                                                 // loanId => Loan
                mapping (bytes32 => LoanParams) public loanParams;                                      // loanParamsId => LoanParams
            
                mapping (address => mapping (bytes32 => Order)) public lenderOrders;                    // lender => orderParamsId => Order
                mapping (address => mapping (bytes32 => Order)) public borrowerOrders;                  // borrower => orderParamsId => Order
            
                mapping (bytes32 => mapping (address => bool)) public delegatedManagers;                // loanId => delegated => approved
            
                // Interest
                mapping (address => mapping (address => LenderInterest)) public lenderInterest;         // lender => loanToken => LenderInterest object
                mapping (bytes32 => LoanInterest) public loanInterest;                                  // loanId => LoanInterest object
            
                // Internals
                EnumerableBytes32Set.Bytes32Set internal logicTargetsSet;                               // implementations set
                EnumerableBytes32Set.Bytes32Set internal activeLoansSet;                                // active loans set
            
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal lenderLoanSets;           // lender loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal borrowerLoanSets;         // borrow loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal userLoanParamSets;        // user loan params set
            
                address public feesController;                                                          // address controlling fee withdrawals
            
                uint256 public lendingFeePercent = 10 ether; // 10% fee                                 // fee taken from lender interest payments
                mapping (address => uint256) public lendingFeeTokensHeld;                               // total interest fees received and not withdrawn per asset
                mapping (address => uint256) public lendingFeeTokensPaid;                               // total interest fees withdraw per asset (lifetime fees = lendingFeeTokensHeld + lendingFeeTokensPaid)
            
                uint256 public tradingFeePercent = 0.15 ether; // 0.15% fee                             // fee paid for each trade
                mapping (address => uint256) public tradingFeeTokensHeld;                               // total trading fees received and not withdrawn per asset
                mapping (address => uint256) public tradingFeeTokensPaid;                               // total trading fees withdraw per asset (lifetime fees = tradingFeeTokensHeld + tradingFeeTokensPaid)
            
                uint256 public borrowingFeePercent = 0.09 ether; // 0.09% fee                           // origination fee paid for each loan
                mapping (address => uint256) public borrowingFeeTokensHeld;                             // total borrowing fees received and not withdrawn per asset
                mapping (address => uint256) public borrowingFeeTokensPaid;                             // total borrowing fees withdraw per asset (lifetime fees = borrowingFeeTokensHeld + borrowingFeeTokensPaid)
            
                uint256 public protocolTokenHeld;                                                       // current protocol token deposit balance
                uint256 public protocolTokenPaid;                                                       // lifetime total payout of protocol token
            
                uint256 public affiliateFeePercent = 30 ether; // 30% fee share                         // fee share for affiliate program
            
                mapping (address => mapping (address => uint256)) public liquidationIncentivePercent;   // percent discount on collateral for liquidators per loanToken and collateralToken
            
                mapping (address => address) public loanPoolToUnderlying;                               // loanPool => underlying
                mapping (address => address) public underlyingToLoanPool;                               // underlying => loanPool
                EnumerableBytes32Set.Bytes32Set internal loanPoolsSet;                                  // loan pools set
            
                mapping (address => bool) public supportedTokens;                                       // supported tokens for swaps
            
                uint256 public maxDisagreement = 5 ether;                                               // % disagreement between swap rate and reference rate
            
                uint256 public sourceBufferPercent = 5 ether;                                           // used to estimate kyber swap source amount
            
                uint256 public maxSwapSize = 1500 ether;                                                // maximum supported swap size in ETH
            
            
                function _setTarget(
                    bytes4 sig,
                    address target)
                    internal
                {
                    logicTargets[sig] = target;
            
                    if (target != address(0)) {
                        logicTargetsSet.addBytes32(bytes32(sig));
                    } else {
                        logicTargetsSet.removeBytes32(bytes32(sig));
                    }
                }
            }
            
            interface IPriceFeeds {
                function queryRate(
                    address sourceToken,
                    address destToken)
                    external
                    view
                    returns (uint256 rate, uint256 precision);
            
                function queryPrecision(
                    address sourceToken,
                    address destToken)
                    external
                    view
                    returns (uint256 precision);
            
                function queryReturn(
                    address sourceToken,
                    address destToken,
                    uint256 sourceAmount)
                    external
                    view
                    returns (uint256 destAmount);
            
                function checkPriceDisagreement(
                    address sourceToken,
                    address destToken,
                    uint256 sourceAmount,
                    uint256 destAmount,
                    uint256 maxSlippage)
                    external
                    view
                    returns (uint256 sourceToDestSwapRate);
            
                function amountInEth(
                    address Token,
                    uint256 amount)
                    external
                    view
                    returns (uint256 ethAmount);
            
                function getMaxDrawdown(
                    address loanToken,
                    address collateralToken,
                    uint256 loanAmount,
                    uint256 collateralAmount,
                    uint256 maintenanceMargin)
                    external
                    view
                    returns (uint256);
            
                function getCurrentMarginAndCollateralSize(
                    address loanToken,
                    address collateralToken,
                    uint256 loanAmount,
                    uint256 collateralAmount)
                    external
                    view
                    returns (uint256 currentMargin, uint256 collateralInEthAmount);
            
                function getCurrentMargin(
                    address loanToken,
                    address collateralToken,
                    uint256 loanAmount,
                    uint256 collateralAmount)
                    external
                    view
                    returns (uint256 currentMargin, uint256 collateralToLoanRate);
            
                function shouldLiquidate(
                    address loanToken,
                    address collateralToken,
                    uint256 loanAmount,
                    uint256 collateralAmount,
                    uint256 maintenanceMargin)
                    external
                    view
                    returns (bool);
            
                function getFastGasPrice(
                    address payToken)
                    external
                    view
                    returns (uint256);
            }
            
            contract FeesEvents {
            
                event PayLendingFee(
                    address indexed payer,
                    address indexed token,
                    uint256 amount
                );
            
                event PayTradingFee(
                    address indexed payer,
                    address indexed token,
                    bytes32 indexed loanId,
                    uint256 amount
                );
            
                event PayBorrowingFee(
                    address indexed payer,
                    address indexed token,
                    bytes32 indexed loanId,
                    uint256 amount
                );
            
                event EarnReward(
                    address indexed receiver,
                    address indexed token,
                    bytes32 indexed loanId,
                    uint256 amount
                );
            }
            
            contract FeesHelper is State, FeesEvents {
                using SafeERC20 for IERC20;
            
                // calculate trading fee
                function _getTradingFee(
                    uint256 feeTokenAmount)
                    internal
                    view
                    returns (uint256)
                {
                    return feeTokenAmount
                        .mul(tradingFeePercent)
                        .divCeil(WEI_PERCENT_PRECISION);
                }
            
                // calculate loan origination fee
                function _getBorrowingFee(
                    uint256 feeTokenAmount)
                    internal
                    view
                    returns (uint256)
                {
                    return feeTokenAmount
                        .mul(borrowingFeePercent)
                        .divCeil(WEI_PERCENT_PRECISION);
                }
            
                // settle trading fee
                function _payTradingFee(
                    address user,
                    bytes32 loanId,
                    address feeToken,
                    uint256 tradingFee)
                    internal
                {
                    if (tradingFee != 0) {
                        tradingFeeTokensHeld[feeToken] = tradingFeeTokensHeld[feeToken]
                            .add(tradingFee);
            
                        emit PayTradingFee(
                            user,
                            feeToken,
                            loanId,
                            tradingFee
                        );
            
                        _payFeeReward(
                            user,
                            loanId,
                            feeToken,
                            tradingFee
                        );
                    }
                }
            
                // settle loan origination fee
                function _payBorrowingFee(
                    address user,
                    bytes32 loanId,
                    address feeToken,
                    uint256 borrowingFee)
                    internal
                {
                    if (borrowingFee != 0) {
                        borrowingFeeTokensHeld[feeToken] = borrowingFeeTokensHeld[feeToken]
                            .add(borrowingFee);
            
                        emit PayBorrowingFee(
                            user,
                            feeToken,
                            loanId,
                            borrowingFee
                        );
            
                        _payFeeReward(
                            user,
                            loanId,
                            feeToken,
                            borrowingFee
                        );
                    }
                }
            
                // settle lender (interest) fee
                function _payLendingFee(
                    address user,
                    address feeToken,
                    uint256 lendingFee)
                    internal
                {
                    if (lendingFee != 0) {
                        lendingFeeTokensHeld[feeToken] = lendingFeeTokensHeld[feeToken]
                            .add(lendingFee);
            
                        emit PayLendingFee(
                            user,
                            feeToken,
                            lendingFee
                        );
            
                         //// NOTE: Lenders do not receive a fee reward ////
                    }
                }
            
                // settles and pays borrowers based on the fees generated by their interest payments
                function _settleFeeRewardForInterestExpense(
                    LoanInterest storage loanInterestLocal,
                    bytes32 loanId,
                    address feeToken,
                    address user,
                    uint256 interestTime)
                    internal
                {
                    uint256 updatedTimestamp = loanInterestLocal.updatedTimestamp;
            
                    uint256 interestExpenseFee;
                    if (updatedTimestamp != 0) {
                        // this represents the fee generated by a borrower's interest payment
                        interestExpenseFee = interestTime
                            .sub(updatedTimestamp)
                            .mul(loanInterestLocal.owedPerDay)
                            .mul(lendingFeePercent)
                            .div(1 days * WEI_PERCENT_PRECISION);
                    }
            
                    loanInterestLocal.updatedTimestamp = interestTime;
            
                    if (interestExpenseFee != 0) {
                        _payFeeReward(
                            user,
                            loanId,
                            feeToken,
                            interestExpenseFee
                        );
                    }
                }
            
                // pay protocolToken reward to user
                function _payFeeReward(
                    address user,
                    bytes32 loanId,
                    address feeToken,
                    uint256 feeAmount)
                    internal
                {
                    // The protocol is designed to allow positions and loans to be closed, if for whatever reason
                    // the price lookup is failing, returning 0, or is otherwise paused. Therefore, we allow this
                    // call to fail silently, rather than revert, to allow the transaction to continue without a
                    // BZRX token reward.
                    uint256 rewardAmount;
                    address _priceFeeds = priceFeeds;
                    (bool success, bytes memory data) = _priceFeeds.staticcall(
                        abi.encodeWithSelector(
                            IPriceFeeds(_priceFeeds).queryReturn.selector,
                            feeToken,
                            bzrxTokenAddress, // price rewards using BZRX price rather than vesting token price
                            feeAmount / 2  // 50% of fee value
                        )
                    );
                    assembly {
                        if eq(success, 1) {
                            rewardAmount := mload(add(data, 32))
                        }
                    }
            
                    if (rewardAmount != 0) {
                        uint256 tokenBalance = protocolTokenHeld;
                        if (rewardAmount > tokenBalance) {
                            rewardAmount = tokenBalance;
                        }
                        if (rewardAmount != 0) {
                            protocolTokenHeld = tokenBalance
                                .sub(rewardAmount);
            
                            bytes32 slot = keccak256(abi.encodePacked(user, UserRewardsID));
                            assembly {
                                sstore(slot, add(sload(slot), rewardAmount))
                            }
            
                            emit EarnReward(
                                user,
                                vbzrxTokenAddress, // rewardToken
                                loanId,
                                rewardAmount
                            );
                        }
                    }
                }
            }
            
            contract VaultController is Constants {
                using SafeERC20 for IERC20;
            
                event VaultDeposit(
                    address indexed asset,
                    address indexed from,
                    uint256 amount
                );
                event VaultWithdraw(
                    address indexed asset,
                    address indexed to,
                    uint256 amount
                );
            
                function vaultEtherDeposit(
                    address from,
                    uint256 value)
                    internal
                {
                    IWethERC20 _wethToken = wethToken;
                    _wethToken.deposit.value(value)();
            
                    emit VaultDeposit(
                        address(_wethToken),
                        from,
                        value
                    );
                }
            
                function vaultEtherWithdraw(
                    address to,
                    uint256 value)
                    internal
                {
                    if (value != 0) {
                        IWethERC20 _wethToken = wethToken;
                        uint256 balance = address(this).balance;
                        if (value > balance) {
                            _wethToken.withdraw(value - balance);
                        }
                        Address.sendValue(to, value);
            
                        emit VaultWithdraw(
                            address(_wethToken),
                            to,
                            value
                        );
                    }
                }
            
                function vaultDeposit(
                    address token,
                    address from,
                    uint256 value)
                    internal
                {
                    if (value != 0) {
                        IERC20(token).safeTransferFrom(
                            from,
                            address(this),
                            value
                        );
            
                        emit VaultDeposit(
                            token,
                            from,
                            value
                        );
                    }
                }
            
                function vaultWithdraw(
                    address token,
                    address to,
                    uint256 value)
                    internal
                {
                    if (value != 0) {
                        IERC20(token).safeTransfer(
                            to,
                            value
                        );
            
                        emit VaultWithdraw(
                            token,
                            to,
                            value
                        );
                    }
                }
            
                function vaultTransfer(
                    address token,
                    address from,
                    address to,
                    uint256 value)
                    internal
                {
                    if (value != 0) {
                        if (from == address(this)) {
                            IERC20(token).safeTransfer(
                                to,
                                value
                            );
                        } else {
                            IERC20(token).safeTransferFrom(
                                from,
                                to,
                                value
                            );
                        }
                    }
                }
            
                function vaultApprove(
                    address token,
                    address to,
                    uint256 value)
                    internal
                {
                    if (value != 0 && IERC20(token).allowance(address(this), to) != 0) {
                        IERC20(token).safeApprove(to, 0);
                    }
                    IERC20(token).safeApprove(to, value);
                }
            }
            
            contract InterestUser is State, VaultController, FeesHelper {
                using SafeERC20 for IERC20;
            
                function _payInterest(
                    address lender,
                    address interestToken)
                    internal
                {
                    LenderInterest storage lenderInterestLocal = lenderInterest[lender][interestToken];
            
                    uint256 interestOwedNow = 0;
                    if (lenderInterestLocal.owedPerDay != 0 && lenderInterestLocal.updatedTimestamp != 0) {
                        interestOwedNow = block.timestamp
                            .sub(lenderInterestLocal.updatedTimestamp)
                            .mul(lenderInterestLocal.owedPerDay)
                            .div(1 days);
            
                        lenderInterestLocal.updatedTimestamp = block.timestamp;
            
                        if (interestOwedNow > lenderInterestLocal.owedTotal)
            	            interestOwedNow = lenderInterestLocal.owedTotal;
            
                        if (interestOwedNow != 0) {
                            lenderInterestLocal.paidTotal = lenderInterestLocal.paidTotal
                                .add(interestOwedNow);
                            lenderInterestLocal.owedTotal = lenderInterestLocal.owedTotal
                                .sub(interestOwedNow);
            
                            _payInterestTransfer(
                                lender,
                                interestToken,
                                interestOwedNow
                            );
                        }
                    } else {
                        lenderInterestLocal.updatedTimestamp = block.timestamp;
                    }
                }
            
                function _payInterestTransfer(
                    address lender,
                    address interestToken,
                    uint256 interestOwedNow)
                    internal
                {
                    uint256 lendingFee = interestOwedNow
                        .mul(lendingFeePercent)
                        .divCeil(WEI_PERCENT_PRECISION);
            
                    _payLendingFee(
                        lender,
                        interestToken,
                        lendingFee
                    );
            
                    // transfers the interest to the lender, less the interest fee
                    vaultWithdraw(
                        interestToken,
                        lender,
                        interestOwedNow
                            .sub(lendingFee)
                    );
                }
            }
            
            contract LiquidationHelper is State {
            
                function _getLiquidationAmounts(
                    uint256 principal,
                    uint256 collateral,
                    uint256 currentMargin,
                    uint256 maintenanceMargin,
                    uint256 collateralToLoanRate,
                    uint256 incentivePercent)
                    internal
                    view
                    returns (uint256 maxLiquidatable, uint256 maxSeizable)
                {
                    if (currentMargin > maintenanceMargin || collateralToLoanRate == 0) {
                        return (maxLiquidatable, maxSeizable);
                    } else if (currentMargin <= incentivePercent) {
                        return (principal, collateral);
                    }
            
                    uint256 desiredMargin = maintenanceMargin
                        .add(5 ether); // 5 percentage points above maintenance
            
                    // maxLiquidatable = ((1 + desiredMargin)*principal - collateralToLoanRate*collateral) / (desiredMargin - incentivePercent)
                    maxLiquidatable = desiredMargin
                        .add(WEI_PERCENT_PRECISION)
                        .mul(principal)
                        .div(WEI_PERCENT_PRECISION);
                    maxLiquidatable = maxLiquidatable
                        .sub(
                            collateral
                                .mul(collateralToLoanRate)
                                .div(WEI_PRECISION)
                        );
                    maxLiquidatable = maxLiquidatable
                        .mul(WEI_PERCENT_PRECISION)
                        .div(
                            desiredMargin
                                .sub(incentivePercent)
                        );
                    if (maxLiquidatable > principal) {
                        maxLiquidatable = principal;
                    }
            
                    // maxSeizable = maxLiquidatable * (1 + incentivePercent) / collateralToLoanRate
                    maxSeizable = maxLiquidatable
                        .mul(
                            incentivePercent
                                .add(WEI_PERCENT_PRECISION)
                        );
                    maxSeizable = maxSeizable
                        .div(collateralToLoanRate)
                        .div(100);
                    if (maxSeizable > collateral) {
                        maxSeizable = collateral;
                    }
            
                    return (maxLiquidatable, maxSeizable);
                }
            }
            
            contract SwapsEvents {
            
                event LoanSwap(
                    bytes32 indexed loanId,
                    address indexed sourceToken,
                    address indexed destToken,
                    address borrower,
                    uint256 sourceAmount,
                    uint256 destAmount
                );
            
                event ExternalSwap(
                    address indexed user,
                    address indexed sourceToken,
                    address indexed destToken,
                    uint256 sourceAmount,
                    uint256 destAmount
                );
            }
            
            interface ISwapsImpl {
                function dexSwap(
                    address sourceTokenAddress,
                    address destTokenAddress,
                    address receiverAddress,
                    address returnToSenderAddress,
                    uint256 minSourceTokenAmount,
                    uint256 maxSourceTokenAmount,
                    uint256 requiredDestTokenAmount)
                    external
                    returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed);
            
                function dexExpectedRate(
                    address sourceTokenAddress,
                    address destTokenAddress,
                    uint256 sourceTokenAmount)
                    external
                    view
                    returns (uint256);
            }
            
            contract SwapsUser is State, SwapsEvents, FeesHelper {
            
                function _loanSwap(
                    bytes32 loanId,
                    address sourceToken,
                    address destToken,
                    address user,
                    uint256 minSourceTokenAmount,
                    uint256 maxSourceTokenAmount,
                    uint256 requiredDestTokenAmount,
                    bool bypassFee,
                    bytes memory loanDataBytes)
                    internal
                    returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed, uint256 sourceToDestSwapRate)
                {
                    (destTokenAmountReceived, sourceTokenAmountUsed) = _swapsCall(
                        [
                            sourceToken,
                            destToken,
                            address(this), // receiver
                            address(this), // returnToSender
                            user
                        ],
                        [
                            minSourceTokenAmount,
                            maxSourceTokenAmount,
                            requiredDestTokenAmount
                        ],
                        loanId,
                        bypassFee,
                        loanDataBytes
                    );
            
                    // will revert if swap size too large
                    _checkSwapSize(sourceToken, sourceTokenAmountUsed);
            
                    // will revert if disagreement found
                    sourceToDestSwapRate = IPriceFeeds(priceFeeds).checkPriceDisagreement(
                        sourceToken,
                        destToken,
                        sourceTokenAmountUsed,
                        destTokenAmountReceived,
                        maxDisagreement
                    );
            
                    emit LoanSwap(
                        loanId,
                        sourceToken,
                        destToken,
                        user,
                        sourceTokenAmountUsed,
                        destTokenAmountReceived
                    );
                }
            
                function _swapsCall(
                    address[5] memory addrs,
                    uint256[3] memory vals,
                    bytes32 loanId,
                    bool miscBool, // bypassFee
                    bytes memory loanDataBytes)
                    internal
                    returns (uint256, uint256)
                {
                    //addrs[0]: sourceToken
                    //addrs[1]: destToken
                    //addrs[2]: receiver
                    //addrs[3]: returnToSender
                    //addrs[4]: user
                    //vals[0]:  minSourceTokenAmount
                    //vals[1]:  maxSourceTokenAmount
                    //vals[2]:  requiredDestTokenAmount
            
                    require(vals[0] != 0, "sourceAmount == 0");
            
                    uint256 destTokenAmountReceived;
                    uint256 sourceTokenAmountUsed;
            
                    uint256 tradingFee;
                    if (!miscBool) { // bypassFee
                        if (vals[2] == 0) {
                            // condition: vals[0] will always be used as sourceAmount
            
                            tradingFee = _getTradingFee(vals[0]);
                            if (tradingFee != 0) {
                                _payTradingFee(
                                    addrs[4], // user
                                    loanId,
                                    addrs[0], // sourceToken
                                    tradingFee
                                );
            
                                vals[0] = vals[0]
                                    .sub(tradingFee);
                            }
                        } else {
                            // condition: unknown sourceAmount will be used
            
                            tradingFee = _getTradingFee(vals[2]);
            
                            if (tradingFee != 0) {
                                vals[2] = vals[2]
                                    .add(tradingFee);
                            }
                        }
                    }
            
                    if (vals[1] == 0) {
                        vals[1] = vals[0];
                    } else {
                        require(vals[0] <= vals[1], "min greater than max");
                    }
            
                    require(loanDataBytes.length == 0, "invalid state");
                    (destTokenAmountReceived, sourceTokenAmountUsed) = _swapsCall_internal(
                        addrs,
                        vals
                    );
            
                    if (vals[2] == 0) {
                        // there's no minimum destTokenAmount, but all of vals[0] (minSourceTokenAmount) must be spent, and amount spent can't exceed vals[0]
                        require(sourceTokenAmountUsed == vals[0], "swap too large to fill");
            
                        if (tradingFee != 0) {
                            sourceTokenAmountUsed = sourceTokenAmountUsed + tradingFee; // will never overflow
                        }
                    } else {
                        // there's a minimum destTokenAmount required, but sourceTokenAmountUsed won't be greater than vals[1] (maxSourceTokenAmount)
                        require(sourceTokenAmountUsed <= vals[1], "swap fill too large");
                        require(destTokenAmountReceived >= vals[2], "insufficient swap liquidity");
            
                        if (tradingFee != 0) {
                            _payTradingFee(
                                addrs[4], // user
                                loanId, // loanId,
                                addrs[1], // destToken
                                tradingFee
                            );
            
                            destTokenAmountReceived = destTokenAmountReceived - tradingFee; // will never overflow
                        }
                    }
            
                    return (destTokenAmountReceived, sourceTokenAmountUsed);
                }
            
                function _swapsCall_internal(
                    address[5] memory addrs,
                    uint256[3] memory vals)
                    internal
                    returns (uint256 destTokenAmountReceived, uint256 sourceTokenAmountUsed)
                {
                    bytes memory data = abi.encodeWithSelector(
                        ISwapsImpl(swapsImpl).dexSwap.selector,
                        addrs[0], // sourceToken
                        addrs[1], // destToken
                        addrs[2], // receiverAddress
                        addrs[3], // returnToSenderAddress
                        vals[0],  // minSourceTokenAmount
                        vals[1],  // maxSourceTokenAmount
                        vals[2]   // requiredDestTokenAmount
                    );
            
                    bool success;
                    (success, data) = swapsImpl.delegatecall(data);
                    require(success, "swap failed");
            
                    (destTokenAmountReceived, sourceTokenAmountUsed) = abi.decode(data, (uint256, uint256));
                }
            
                function _swapsExpectedReturn(
                    address sourceToken,
                    address destToken,
                    uint256 sourceTokenAmount)
                    internal
                    view
                    returns (uint256)
                {
                    uint256 tradingFee = _getTradingFee(sourceTokenAmount);
                    if (tradingFee != 0) {
                        sourceTokenAmount = sourceTokenAmount
                            .sub(tradingFee);
                    }
            
                    uint256 sourceToDestRate = ISwapsImpl(swapsImpl).dexExpectedRate(
                        sourceToken,
                        destToken,
                        sourceTokenAmount
                    );
                    uint256 sourceToDestPrecision = IPriceFeeds(priceFeeds).queryPrecision(
                        sourceToken,
                        destToken
                    );
            
                    return sourceTokenAmount
                        .mul(sourceToDestRate)
                        .div(sourceToDestPrecision);
                }
            
                function _checkSwapSize(
                    address tokenAddress,
                    uint256 amount)
                    internal
                    view
                {
                    uint256 _maxSwapSize = maxSwapSize;
                    if (_maxSwapSize != 0) {
                        uint256 amountInEth;
                        if (tokenAddress == address(wethToken)) {
                            amountInEth = amount;
                        } else {
                            amountInEth = IPriceFeeds(priceFeeds).amountInEth(tokenAddress, amount);
                        }
                        require(amountInEth <= _maxSwapSize, "swap too large");
                    }
                }
            }
            
            contract LoanMaintenanceEvents {
            
                event DepositCollateral(
                    address indexed user,
                    address indexed depositToken,
                    bytes32 indexed loanId,
                    uint256 depositAmount
                );
            
                event WithdrawCollateral(
                    address indexed user,
                    address indexed withdrawToken,
                    bytes32 indexed loanId,
                    uint256 withdrawAmount
                );
            
                event ExtendLoanDuration(
                    address indexed user,
                    address indexed depositToken,
                    bytes32 indexed loanId,
                    uint256 depositAmount,
                    uint256 collateralUsedAmount,
                    uint256 newEndTimestamp
                );
            
                event ReduceLoanDuration(
                    address indexed user,
                    address indexed withdrawToken,
                    bytes32 indexed loanId,
                    uint256 withdrawAmount,
                    uint256 newEndTimestamp
                );
            
                event ClaimReward(
                    address indexed user,
                    address indexed receiver,
                    address indexed token,
                    uint256 amount
                );
            
                enum LoanType {
                    All,
                    Margin,
                    NonMargin
                }
            
                struct LoanReturnData {
                    bytes32 loanId;
                    uint96 endTimestamp;
                    address loanToken;
                    address collateralToken;
                    uint256 principal;
                    uint256 collateral;
                    uint256 interestOwedPerDay;
                    uint256 interestDepositRemaining;
                    uint256 startRate; // collateralToLoanRate
                    uint256 startMargin;
                    uint256 maintenanceMargin;
                    uint256 currentMargin;
                    uint256 maxLoanTerm;
                    uint256 maxLiquidatable;
                    uint256 maxSeizable;
                }
            }
            
            contract LoanMaintenance is State, LoanMaintenanceEvents, VaultController, InterestUser, SwapsUser, LiquidationHelper {
            
                function initialize(
                    address target)
                    external
                    onlyOwner
                {
                    _setTarget(this.depositCollateral.selector, target);
                    _setTarget(this.withdrawCollateral.selector, target);
                    _setTarget(this.withdrawAccruedInterest.selector, target);
                    _setTarget(this.extendLoanDuration.selector, target);
                    _setTarget(this.reduceLoanDuration.selector, target);
                    _setTarget(this.claimRewards.selector, target);
                    _setTarget(this.rewardsBalanceOf.selector, target);
                    _setTarget(this.getLenderInterestData.selector, target);
                    _setTarget(this.getLoanInterestData.selector, target);
                    _setTarget(this.getUserLoans.selector, target);
                    _setTarget(this.getUserLoansCount.selector, target);
                    _setTarget(this.getLoan.selector, target);
                    _setTarget(this.getActiveLoans.selector, target);
                    _setTarget(this.getActiveLoansCount.selector, target);
                }
            
                function depositCollateral(
                    bytes32 loanId,
                    uint256 depositAmount) // must match msg.value if ether is sent
                    external
                    payable
                    nonReentrant
                {
                    require(depositAmount != 0, "depositAmount is 0");
            
                    Loan storage loanLocal = loans[loanId];
                    require(loanLocal.active, "loan is closed");
            
                    LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
            
                    address collateralToken = loanParamsLocal.collateralToken;
                    uint256 collateral = loanLocal.collateral;
            
                    require(msg.value == 0 || collateralToken == address(wethToken), "wrong asset sent");
            
                    collateral = collateral
                        .add(depositAmount);
                    loanLocal.collateral = collateral;
            
                    if (msg.value == 0) {
                        vaultDeposit(
                            collateralToken,
                            msg.sender,
                            depositAmount
                        );
                    } else {
                        require(msg.value == depositAmount, "ether deposit mismatch");
                        vaultEtherDeposit(
                            msg.sender,
                            msg.value
                        );
                    }
            
                    emit DepositCollateral(
                        loanLocal.borrower,
                        collateralToken,
                        loanId,
                        depositAmount
                    );
                }
            
                function withdrawCollateral(
                    bytes32 loanId,
                    address receiver,
                    uint256 withdrawAmount)
                    external
                    nonReentrant
                    returns (uint256 actualWithdrawAmount)
                {
                    require(withdrawAmount != 0, "withdrawAmount is 0");
                    Loan storage loanLocal = loans[loanId];
                    LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
            
                    require(loanLocal.active, "loan is closed");
                    require(
                        msg.sender == loanLocal.borrower ||
                        delegatedManagers[loanLocal.id][msg.sender],
                        "unauthorized"
                    );
            
                    address collateralToken = loanParamsLocal.collateralToken;
                    uint256 collateral = loanLocal.collateral;
            
                    uint256 maxDrawdown = IPriceFeeds(priceFeeds).getMaxDrawdown(
                        loanParamsLocal.loanToken,
                        collateralToken,
                        loanLocal.principal,
                        collateral,
                        loanParamsLocal.maintenanceMargin
                    );
            
                    if (withdrawAmount > maxDrawdown) {
                        actualWithdrawAmount = maxDrawdown;
                    } else {
                        actualWithdrawAmount = withdrawAmount;
                    }
            
                    collateral = collateral
                        .sub(actualWithdrawAmount, "withdrawAmount too high");
                    loanLocal.collateral = collateral;
            
                    if (collateralToken == address(wethToken)) {
                        vaultEtherWithdraw(
                            receiver,
                            actualWithdrawAmount
                        );
                    } else {
                        vaultWithdraw(
                            collateralToken,
                            receiver,
                            actualWithdrawAmount
                        );
                    }
            
                    emit WithdrawCollateral(
                        loanLocal.borrower,
                        collateralToken,
                        loanId,
                        withdrawAmount
                    );
                }
            
                function withdrawAccruedInterest(
                    address loanToken)
                    external
                {
                    // pay outstanding interest to lender
                    _payInterest(
                        msg.sender, // lender
                        loanToken
                    );
                }
            
                function extendLoanDuration(
                    bytes32 loanId,
                    uint256 depositAmount,
                    bool useCollateral,
                    bytes calldata /*loanDataBytes*/) // for future use
                    external
                    payable
                    nonReentrant
                    returns (uint256 secondsExtended)
                {
                    require(depositAmount != 0, "depositAmount is 0");
                    Loan storage loanLocal = loans[loanId];
                    LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
            
                    require(loanLocal.active, "loan is closed");
                    require(
                        !useCollateral ||
                        msg.sender == loanLocal.borrower ||
                        delegatedManagers[loanLocal.id][msg.sender],
                        "unauthorized"
                    );
                    require(loanParamsLocal.maxLoanTerm == 0, "indefinite-term only");
                    require(msg.value == 0 || (!useCollateral && loanParamsLocal.loanToken == address(wethToken)), "wrong asset sent");
            
                    // pay outstanding interest to lender
                    _payInterest(
                        loanLocal.lender,
                        loanParamsLocal.loanToken
                    );
            
                    LoanInterest storage loanInterestLocal = loanInterest[loanLocal.id];
            
                    _settleFeeRewardForInterestExpense(
                        loanInterestLocal,
                        loanLocal.id,
                        loanParamsLocal.loanToken,
                        loanLocal.borrower,
                        block.timestamp
                    );
            
                    // Handle back interest: calculates interest owned since the loan endtime passed but the loan remained open
                    uint256 backInterestOwed;
                    if (block.timestamp > loanLocal.endTimestamp) {
                        backInterestOwed = block.timestamp
                            .sub(loanLocal.endTimestamp);
                        backInterestOwed = backInterestOwed
                            .mul(loanInterestLocal.owedPerDay);
                        backInterestOwed = backInterestOwed
                            .div(1 days);
            
                        require(depositAmount > backInterestOwed, "deposit cannot cover back interest");
                    }
            
                    // deposit interest
                    uint256 collateralUsed;
                    if (useCollateral) {
                        collateralUsed = _doSwapWithCollateral(
                            loanLocal,
                            loanParamsLocal,
                            depositAmount
                        );
                    } else {
                        if (msg.value == 0) {
                            vaultDeposit(
                                loanParamsLocal.loanToken,
                                msg.sender,
                                depositAmount
                            );
                        } else {
                            require(msg.value == depositAmount, "ether deposit mismatch");
                            vaultEtherDeposit(
                                msg.sender,
                                msg.value
                            );
                        }
                    }
            
                    if (backInterestOwed != 0) {
                        depositAmount = depositAmount
                            .sub(backInterestOwed);
            
                        // pay out backInterestOwed
                        _payInterestTransfer(
                            loanLocal.lender,
                            loanParamsLocal.loanToken,
                            backInterestOwed
                        );
                    }
            
                    secondsExtended = depositAmount
                        .mul(1 days)
                        .div(loanInterestLocal.owedPerDay);
            
                    loanLocal.endTimestamp = loanLocal.endTimestamp
                        .add(secondsExtended);
            
                    require(loanLocal.endTimestamp > block.timestamp &&
                           (loanLocal.endTimestamp - block.timestamp) > 1 hours,
                        "loan too short"
                    );
            
                    loanInterestLocal.depositTotal = loanInterestLocal.depositTotal
                        .add(depositAmount);
            
                    lenderInterest[loanLocal.lender][loanParamsLocal.loanToken].owedTotal = lenderInterest[loanLocal.lender][loanParamsLocal.loanToken].owedTotal
                        .add(depositAmount);
            
                    emit ExtendLoanDuration(
                        loanLocal.borrower,
                        loanParamsLocal.loanToken,
                        loanId,
                        depositAmount,
                        collateralUsed,
                        loanLocal.endTimestamp
                    );
                }
            
                function reduceLoanDuration(
                    bytes32 loanId,
                    address receiver,
                    uint256 withdrawAmount)
                    external
                    nonReentrant
                    returns (uint256 secondsReduced)
                {
                    require(withdrawAmount != 0, "withdrawAmount is 0");
                    Loan storage loanLocal = loans[loanId];
                    LoanParams storage loanParamsLocal = loanParams[loanLocal.loanParamsId];
            
                    require(loanLocal.active, "loan is closed");
                    require(
                        msg.sender == loanLocal.borrower ||
                        delegatedManagers[loanLocal.id][msg.sender],
                        "unauthorized"
                    );
                    require(loanParamsLocal.maxLoanTerm == 0, "indefinite-term only");
                    require(loanLocal.endTimestamp > block.timestamp, "loan term has ended");
            
                    // pay outstanding interest to lender
                    _payInterest(
                        loanLocal.lender,
                        loanParamsLocal.loanToken
                    );
            
                    LoanInterest storage loanInterestLocal = loanInterest[loanLocal.id];
            
                    _settleFeeRewardForInterestExpense(
                        loanInterestLocal,
                        loanLocal.id,
                        loanParamsLocal.loanToken,
                        loanLocal.borrower,
                        block.timestamp
                    );
            
                    uint256 interestDepositRemaining = loanLocal.endTimestamp
                        .sub(block.timestamp)
                        .mul(loanInterestLocal.owedPerDay)
                        .div(1 days);
                    require(withdrawAmount < interestDepositRemaining, "withdraw amount too high");
            
                    // withdraw interest
                    if (loanParamsLocal.loanToken == address(wethToken)) {
                        vaultEtherWithdraw(
                            receiver,
                            withdrawAmount
                        );
                    } else {
                        vaultWithdraw(
                            loanParamsLocal.loanToken,
                            receiver,
                            withdrawAmount
                        );
                    }
            
                    secondsReduced = withdrawAmount
                        .mul(1 days)
                        .div(loanInterestLocal.owedPerDay);
            
                    require (loanLocal.endTimestamp > secondsReduced, "loan too short");
            
                    loanLocal.endTimestamp = loanLocal.endTimestamp
                        .sub(secondsReduced);
            
                    require(loanLocal.endTimestamp > block.timestamp &&
                           (loanLocal.endTimestamp - block.timestamp) > 1 hours,
                        "loan too short"
                    );
            
                    loanInterestLocal.depositTotal = loanInterestLocal.depositTotal
                        .sub(withdrawAmount);
            
                    lenderInterest[loanLocal.lender][loanParamsLocal.loanToken].owedTotal = lenderInterest[loanLocal.lender][loanParamsLocal.loanToken].owedTotal
                        .sub(withdrawAmount);
            
                    emit ReduceLoanDuration(
                        loanLocal.borrower,
                        loanParamsLocal.loanToken,
                        loanId,
                        withdrawAmount,
                        loanLocal.endTimestamp
                    );
                }
            
                function claimRewards(
                    address receiver)
                    external
                    returns (uint256 claimAmount)
                {
                    bytes32 slot = keccak256(abi.encodePacked(msg.sender, UserRewardsID));
                    assembly {
                        claimAmount := sload(slot)
                    }
            
                    if (claimAmount != 0) {
                        assembly {
                            sstore(slot, 0)
                        }
            
                        protocolTokenPaid = protocolTokenPaid
                            .add(claimAmount);
            
                        IERC20(vbzrxTokenAddress).transfer(
                            receiver,
                            claimAmount
                        );
            
                        emit ClaimReward(
                            msg.sender,
                            receiver,
                            vbzrxTokenAddress,
                            claimAmount
                        );
                    }
                }
            
                function rewardsBalanceOf(
                    address user)
                    external
                    view
                    returns (uint256 rewardsBalance)
                {
                    bytes32 slot = keccak256(abi.encodePacked(user, UserRewardsID));
                    assembly {
                        rewardsBalance := sload(slot)
                    }
                }
            
                /// @dev Gets current lender interest data totals for all loans with a specific oracle and interest token
                /// @param lender The lender address
                /// @param loanToken The loan token address
                /// @return interestPaid The total amount of interest that has been paid to a lender so far
                /// @return interestPaidDate The date of the last interest pay out, or 0 if no interest has been withdrawn yet
                /// @return interestOwedPerDay The amount of interest the lender is earning per day
                /// @return interestUnPaid The total amount of interest the lender is owned and not yet withdrawn
                /// @return interestFeePercent The fee retained by the protocol before interest is paid to the lender
                /// @return principalTotal The total amount of outstading principal the lender has loaned
                function getLenderInterestData(
                    address lender,
                    address loanToken)
                    external
                    view
                    returns (
                        uint256 interestPaid,
                        uint256 interestPaidDate,
                        uint256 interestOwedPerDay,
                        uint256 interestUnPaid,
                        uint256 interestFeePercent,
                        uint256 principalTotal)
                {
                    LenderInterest memory lenderInterestLocal = lenderInterest[lender][loanToken];
            
                    interestUnPaid = block.timestamp.sub(lenderInterestLocal.updatedTimestamp).mul(lenderInterestLocal.owedPerDay).div(1 days);
                    if (interestUnPaid > lenderInterestLocal.owedTotal)
                        interestUnPaid = lenderInterestLocal.owedTotal;
            
                    return (
                        lenderInterestLocal.paidTotal,
                        lenderInterestLocal.paidTotal != 0 ? lenderInterestLocal.updatedTimestamp : 0,
                        lenderInterestLocal.owedPerDay,
                        lenderInterestLocal.updatedTimestamp != 0 ? interestUnPaid : 0,
                        lendingFeePercent,
                        lenderInterestLocal.principalTotal
                    );
                }
            
                /// @dev Gets current interest data for a loan
                /// @param loanId A unique id representing the loan
                /// @return loanToken The loan token that interest is paid in
                /// @return interestOwedPerDay The amount of interest the borrower is paying per day
                /// @return interestDepositTotal The total amount of interest the borrower has deposited
                /// @return interestDepositRemaining The amount of deposited interest that is not yet owed to a lender
                function getLoanInterestData(
                    bytes32 loanId)
                    external
                    view
                    returns (
                        address loanToken,
                        uint256 interestOwedPerDay,
                        uint256 interestDepositTotal,
                        uint256 interestDepositRemaining)
                {
                    loanToken = loanParams[loans[loanId].loanParamsId].loanToken;
                    interestOwedPerDay = loanInterest[loanId].owedPerDay;
                    interestDepositTotal = loanInterest[loanId].depositTotal;
            
                    uint256 endTimestamp = loans[loanId].endTimestamp;
                    uint256 interestTime = block.timestamp > endTimestamp ?
                        endTimestamp :
                        block.timestamp;
                    interestDepositRemaining = endTimestamp > interestTime ?
                        endTimestamp
                            .sub(interestTime)
                            .mul(interestOwedPerDay)
                            .div(1 days) :
                            0;
                }
            
                // Only returns data for loans that are active
                // All(0): all loans
                // Margin(1): margin trade loans
                // NonMargin(2): non-margin trade loans
                // only active loans are returned
                function getUserLoans(
                    address user,
                    uint256 start,
                    uint256 count,
                    LoanType loanType,
                    bool isLender,
                    bool unsafeOnly)
                    external
                    view
                    returns (LoanReturnData[] memory loansData)
                {
                    EnumerableBytes32Set.Bytes32Set storage set = isLender ?
                        lenderLoanSets[user] :
                        borrowerLoanSets[user];
            
                    uint256 end = start.add(count).min256(set.length());
                    if (start >= end) {
                        return loansData;
                    }
                    count = end-start;
            
                    uint256 idx = count;
                    LoanReturnData memory loanData;
                    loansData = new LoanReturnData[](idx);
                    for (uint256 i = --end; i >= start; i--) {
                        loanData = _getLoan(
                            set.get(i), // loanId
                            loanType,
                            unsafeOnly
                        );
                        if (loanData.loanId == 0) {
                            if (i == 0) {
                                break;
                            } else {
                                continue;
                            }
                        }
            
                        loansData[count-(idx--)] = loanData;
            
                        if (i == 0) {
                            break;
                        }
                    }
            
                    if (idx != 0) {
                        count -= idx;
                        assembly {
                            mstore(loansData, count)
                        }
                    }
                }
            
                function getUserLoansCount(
                    address user,
                    bool isLender)
                    external
                    view
                    returns (uint256)
                {
                    return isLender ?
                        lenderLoanSets[user].length() :
                        borrowerLoanSets[user].length();
                }
            
                function getLoan(
                    bytes32 loanId)
                    external
                    view
                    returns (LoanReturnData memory loanData)
                {
                    return _getLoan(
                        loanId,
                        LoanType.All,
                        false // unsafeOnly
                    );
                }
            
                function getActiveLoans(
                    uint256 start,
                    uint256 count,
                    bool unsafeOnly)
                    external
                    view
                    returns (LoanReturnData[] memory loansData)
                {
                    uint256 end = start.add(count).min256(activeLoansSet.length());
                    if (start >= end) {
                        return loansData;
                    }
                    count = end-start;
            
                    uint256 idx = count;
                    LoanReturnData memory loanData;
                    loansData = new LoanReturnData[](idx);
                    for (uint256 i = --end; i >= start; i--) {
                        loanData = _getLoan(
                            activeLoansSet.get(i), // loanId
                            LoanType.All,
                            unsafeOnly
                        );
                        if (loanData.loanId == 0) {
                            if (i == 0) {
                                break;
                            } else {
                                continue;
                            }
                        }
            
                        loansData[count-(idx--)] = loanData;
            
                        if (i == 0) {
                            break;
                        }
                    }
            
                    if (idx != 0) {
                        count -= idx;
                        assembly {
                            mstore(loansData, count)
                        }
                    }
                }
            
                function getActiveLoansCount()
                    external
                    view
                    returns (uint256)
                {
                    return activeLoansSet.length();
                }
            
                function _getLoan(
                    bytes32 loanId,
                    LoanType loanType,
                    bool unsafeOnly)
                    internal
                    view
                    returns (LoanReturnData memory loanData)
                {
                    Loan memory loanLocal = loans[loanId];
                    LoanParams memory loanParamsLocal = loanParams[loanLocal.loanParamsId];
            
                    if ((loanType == LoanType.Margin && loanParamsLocal.maxLoanTerm == 0) ||
                        (loanType == LoanType.NonMargin && loanParamsLocal.maxLoanTerm != 0)) {
                        return loanData;
                    }
            
                    LoanInterest memory loanInterestLocal = loanInterest[loanId];
            
                    (uint256 currentMargin, uint256 collateralToLoanRate) = IPriceFeeds(priceFeeds).getCurrentMargin(
                        loanParamsLocal.loanToken,
                        loanParamsLocal.collateralToken,
                        loanLocal.principal,
                        loanLocal.collateral
                    );
            
                    uint256 maxLiquidatable;
                    uint256 maxSeizable;
                    if (currentMargin <= loanParamsLocal.maintenanceMargin) {
                        (maxLiquidatable, maxSeizable) = _getLiquidationAmounts(
                            loanLocal.principal,
                            loanLocal.collateral,
                            currentMargin,
                            loanParamsLocal.maintenanceMargin,
                            collateralToLoanRate,
                            liquidationIncentivePercent[loanParamsLocal.loanToken][loanParamsLocal.collateralToken]
                        );
                    } else if (unsafeOnly) {
                        return loanData;
                    }
            
                    return LoanReturnData({
                        loanId: loanId,
                        endTimestamp: uint96(loanLocal.endTimestamp),
                        loanToken: loanParamsLocal.loanToken,
                        collateralToken: loanParamsLocal.collateralToken,
                        principal: loanLocal.principal,
                        collateral: loanLocal.collateral,
                        interestOwedPerDay: loanInterestLocal.owedPerDay,
                        interestDepositRemaining: loanLocal.endTimestamp >= block.timestamp ? loanLocal.endTimestamp.sub(block.timestamp).mul(loanInterestLocal.owedPerDay).div(1 days) : 0,
                        startRate: loanLocal.startRate,
                        startMargin: loanLocal.startMargin,
                        maintenanceMargin: loanParamsLocal.maintenanceMargin,
                        currentMargin: currentMargin,
                        maxLoanTerm: loanParamsLocal.maxLoanTerm,
                        maxLiquidatable: maxLiquidatable,
                        maxSeizable: maxSeizable
                    });
                }
            
                function _doSwapWithCollateral(
                    Loan storage loanLocal,
                    LoanParams memory loanParamsLocal,
                    uint256 depositAmount)
                    internal
                    returns (uint256)
                {
                    // reverts in _loanSwap if amountNeeded can't be bought
                    (,uint256 sourceTokenAmountUsed,) = _loanSwap(
                        loanLocal.id,
                        loanParamsLocal.collateralToken,
                        loanParamsLocal.loanToken,
                        loanLocal.borrower,
                        loanLocal.collateral, // minSourceTokenAmount
                        0, // maxSourceTokenAmount (0 means minSourceTokenAmount)
                        depositAmount, // requiredDestTokenAmount (partial spend of loanLocal.collateral to fill this amount)
                        true, // bypassFee
                        "" // loanDataBytes
                    );
                    loanLocal.collateral = loanLocal.collateral
                        .sub(sourceTokenAmountUsed);
            
                    // ensure the loan is still healthy
                    (uint256 currentMargin,) = IPriceFeeds(priceFeeds).getCurrentMargin(
                        loanParamsLocal.loanToken,
                        loanParamsLocal.collateralToken,
                        loanLocal.principal,
                        loanLocal.collateral
                    );
                    require(
                        currentMargin > loanParamsLocal.maintenanceMargin,
                        "unhealthy position"
                    );
            
                    return sourceTokenAmountUsed;
                }
            }

            File 5 of 5: LoanSettings
            /**
             * Copyright 2017-2020, bZeroX, LLC <https://bzx.network/>. All Rights Reserved.
             * Licensed under the Apache License, Version 2.0.
             */
            
            pragma solidity 0.5.17;
            pragma experimental ABIEncoderV2;
            
            
            interface IWeth {
                function deposit() external payable;
                function withdraw(uint256 wad) external;
            }
            
            contract IERC20 {
                string public name;
                uint8 public decimals;
                string public symbol;
                function totalSupply() public view returns (uint256);
                function balanceOf(address _who) public view returns (uint256);
                function allowance(address _owner, address _spender) public view returns (uint256);
                function approve(address _spender, uint256 _value) public returns (bool);
                function transfer(address _to, uint256 _value) public returns (bool);
                function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
                event Transfer(address indexed from, address indexed to, uint256 value);
                event Approval(address indexed owner, address indexed spender, uint256 value);
            }
            
            contract IWethERC20 is IWeth, IERC20 {}
            
            contract Constants {
            
                uint256 internal constant WEI_PRECISION = 10**18;
                uint256 internal constant WEI_PERCENT_PRECISION = 10**20;
            
                uint256 internal constant DAYS_IN_A_YEAR = 365;
                uint256 internal constant ONE_MONTH = 2628000; // approx. seconds in a month
            
                IWethERC20 public constant wethToken = IWethERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
                address public constant bzrxTokenAddress = 0x56d811088235F11C8920698a204A5010a788f4b3;
                address public constant vbzrxTokenAddress = 0xB72B31907C1C95F3650b64b2469e08EdACeE5e8F;
            }
            
            /**
             * @dev Library for managing loan sets
             *
             * Sets have the following properties:
             *
             * - Elements are added, removed, and checked for existence in constant time
             * (O(1)).
             * - Elements are enumerated in O(n). No guarantees are made on the ordering.
             *
             * Include with `using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;`.
             *
             */
            library EnumerableBytes32Set {
            
                struct Bytes32Set {
                    // Position of the value in the `values` array, plus 1 because index 0
                    // means a value is not in the set.
                    mapping (bytes32 => uint256) index;
                    bytes32[] values;
                }
            
                /**
                 * @dev Add an address value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return addBytes32(set, value);
                }
            
                /**
                 * @dev Add a value to a set. O(1).
                 * Returns false if the value was already in the set.
                 */
                function addBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (!contains(set, value)){
                        set.index[value] = set.values.push(value);
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Removes an address value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return removeBytes32(set, value);
                }
            
                /**
                 * @dev Removes a value from a set. O(1).
                 * Returns false if the value was not present in the set.
                 */
                function removeBytes32(Bytes32Set storage set, bytes32 value)
                    internal
                    returns (bool)
                {
                    if (contains(set, value)){
                        uint256 toDeleteIndex = set.index[value] - 1;
                        uint256 lastIndex = set.values.length - 1;
            
                        // If the element we're deleting is the last one, we can just remove it without doing a swap
                        if (lastIndex != toDeleteIndex) {
                            bytes32 lastValue = set.values[lastIndex];
            
                            // Move the last value to the index where the deleted value is
                            set.values[toDeleteIndex] = lastValue;
                            // Update the index for the moved value
                            set.index[lastValue] = toDeleteIndex + 1; // All indexes are 1-based
                        }
            
                        // Delete the index entry for the deleted value
                        delete set.index[value];
            
                        // Delete the old entry for the moved value
                        set.values.pop();
            
                        return true;
                    } else {
                        return false;
                    }
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function contains(Bytes32Set storage set, bytes32 value)
                    internal
                    view
                    returns (bool)
                {
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns true if the value is in the set. O(1).
                 */
                function containsAddress(Bytes32Set storage set, address addrvalue)
                    internal
                    view
                    returns (bool)
                {
                    bytes32 value;
                    assembly {
                        value := addrvalue
                    }
                    return set.index[value] != 0;
                }
            
                /**
                 * @dev Returns an array with all values in the set. O(N).
                 * Note that there are no guarantees on the ordering of values inside the
                 * array, and it may change when more values are added or removed.
            
                 * WARNING: This function may run out of gas on large sets: use {length} and
                 * {get} instead in these cases.
                 */
                function enumerate(Bytes32Set storage set, uint256 start, uint256 count)
                    internal
                    view
                    returns (bytes32[] memory output)
                {
                    uint256 end = start + count;
                    require(end >= start, "addition overflow");
                    end = set.values.length < end ? set.values.length : end;
                    if (end == 0 || start >= end) {
                        return output;
                    }
            
                    output = new bytes32[](end-start);
                    for (uint256 i = start; i < end; i++) {
                        output[i-start] = set.values[i];
                    }
                    return output;
                }
            
                /**
                 * @dev Returns the number of elements on the set. O(1).
                 */
                function length(Bytes32Set storage set)
                    internal
                    view
                    returns (uint256)
                {
                    return set.values.length;
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function get(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (bytes32)
                {
                    return set.values[index];
                }
            
               /** @dev Returns the element stored at position `index` in the set. O(1).
                * Note that there are no guarantees on the ordering of values inside the
                * array, and it may change when more values are added or removed.
                *
                * Requirements:
                *
                * - `index` must be strictly less than {length}.
                */
                function getAddress(Bytes32Set storage set, uint256 index)
                    internal
                    view
                    returns (address)
                {
                    bytes32 value = set.values[index];
                    address addrvalue;
                    assembly {
                        addrvalue := value
                    }
                    return addrvalue;
                }
            }
            
            /**
             * @title Helps contracts guard against reentrancy attacks.
             * @author Remco Bloemen <remco@2π.com>, Eenae <alexey@mixbytes.io>
             * @dev If you mark a function `nonReentrant`, you should also
             * mark it `external`.
             */
            contract ReentrancyGuard {
            
                /// @dev Constant for unlocked guard state - non-zero to prevent extra gas costs.
                /// See: https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1056
                uint256 internal constant REENTRANCY_GUARD_FREE = 1;
            
                /// @dev Constant for locked guard state
                uint256 internal constant REENTRANCY_GUARD_LOCKED = 2;
            
                /**
                * @dev We use a single lock for the whole contract.
                */
                uint256 internal reentrancyLock = REENTRANCY_GUARD_FREE;
            
                /**
                * @dev Prevents a contract from calling itself, directly or indirectly.
                * If you mark a function `nonReentrant`, you should also
                * mark it `external`. Calling one `nonReentrant` function from
                * another is not supported. Instead, you can implement a
                * `private` function doing the actual work, and an `external`
                * wrapper marked as `nonReentrant`.
                */
                modifier nonReentrant() {
                    require(reentrancyLock == REENTRANCY_GUARD_FREE, "nonReentrant");
                    reentrancyLock = REENTRANCY_GUARD_LOCKED;
                    _;
                    reentrancyLock = REENTRANCY_GUARD_FREE;
                }
            }
            
            /*
             * @dev Provides information about the current execution context, including the
             * sender of the transaction and its data. While these are generally available
             * via msg.sender and msg.data, they should not be accessed in such a direct
             * manner, since when dealing with GSN meta-transactions the account sending and
             * paying for execution may not be the actual sender (as far as an application
             * is concerned).
             *
             * This contract is only required for intermediate, library-like contracts.
             */
            contract Context {
                // Empty internal constructor, to prevent people from mistakenly deploying
                // an instance of this contract, which should be used via inheritance.
                constructor () internal { }
                // solhint-disable-previous-line no-empty-blocks
            
                function _msgSender() internal view returns (address payable) {
                    return msg.sender;
                }
            
                function _msgData() internal view returns (bytes memory) {
                    this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                    return msg.data;
                }
            }
            
            /**
             * @dev Contract module which provides a basic access control mechanism, where
             * there is an account (an owner) that can be granted exclusive access to
             * specific functions.
             *
             * This module is used through inheritance. It will make available the modifier
             * `onlyOwner`, which can be applied to your functions to restrict their use to
             * the owner.
             */
            contract Ownable is Context {
                address private _owner;
            
                event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            
                /**
                 * @dev Initializes the contract setting the deployer as the initial owner.
                 */
                constructor () internal {
                    address msgSender = _msgSender();
                    _owner = msgSender;
                    emit OwnershipTransferred(address(0), msgSender);
                }
            
                /**
                 * @dev Returns the address of the current owner.
                 */
                function owner() public view returns (address) {
                    return _owner;
                }
            
                /**
                 * @dev Throws if called by any account other than the owner.
                 */
                modifier onlyOwner() {
                    require(isOwner(), "unauthorized");
                    _;
                }
            
                /**
                 * @dev Returns true if the caller is the current owner.
                 */
                function isOwner() public view returns (bool) {
                    return _msgSender() == _owner;
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 * Can only be called by the current owner.
                 */
                function transferOwnership(address newOwner) public onlyOwner {
                    _transferOwnership(newOwner);
                }
            
                /**
                 * @dev Transfers ownership of the contract to a new account (`newOwner`).
                 */
                function _transferOwnership(address newOwner) internal {
                    require(newOwner != address(0), "Ownable: new owner is the zero address");
                    emit OwnershipTransferred(_owner, newOwner);
                    _owner = newOwner;
                }
            }
            
            /**
             * @dev Wrappers over Solidity's arithmetic operations with added overflow
             * checks.
             *
             * Arithmetic operations in Solidity wrap on overflow. This can easily result
             * in bugs, because programmers usually assume that an overflow raises an
             * error, which is the standard behavior in high level programming languages.
             * `SafeMath` restores this intuition by reverting the transaction when an
             * operation overflows.
             *
             * Using this library instead of the unchecked operations eliminates an entire
             * class of bugs, so it's recommended to use it always.
             */
            library SafeMath {
                /**
                 * @dev Returns the addition of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `+` operator.
                 *
                 * Requirements:
                 * - Addition cannot overflow.
                 */
                function add(uint256 a, uint256 b) internal pure returns (uint256) {
                    uint256 c = a + b;
                    require(c >= a, "SafeMath: addition overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 */
                function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                    return sub(a, b, "SafeMath: subtraction overflow");
                }
            
                /**
                 * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                 * overflow (when the result is negative).
                 *
                 * Counterpart to Solidity's `-` operator.
                 *
                 * Requirements:
                 * - Subtraction cannot overflow.
                 *
                 * _Available since v2.4.0._
                 */
                function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b <= a, errorMessage);
                    uint256 c = a - b;
            
                    return c;
                }
            
                /**
                 * @dev Returns the multiplication of two unsigned integers, reverting on
                 * overflow.
                 *
                 * Counterpart to Solidity's `*` operator.
                 *
                 * Requirements:
                 * - Multiplication cannot overflow.
                 */
                function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) {
                        return 0;
                    }
            
                    uint256 c = a * b;
                    require(c / a == b, "SafeMath: multiplication overflow");
            
                    return c;
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function div(uint256 a, uint256 b) internal pure returns (uint256) {
                    return div(a, b, "SafeMath: division by zero");
                }
            
                /**
                 * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                 * division by zero. The result is rounded towards zero.
                 *
                 * Counterpart to Solidity's `/` operator. Note: this function uses a
                 * `revert` opcode (which leaves remaining gas untouched) while Solidity
                 * uses an invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
                    uint256 c = a / b;
                    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
            
                    return c;
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
                    return divCeil(a, b, "SafeMath: division by zero");
                }
            
                /**
                * @dev Integer division of two numbers, rounding up and truncating the quotient
                */
                function divCeil(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    // Solidity only automatically asserts when dividing by 0
                    require(b != 0, errorMessage);
            
                    if (a == 0) {
                        return 0;
                    }
                    uint256 c = ((a - 1) / b) + 1;
            
                    return c;
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 */
                function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                    return mod(a, b, "SafeMath: modulo by zero");
                }
            
                /**
                 * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                 * Reverts with custom message when dividing by zero.
                 *
                 * Counterpart to Solidity's `%` operator. This function uses a `revert`
                 * opcode (which leaves remaining gas untouched) while Solidity uses an
                 * invalid opcode to revert (consuming all remaining gas).
                 *
                 * Requirements:
                 * - The divisor cannot be zero.
                 *
                 * _Available since v2.4.0._
                 */
                function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                    require(b != 0, errorMessage);
                    return a % b;
                }
            
                function min256(uint256 _a, uint256 _b) internal pure returns (uint256) {
                    return _a < _b ? _a : _b;
                }
            }
            
            contract LoanStruct {
                struct Loan {
                    bytes32 id;                 // id of the loan
                    bytes32 loanParamsId;       // the linked loan params id
                    bytes32 pendingTradesId;    // the linked pending trades id
                    uint256 principal;          // total borrowed amount outstanding
                    uint256 collateral;         // total collateral escrowed for the loan
                    uint256 startTimestamp;     // loan start time
                    uint256 endTimestamp;       // for active loans, this is the expected loan end time, for in-active loans, is the actual (past) end time
                    uint256 startMargin;        // initial margin when the loan opened
                    uint256 startRate;          // reference rate when the loan opened for converting collateralToken to loanToken
                    address borrower;           // borrower of this loan
                    address lender;             // lender of this loan
                    bool active;                // if false, the loan has been fully closed
                }
            }
            
            contract LoanParamsStruct {
                struct LoanParams {
                    bytes32 id;                 // id of loan params object
                    bool active;                // if false, this object has been disabled by the owner and can't be used for future loans
                    address owner;              // owner of this object
                    address loanToken;          // the token being loaned
                    address collateralToken;    // the required collateral token
                    uint256 minInitialMargin;   // the minimum allowed initial margin
                    uint256 maintenanceMargin;  // an unhealthy loan when current margin is at or below this value
                    uint256 maxLoanTerm;        // the maximum term for new loans (0 means there's no max term)
                }
            }
            
            contract OrderStruct {
                struct Order {
                    uint256 lockedAmount;           // escrowed amount waiting for a counterparty
                    uint256 interestRate;           // interest rate defined by the creator of this order
                    uint256 minLoanTerm;            // minimum loan term allowed
                    uint256 maxLoanTerm;            // maximum loan term allowed
                    uint256 createdTimestamp;       // timestamp when this order was created
                    uint256 expirationTimestamp;    // timestamp when this order expires
                }
            }
            
            contract LenderInterestStruct {
                struct LenderInterest {
                    uint256 principalTotal;     // total borrowed amount outstanding of asset
                    uint256 owedPerDay;         // interest owed per day for all loans of asset
                    uint256 owedTotal;          // total interest owed for all loans of asset (assuming they go to full term)
                    uint256 paidTotal;          // total interest paid so far for asset
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract LoanInterestStruct {
                struct LoanInterest {
                    uint256 owedPerDay;         // interest owed per day for loan
                    uint256 depositTotal;       // total escrowed interest for loan
                    uint256 updatedTimestamp;   // last update
                }
            }
            
            contract Objects is
                LoanStruct,
                LoanParamsStruct,
                OrderStruct,
                LenderInterestStruct,
                LoanInterestStruct
            {}
            
            contract State is Constants, Objects, ReentrancyGuard, Ownable {
                using SafeMath for uint256;
                using EnumerableBytes32Set for EnumerableBytes32Set.Bytes32Set;
            
                address public priceFeeds;                                                          // handles asset reference price lookups
                address public swapsImpl;                                                           // handles asset swaps using dex liquidity
            
                mapping (bytes4 => address) public logicTargets;                                    // implementations of protocol functions
            
                mapping (bytes32 => Loan) public loans;                                             // loanId => Loan
                mapping (bytes32 => LoanParams) public loanParams;                                  // loanParamsId => LoanParams
            
                mapping (address => mapping (bytes32 => Order)) public lenderOrders;                // lender => orderParamsId => Order
                mapping (address => mapping (bytes32 => Order)) public borrowerOrders;              // borrower => orderParamsId => Order
            
                mapping (bytes32 => mapping (address => bool)) public delegatedManagers;            // loanId => delegated => approved
            
                // Interest
                mapping (address => mapping (address => LenderInterest)) public lenderInterest;     // lender => loanToken => LenderInterest object
                mapping (bytes32 => LoanInterest) public loanInterest;                              // loanId => LoanInterest object
            
                // Internals
                EnumerableBytes32Set.Bytes32Set internal logicTargetsSet;                           // implementations set
                EnumerableBytes32Set.Bytes32Set internal activeLoansSet;                            // active loans set
            
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal lenderLoanSets;       // lender loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal borrowerLoanSets;     // borrow loans set
                mapping (address => EnumerableBytes32Set.Bytes32Set) internal userLoanParamSets;    // user loan params set
            
                address public feesController;                                                      // address controlling fee withdrawals
            
                uint256 public lendingFeePercent = 10 ether; // 10% fee                             // fee taken from lender interest payments
                mapping (address => uint256) public lendingFeeTokensHeld;                           // total interest fees received and not withdrawn per asset
                mapping (address => uint256) public lendingFeeTokensPaid;                           // total interest fees withdraw per asset (lifetime fees = lendingFeeTokensHeld + lendingFeeTokensPaid)
            
                uint256 public tradingFeePercent = 0.15 ether; // 0.15% fee                         // fee paid for each trade
                mapping (address => uint256) public tradingFeeTokensHeld;                           // total trading fees received and not withdrawn per asset
                mapping (address => uint256) public tradingFeeTokensPaid;                           // total trading fees withdraw per asset (lifetime fees = tradingFeeTokensHeld + tradingFeeTokensPaid)
            
                uint256 public borrowingFeePercent = 0.09 ether; // 0.09% fee                       // origination fee paid for each loan
                mapping (address => uint256) public borrowingFeeTokensHeld;                         // total borrowing fees received and not withdrawn per asset
                mapping (address => uint256) public borrowingFeeTokensPaid;                         // total borrowing fees withdraw per asset (lifetime fees = borrowingFeeTokensHeld + borrowingFeeTokensPaid)
            
                uint256 public protocolTokenHeld;                                                   // current protocol token deposit balance
                uint256 public protocolTokenPaid;                                                   // lifetime total payout of protocol token
            
                uint256 public affiliateFeePercent = 30 ether; // 30% fee share                     // fee share for affiliate program
            
                mapping (address => uint256) public liquidationIncentivePercent;                    // percent discount on collateral for liquidators per collateral asset
            
                mapping (address => address) public loanPoolToUnderlying;                           // loanPool => underlying
                mapping (address => address) public underlyingToLoanPool;                           // underlying => loanPool
                EnumerableBytes32Set.Bytes32Set internal loanPoolsSet;                              // loan pools set
            
                mapping (address => bool) public supportedTokens;                                   // supported tokens for swaps
            
                uint256 public maxDisagreement = 5 ether;                                           // % disagreement between swap rate and reference rate
            
                uint256 public sourceBufferPercent = 5 ether;                                       // used to estimate kyber swap source amount
            
                uint256 public maxSwapSize = 1500 ether;                                            // maximum supported swap size in ETH
            
            
                function _setTarget(
                    bytes4 sig,
                    address target)
                    internal
                {
                    logicTargets[sig] = target;
            
                    if (target != address(0)) {
                        logicTargetsSet.addBytes32(bytes32(sig));
                    } else {
                        logicTargetsSet.removeBytes32(bytes32(sig));
                    }
                }
            }
            
            contract LoanSettingsEvents {
            
                event LoanParamsSetup(
                    bytes32 indexed id,
                    address owner,
                    address indexed loanToken,
                    address indexed collateralToken,
                    uint256 minInitialMargin,
                    uint256 maintenanceMargin,
                    uint256 maxLoanTerm
                );
                event LoanParamsIdSetup(
                    bytes32 indexed id,
                    address indexed owner
                );
            
                event LoanParamsDisabled(
                    bytes32 indexed id,
                    address owner,
                    address indexed loanToken,
                    address indexed collateralToken,
                    uint256 minInitialMargin,
                    uint256 maintenanceMargin,
                    uint256 maxLoanTerm
                );
                event LoanParamsIdDisabled(
                    bytes32 indexed id,
                    address indexed owner
                );
            }
            
            contract LoanSettings is State, LoanSettingsEvents {
            
                function initialize(
                    address target)
                    external
                    onlyOwner
                {
                    _setTarget(this.setupLoanParams.selector, target);
                    _setTarget(this.disableLoanParams.selector, target);
                    _setTarget(this.getLoanParams.selector, target);
                    _setTarget(this.getLoanParamsList.selector, target);
                    _setTarget(this.getTotalPrincipal.selector, target);
                }
            
                function setupLoanParams(
                    LoanParams[] calldata loanParamsList)
                    external
                    returns (bytes32[] memory loanParamsIdList)
                {
                    loanParamsIdList = new bytes32[](loanParamsList.length);
                    for (uint256 i = 0; i < loanParamsList.length; i++) {
                        loanParamsIdList[i] = _setupLoanParams(loanParamsList[i]);
                    }
                }
            
                // Deactivates LoanParams for future loans. Active loans using it are unaffected.
                function disableLoanParams(
                    bytes32[] calldata loanParamsIdList)
                    external
                {
                    for (uint256 i = 0; i < loanParamsIdList.length; i++) {
                        require(msg.sender == loanParams[loanParamsIdList[i]].owner, "unauthorized owner");
                        loanParams[loanParamsIdList[i]].active = false;
            
                        LoanParams memory loanParamsLocal = loanParams[loanParamsIdList[i]];
                        emit LoanParamsDisabled(
                            loanParamsLocal.id,
                            loanParamsLocal.owner,
                            loanParamsLocal.loanToken,
                            loanParamsLocal.collateralToken,
                            loanParamsLocal.minInitialMargin,
                            loanParamsLocal.maintenanceMargin,
                            loanParamsLocal.maxLoanTerm
                        );
                        emit LoanParamsIdDisabled(
                            loanParamsLocal.id,
                            loanParamsLocal.owner
                        );
                    }
                }
            
                function getLoanParams(
                    bytes32[] memory loanParamsIdList)
                    public
                    view
                    returns (LoanParams[] memory loanParamsList)
                {
                    loanParamsList = new LoanParams[](loanParamsIdList.length);
                    uint256 itemCount;
            
                    for (uint256 i = 0; i < loanParamsIdList.length; i++) {
                        LoanParams memory loanParamsLocal = loanParams[loanParamsIdList[i]];
                        if (loanParamsLocal.id == 0) {
                            continue;
                        }
                        loanParamsList[itemCount] = loanParamsLocal;
                        itemCount++;
                    }
            
                    if (itemCount < loanParamsList.length) {
                        assembly {
                            mstore(loanParamsList, itemCount)
                        }
                    }
                }
            
                function getLoanParamsList(
                    address owner,
                    uint256 start,
                    uint256 count)
                    external
                    view
                    returns (bytes32[] memory loanParamsList)
                {
                    EnumerableBytes32Set.Bytes32Set storage set = userLoanParamSets[owner];
                    uint256 end = start.add(count).min256(set.length());
                    if (start >= end) {
                        return loanParamsList;
                    }
                    count = end-start;
            
                    loanParamsList = new bytes32[](count);
                    for (uint256 i = --end; i >= start; i--) {
                        loanParamsList[--count] = set.get(i);
            
                        if (i == 0) {
                            break;
                        }
                    }
                }
            
                function getTotalPrincipal(
                    address lender,
                    address loanToken)
                    external
                    view
                    returns (uint256)
                {
                    return lenderInterest[lender][loanToken].principalTotal;
                }
            
                function _setupLoanParams(
                    LoanParams memory loanParamsLocal)
                    internal
                    returns (bytes32)
                {
                    bytes32 loanParamsId = keccak256(abi.encode(
                        loanParamsLocal.loanToken,
                        loanParamsLocal.collateralToken,
                        loanParamsLocal.minInitialMargin,
                        loanParamsLocal.maintenanceMargin,
                        loanParamsLocal.maxLoanTerm,
                        block.timestamp
                    ));
                    require(loanParams[loanParamsId].id == 0, "loanParams exists");
            
                    require(loanParamsLocal.loanToken != address(0) &&
                        loanParamsLocal.collateralToken != address(0) &&
                        loanParamsLocal.minInitialMargin > loanParamsLocal.maintenanceMargin &&
                        (loanParamsLocal.maxLoanTerm == 0 || loanParamsLocal.maxLoanTerm > 1 hours), // a defined maxLoanTerm has to be greater than one hour
                        "invalid params"
                    );
            
                    loanParamsLocal.id = loanParamsId;
                    loanParamsLocal.active = true;
                    loanParamsLocal.owner = msg.sender;
            
                    loanParams[loanParamsId] = loanParamsLocal;
                    userLoanParamSets[msg.sender].addBytes32(loanParamsId);
            
                    emit LoanParamsSetup(
                        loanParamsId,
                        loanParamsLocal.owner,
                        loanParamsLocal.loanToken,
                        loanParamsLocal.collateralToken,
                        loanParamsLocal.minInitialMargin,
                        loanParamsLocal.maintenanceMargin,
                        loanParamsLocal.maxLoanTerm
                    );
                    emit LoanParamsIdSetup(
                        loanParamsId,
                        loanParamsLocal.owner
                    );
            
                    return loanParamsId;
                }
            }