// File: contracts/FeeController.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "@openzeppelin/contracts/access/Ownable.sol";

import "./interfaces/IFeeController.sol";
import "./libraries/FeeLookups.sol";

import { FC_LendingFeeOverMax, FC_VaultMintFeeOverMax } from "./errors/Lending.sol";

/**
 * @title FeeController
 * @author Non-Fungible Technologies, Inc.
 *
 * The Fee Controller is used by other lending protocol contracts to query for fees
 * for different protocol operations (loan origination, rollovers, etc). All fees should
 * have setters and getters. In the future, this contract could be extended to
 * support more complex logic (introducing a mapping of users who get a discount, e.g.).
 * Since LoanCore can change the fee controller reference, any changes to this contract
 * can be newly deployed on-chain and adopted.
 */
contract FeeController is IFeeController, FeeLookups, Ownable {
    // ============================================ STATE ==============================================

    /// @dev Fee for minting a vault.
    uint64 public vaultMintFee;

    /// @dev Max fee for minting a vault.
    uint64 public immutable maxVaultMintFee;

    /// @dev Fee mapping for lending protocol operations.
    /// @dev Important: these fees are expressed in basis points. It's required that
    ///                 the consumer of this controller handle accounting properly based
    ///                 on their own knowledge of the fee type.
    mapping(bytes32 => uint16) public loanFees;

    /// @dev Max fees for lending protocol operations.
    /// @dev Functionally immutable, can only be set on deployment. Can specify a maximum fee
    ///      for any id.
    mapping(bytes32 => uint16) public maxLoanFees;

    // ========================================= CONSTRUCTOR ===========================================

    /**
     * @notice Deploy the contract, and set the required max fees to support the lending protocol.
     */
    constructor() {
        /// @dev Vault mint fee - gross
        maxVaultMintFee = 1 ether;

        /// @dev Origination fees - bps
        maxLoanFees[FL_01] = 10_00;
        maxLoanFees[FL_02] = 10_00;

        /// @dev Rollover fees - bps
        maxLoanFees[FL_03] = 20_00;
        maxLoanFees[FL_04] = 20_00;

        /// @dev Loan closure fees - bps
        maxLoanFees[FL_05] = 10_00;
        maxLoanFees[FL_06] = 50_00;
        maxLoanFees[FL_07] = 10_00;
        maxLoanFees[FL_08] = 10_00;
    }

    // ======================================== GETTER/SETTER ==========================================

    /**
     * @notice Set the protocol fee for a given operation to the given value.
     *         The caller must be the owner of the contract.
     *
     * @param id                            The bytes32 identifier for the fee.
     * @param fee                           The fee to set.
     */
    function setLendingFee(bytes32 id, uint16 fee) public override onlyOwner {
        if (maxLoanFees[id] != 0 && fee > maxLoanFees[id]) {
            revert FC_LendingFeeOverMax(id, fee, maxLoanFees[id]);
        }

        loanFees[id] = fee;

        emit SetLendingFee(id, fee);
    }

    /**
     * @notice Set the vault mint fee. The caller must be the owner of the contract.
     *
     * @param fee                           The fee to set.
     */
    function setVaultMintFee(uint64 fee) public override onlyOwner {
        if (maxVaultMintFee != 0 && fee > maxVaultMintFee) {
            revert FC_VaultMintFeeOverMax(fee, maxVaultMintFee);
        }

        vaultMintFee = fee;

        emit SetVaultMintFee(fee);
    }

    /**
     * @notice Get the fee for the given lending protocol fee id.
     *
     * @param id                      The bytes32 id for the fee.
     *
     * @return fee                    The fee for the given id.
     */
    function getLendingFee(bytes32 id) external view override returns (uint16) {
        return loanFees[id];
    }

    /**
     * @notice Get the vault mint fee.
     *
     * @return vaultMintFee            The fee for minting a vault.
     */
    function getVaultMintFee() external view override returns (uint64) {
        return vaultMintFee;
    }

    /**
     * @notice Get the fees for loan origination. Fees are returned in a struct to be used
     *         upon loan origination.
     *
     * @return FeesOrigination              Applicable fees for loan origination.
     */
    function getFeesOrigination() external view override returns (FeesOrigination memory) {
        return FeesOrigination({
            borrowerOriginationFee: loanFees[FL_01],
            lenderOriginationFee: loanFees[FL_02],
            lenderDefaultFee: loanFees[FL_05],
            lenderInterestFee: loanFees[FL_06],
            lenderPrincipalFee: loanFees[FL_07]
        });
    }

    /**
     * @notice Get the fees for loan rollover. Fees are returned in a struct to be used
     *         when a rollover occurs in the repayment controller.
     *
     * @return FeesRollover              Applicable fees for a loan rollover.
     */
    function getFeesRollover() external view override returns (FeesRollover memory) {
        return FeesRollover({
            borrowerRolloverFee: loanFees[FL_03],
            lenderRolloverFee: loanFees[FL_04]
        });
    }

    /**
     * @notice Get the max lending fee for the given id. Unset max fees return 0.
     *
     * @param id                      The bytes32 id for the fee.
     *
     * @return maxLoanFee             The maximum fee for the given id.
     */
    function getMaxLendingFee(bytes32 id) external view override returns (uint16) {
        return maxLoanFees[id];
    }

    /**
     * @notice Get the max vault mint fee.
     *
     * @return maxVaultMintFee        The maximum fee for minting a vault.
     */
    function getMaxVaultMintFee() external view override returns (uint64) {
        return maxVaultMintFee;
    }
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract 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() {
        _setOwner(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: contracts/interfaces/IFeeController.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

interface IFeeController {
    // ================ Structs ================

    struct FeesOrigination {
        uint16 borrowerOriginationFee;
        uint16 lenderOriginationFee;
        uint16 lenderDefaultFee;
        uint16 lenderInterestFee;
        uint16 lenderPrincipalFee;
    }

    struct FeesRollover {
        uint16 borrowerRolloverFee;
        uint16 lenderRolloverFee;
    }

    // ================ Events =================

    event SetLendingFee(bytes32 indexed id, uint16 fee);

    event SetVaultMintFee(uint64 fee);

    // ================ Getter/Setter =================

    function setLendingFee(bytes32 id, uint16 fee) external;

    function setVaultMintFee(uint64 fee) external;

    function getLendingFee(bytes32 id) external view returns (uint16);

    function getVaultMintFee() external view returns (uint64);

    function getFeesOrigination() external view returns (FeesOrigination memory);

    function getFeesRollover() external view returns (FeesRollover memory);

    function getMaxLendingFee(bytes32 id) external view returns (uint16);

    function getMaxVaultMintFee() external view returns (uint64);
}
// File: contracts/libraries/FeeLookups.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title FeeLookups
 * @author Non-Fungible Technologies, Inc.
 *
 * Enumerates unique identifiers for fee identifiers
 * that the lending protocol uses.
 */
abstract contract FeeLookups {
    /// @dev Origination fees: amount in bps, payable in loan token
    bytes32 public constant FL_01 = keccak256("BORROWER_ORIGINATION_FEE");
    bytes32 public constant FL_02 = keccak256("LENDER_ORIGINATION_FEE");

    /// @dev Rollover fees: amount in bps, payable in loan token
    bytes32 public constant FL_03 = keccak256("BORROWER_ROLLOVER_FEE");
    bytes32 public constant FL_04 = keccak256("LENDER_ROLLOVER_FEE");

    /// @dev Loan closure fees: amount in bps, payable in loan token
    bytes32 public constant FL_05 = keccak256("LENDER_DEFAULT_FEE");
    bytes32 public constant FL_06 = keccak256("LENDER_INTEREST_FEE");
    bytes32 public constant FL_07 = keccak256("LENDER_PRINCIPAL_FEE");
    bytes32 public constant FL_08 = keccak256("LENDER_REDEEM_FEE");
}

// File: contracts/errors/Lending.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "../libraries/LoanLibrary.sol";

/**
 * @title LendingErrors
 * @author Non-Fungible Technologies, Inc.
 *
 * This file contains custom errors for the core lending protocol contracts, with errors
 * prefixed by the contract that throws them (e.g., "OC_" for OriginationController).
 * Errors located in one place to make it possible to holistically look at all
 * protocol failure cases.
 */

// ==================================== ORIGINATION CONTROLLER ======================================
/// @notice All errors prefixed with OC_, to separate from other contracts in the protocol.

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                  The name of the parameter for which a zero address was provided.
 */
error OC_ZeroAddress(string addressType);

/**
 * @notice Ensure valid loan state for loan lifceycle operations.
 *
 * @param state                         Current state of a loan according to LoanState enum.
 */
error OC_InvalidState(LoanLibrary.LoanState state);

/**
 * @notice Loan duration must be greater than 1hr and less than 3yrs.
 *
 * @param durationSecs                 Total amount of time in seconds.
 */
error OC_LoanDuration(uint256 durationSecs);

/**
 * @notice Interest must be greater than 0.01% and less than 10,000%. (interestRate / 1e18 >= 1)
 *
 * @param interestRate                  InterestRate with 1e18 multiplier.
 */
error OC_InterestRate(uint256 interestRate);

/**
 * @notice One of the predicates for item verification failed.
 *
 * @param borrower                      The address of the borrower.
 * @param lender                        The address of the lender.
 * @param verifier                      The address of the verifier contract.
 * @param collateralAddress             The address of the collateral token.
 * @param collateralId                  The token ID of the collateral.
 * @param data                          The verification data (to be parsed by verifier).
 */
error OC_PredicateFailed(
    address borrower,
    address lender,
    address verifier,
    address collateralAddress,
    uint256 collateralId,
    bytes data
);

/**
 * @notice The predicates array is empty.
 */
error OC_PredicatesArrayEmpty();

/**
 * @notice A caller attempted to approve themselves.
 *
 * @param caller                        The caller of the approve function.
 */
error OC_SelfApprove(address caller);

/**
 * @notice A caller attempted to originate a loan with their own signature.
 *
 * @param caller                        The caller of the approve function, who was also the signer.
 */
error OC_ApprovedOwnLoan(address caller);

/**
 * @notice The signature could not be recovered to the counterparty or approved party.
 *
 * @param target                        The target party of the signature, which should either be the signer,
 *                                      or someone who has approved the signer.
 * @param signer                        The signer determined from ECDSA.recover.
 */
error OC_InvalidSignature(address target, address signer);

/**
 * @notice The verifier contract specified in a predicate has not been whitelisted.
 *
 * @param verifier                      The verifier the caller attempted to use.
 */
error OC_InvalidVerifier(address verifier);

/**
 * @notice The function caller was neither borrower or lender, and was not approved by either.
 *
 * @param caller                        The unapproved function caller.
 */
error OC_CallerNotParticipant(address caller);

/**
 * @notice Signer is attempting to take the wrong side of the loan.
 *
 * @param signer                       The address of the external signer.
 */
error OC_SideMismatch(address signer);

/**
 * @notice Two related parameters for batch operations did not match in length.
 */
error OC_BatchLengthMismatch();

/**
 * @notice Principal must be greater than 9999 Wei.
 *
 * @param principal                     Principal in ether.
 */
error OC_PrincipalTooLow(uint256 principal);

/**
 * @notice Signature must not be expired.
 *
 * @param deadline                      Deadline in seconds.
 */
error OC_SignatureIsExpired(uint256 deadline);

/**
 * @notice New currency does not match for a loan rollover request.
 *
 * @param oldCurrency                   The currency of the active loan.
 * @param newCurrency                   The currency of the new loan.
 */
error OC_RolloverCurrencyMismatch(address oldCurrency, address newCurrency);

/**
 * @notice New currency does not match for a loan rollover request.
 *
 * @param oldCollateralAddress          The address of the active loan's collateral.
 * @param newCollateralAddress          The token ID of the active loan's collateral.
 * @param oldCollateralId               The address of the new loan's collateral.
 * @param newCollateralId               The token ID of the new loan's collateral.
 */
error OC_RolloverCollateralMismatch(
    address oldCollateralAddress,
    uint256 oldCollateralId,
    address newCollateralAddress,
    uint256 newCollateralId
);

/**
 * @notice Provided payable currency address is not approved for lending.
 *
 * @param payableCurrency       ERC20 token address supplied in loan terms.
 */
error OC_InvalidCurrency(address payableCurrency);

/**
 * @notice Provided collateral address is not approved for lending.
 *
 * @param collateralAddress       ERC721 or ERC1155 token address supplied in loan terms.
 */
error OC_InvalidCollateral(address collateralAddress);

/**
 * @notice Provided token array does not hold any token addresses.
 */
error OC_ZeroArrayElements();

/**
 * @notice Provided token array holds more than 50 token addresses.
 */
error OC_ArrayTooManyElements();

// ==================================== ITEMS VERIFIER ======================================
/// @notice All errors prefixed with IV_, to separate from other contracts in the protocol.

/**
 * @notice The predicate payload was decoded successfully, but list of predicates is empty.
 */
error IV_NoPredicates();

/**
 * @notice Provided SignatureItem is missing an address.
 */
error IV_ItemMissingAddress();

/**
 * @notice Provided SignatureItem has an invalid collateral type.
 * @dev    Should never actually fire, since cType is defined by an enum, so will fail on decode.
 *
 * @param asset                        The NFT contract being checked.
 * @param cType                        The collateralTytpe provided.
 */
error IV_InvalidCollateralType(address asset, uint256 cType);

/**
 * @notice Provided signature item with no required amount. For single ERC721s, specify 1.
 *
 * @param asset                         The NFT contract being checked.
 * @param amount                        The amount provided (should be 0).
 */
error IV_NoAmount(address asset, uint256 amount);

/**
 * @notice Provided a wildcard for a non-ERC721.
 *
 * @param asset                         The NFT contract being checked.
 */
error IV_InvalidWildcard(address asset);

/**
 * @notice The provided token ID is out of bounds for the given collection.
 *
 * @param tokenId                       The token ID provided.
 */
error IV_InvalidTokenId(int256 tokenId);

/**
 * @notice The provided project ID does not exist on the target contract. Only
 *         used for ArtBlocks.
 *
 * @param projectId                     The project ID provided.
 * @param nextProjectId                 The contract's reported nextProjectId.
 */
error IV_InvalidProjectId(uint256 projectId, uint256 nextProjectId);

/**
 * @notice The provided collateralId converts to a vault, but
 *         the vault's address does not convert back to the provided collateralId
 *         when casted to a uint256.
 */
error IV_InvalidCollateralId(uint256 collateralId);

// ==================================== REPAYMENT CONTROLLER ======================================
/// @notice All errors prefixed with RC_, to separate from other contracts in the protocol.

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                  The name of the parameter for which a zero address was provided.
 */
error RC_ZeroAddress(string addressType);

/**
 * @notice Could not dereference loan from loan ID.
 *
 * @param target                     The loanId being checked.
 */
error RC_CannotDereference(uint256 target);

/**
 * @notice Ensure valid loan state for loan lifceycle operations.
 *
 * @param state                         Current state of a loan according to LoanState enum.
 */
error RC_InvalidState(LoanLibrary.LoanState state);

/**
 * @notice Caller is not the owner of lender note.
 *
 * @param lender                     The owner of the lender note.
 * @param caller                     Msg.sender of the function call.
 */
error RC_OnlyLender(address lender, address caller);

// ==================================== Loan Core ======================================
/// @notice All errors prefixed with LC_, to separate from other contracts in the protocol.

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                  The name of the parameter for which a zero address was provided.
 */
error LC_ZeroAddress(string addressType);

/// @notice Borrower address is same as lender address.
error LC_ReusedNote();

/// @notice Zero amount passed in where not allowed.
error LC_ZeroAmount();

/**
 * @notice Check collateral is not already used in a active loan.
 *
 * @param collateralAddress             Address of the collateral.
 * @param collateralId                  ID of the collateral token.
 */
error LC_CollateralInUse(address collateralAddress, uint256 collateralId);

/**
 * @notice The reported settlements are invalid, and LoanCore would lose tokens
 *         attempting to perform the requested operations.
 *
 *
 * @param payout                        Amount of tokens to be paid out.
 * @param collected                     Amount of tokens to collect - should be fewer than payout.
 */
error LC_CannotSettle(uint256 payout, uint256 collected);

/**
 * @notice User attempted to withdraw a pending balance that was in excess
 *         of what is available.
 *
 * @param amount                        Amount of tokens to be withdrawn.
 * @param available                     Amount of tokens available to withdraw.
 */
error LC_CannotWithdraw(uint256 amount, uint256 available);

/**
 * @notice Two arrays were provided that must be of matching length, but were not.
 *
 */
error LC_ArrayLengthMismatch();

/**
 * @notice A proposed affiliate split was submitted that is over the maximum.
 *
 * @param splitBps                     The proposed affiliate split.
 * @param maxSplitBps                  The maximum allowed affiliate split.
 *
 */
error LC_OverMaxSplit(uint96 splitBps, uint96 maxSplitBps);

/**
 * @notice Ensure valid loan state for loan lifceycle operations.
 *
 * @param state                         Current state of a loan according to LoanState enum.
 */
error LC_InvalidState(LoanLibrary.LoanState state);

/**
 * @notice Loan duration has not expired.
 *
 * @param dueDate                       Timestamp of the end of the loan duration.
 */
error LC_NotExpired(uint256 dueDate);

/**
 * @notice User address and the specified nonce have already been used.
 *
 * @param user                          Address of collateral owner.
 * @param nonce                         Represents the number of transactions sent by address.
 */
error LC_NonceUsed(address user, uint160 nonce);

/**
 * @notice Protocol attempted to set an affiliate code which already exists. Affiliate
 *         codes are immutable.
 *
 * @param affiliateCode                 The affiliate code being set.
 */
error LC_AffiliateCodeAlreadySet(bytes32 affiliateCode);

/**
 * @notice Specified note token ID does not have a redeemable receipt.
 *
 * @param loanId                     The loanId being checked.
 */
error LC_NoReceipt(uint256 loanId);

/**
 * @notice Only Loan Core contract can call this function.
 */
error LC_CallerNotLoanCore();

/**
 * @notice The loan core contract has been irreversibly shut down.
 */
error LC_Shutdown();

// ==================================== Promissory Note ======================================
/// @notice All errors prefixed with PN_, to separate from other contracts in the protocol.

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                  The name of the parameter for which a zero address was provided.
 */
error PN_ZeroAddress(string addressType);

/**
 * @notice Caller of mint function must have the MINTER_ROLE in AccessControl.
 *
 * @param caller                        Address of the function caller.
 */
error PN_MintingRole(address caller);

/**
 * @notice Caller of burn function must have the BURNER_ROLE in AccessControl.
 *
 * @param caller                        Address of the function caller.
 */
error PN_BurningRole(address caller);

/**
 * @notice Non-existant token id provided as argument.
 *
 * @param tokenId                       The ID of the token to lookup the URI for.
 */
error PN_DoesNotExist(uint256 tokenId);

// ==================================== Fee Controller ======================================
/// @notice All errors prefixed with FC_, to separate from other contracts in the protocol.

/**
 * @notice Caller attempted to set a lending fee which is larger than the global maximum.
 */
error FC_LendingFeeOverMax(bytes32 selector, uint256 fee, uint256 maxFee);

/**
 * @notice Caller attempted to set a vault mint fee which is larger than the global maximum.
 */
error FC_VaultMintFeeOverMax(uint256 fee, uint256 maxFee);

// ==================================== ERC721 Permit ======================================
/// @notice All errors prefixed with ERC721P_, to separate from other contracts in the protocol.

/**
 * @notice Deadline for the permit has expired.
 *
 * @param deadline                      Permit deadline parameter as a timestamp.
 */
error ERC721P_DeadlineExpired(uint256 deadline);

/**
 * @notice Address of the owner to also be the owner of the tokenId.
 *
 * @param owner                        Owner parameter for the function call.
 */
error ERC721P_NotTokenOwner(address owner);

/**
 * @notice Invalid signature.
 *
 * @param signer                        Signer recovered from ECDSA sugnature hash.
 */
error ERC721P_InvalidSignature(address signer);



// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @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 meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/libraries/LoanLibrary.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title LoanLibrary
 * @author Non-Fungible Technologies, Inc.
 *
 * Contains all data types used across Arcade lending contracts.
 */
library LoanLibrary {
    /**
     * @dev Enum describing the current state of a loan.
     * State change flow:
     * Created -> Active -> Repaid
     *                   -> Defaulted
     */
    enum LoanState {
        // We need a default that is not 'Created' - this is the zero value
        DUMMY_DO_NOT_USE,
        // The loan has been initialized, funds have been delivered to the borrower and the collateral is held.
        Active,
        // The loan has been repaid, and the collateral has been returned to the borrower. This is a terminal state.
        Repaid,
        // The loan was delinquent and collateral claimed by the lender. This is a terminal state.
        Defaulted
    }

    /**
     * @dev The raw terms of a loan.
     */
    struct LoanTerms {
        // Interest expressed as a rate, unlike V1 gross value.
        // Input conversion: 0.01% = (1 * 10**18) ,  10.00% = (1000 * 10**18)
        // This represents the rate over the lifetime of the loan, not APR.
        // 0.01% is the minimum interest rate allowed by the protocol.
        uint256 proratedInterestRate;
        /// @dev Full-slot variables
        // The amount of principal in terms of the payableCurrency.
        uint256 principal;
        // The token ID of the address holding the collateral.
        /// @dev Can be an AssetVault, or the NFT contract for unbundled collateral
        address collateralAddress;
        /// @dev Packed variables
        // The number of seconds representing relative due date of the loan.
        /// @dev Max is 94,608,000, fits in 96 bits
        uint96 durationSecs;
        // The token ID of the collateral.
        uint256 collateralId;
        // The payable currency for the loan principal and interest.
        address payableCurrency;
        // Timestamp for when signature for terms expires
        uint96 deadline;
        // Affiliate code used to start the loan.
        bytes32 affiliateCode;
    }

    /**
     * @dev Modification of loan terms, used for signing only.
     *      Instead of a collateralId, a list of predicates
     *      is defined by 'bytes' in items.
     */
    struct LoanTermsWithItems {
        // Interest expressed as a rate, unlike V1 gross value.
        // Input conversion: 0.01% = (1 * 10**18) ,  10.00% = (1000 * 10**18)
        // This represents the rate over the lifetime of the loan, not APR.
        // 0.01% is the minimum interest rate allowed by the protocol.
        uint256 proratedInterestRate;
        /// @dev Full-slot variables
        // The amount of principal in terms of the payableCurrency.
        uint256 principal;
        // The tokenID of the address holding the collateral
        address collateralAddress;
        /// @dev Packed variables
        // The number of seconds representing relative due date of the loan.
        /// @dev Max is 94,608,000, fits in 96 bits
        uint96 durationSecs;
        // An encoded list of predicates, along with their verifiers.
        bytes items;
        // The payable currency for the loan principal and interest.
        address payableCurrency;
        // Timestamp for when signature for terms expires
        uint96 deadline;
        // Affiliate code used to start the loan.
        bytes32 affiliateCode;
    }

    /**
     * @dev Predicate for item-based verifications
     */
    struct Predicate {
        // The encoded predicate, to decoded and parsed by the verifier contract.
        bytes data;
        // The verifier contract.
        address verifier;
    }

    /**
     * @dev Snapshot of lending fees at the time of loan creation.
     */
    struct FeeSnapshot {
        // The fee taken when lender claims defaulted collateral.
        uint16 lenderDefaultFee;
        // The fee taken from the borrower's interest repayment.
        uint16 lenderInterestFee;
        // The fee taken from the borrower's principal repayment.
        uint16 lenderPrincipalFee;
    }

    /**
     * @dev The data of a loan. This is stored once the loan is Active
     */
    struct LoanData {
        /// @dev Packed variables
        // The current state of the loan.
        LoanState state;
        // Start date of the loan, using block.timestamp.
        uint160 startDate;
        /// @dev Full-slot variables
        // The raw terms of the loan.
        LoanTerms terms;
        // Record of lending fees at the time of loan creation.
        FeeSnapshot feeSnapshot;
    }
}