TinteroLoan

Inherits: Initializable, UUPSUpgradeable, TinteroLoanView

Author: Ernesto García

Loan that uses an ERC721 token as collateral. The loan is funded with an ERC20 token and structured in a series of payments and tranches they belong to. This contract behaves as a state machine with the following states:

  • CREATED: The loan has been initialized and payments or tranches are being added.
  • CANCELED: The loan has been canceled and the collateral is being withdrawn.
  • FUNDING: The loan is being funded by the liquidity provider.
  • ONGOING: The loan has been funded and the payments are being repaid.
  • DEFAULTED: The loan has defaulted and the collateral can be repossessed.
  • REPOSSESSED: The collateral is being repossessed by the liquidity provider.
  • PAID: The loan has been fully repaid.

Concepts

  • Payments: A payment is a structure that represents a payment to be made back to the loan. Each payment has a principal amount and an interest rate that is accrued over time in a linear fashion. A premium rate is added to the interest rate after the payment is due (at maturity).
  • Tranches: A tranche is a collection of payments that have the same recipient. They are used to sell parts of the loan to different investors.
  • Collateral: The collateral is an ERC721 token that is used to back the payments. A payment’s collateral can be repossessed if the loan defaults after a default threshold.

Users must approve this contract to transfer their ERC-721 tokens used as collateral. This may allow a malicious actor to transfer request a loan and transferring their tokens to this contract unexpectedly. For those cases, the original owner can retake their collateral with the withdrawPaymentCollateral function.

security-contact: security@plumaa.id

Functions

onlyLiquidityProvider

Reverts if the caller is not the loan’s liquidity provider

modifier onlyLiquidityProvider();

onlyBeneficiary

Reverts if the caller is not the loan’s beneficiary

modifier onlyBeneficiary();

constructor

Note: oz-upgrades-unsafe-allow: constructor

constructor();

initialize

Initializes the loan with the provided parameters.

function initialize(
    address liquidityProvider_,
    address collateralAsset_,
    address beneficiary_,
    uint24 defaultThreshold_
) public initializer;

Parameters

NameTypeDescription
liquidityProvider_addressThe address funding the loan.
collateralAsset_addressThe ERC721 token used as collateral.
beneficiary_addressThe address to receive the principal once funded.
defaultThreshold_uint24The number of missed payments at which the loan defaults.

pushPayments

*Adds a list of payments to the loan.

Requirements:

  • The caller MUST be the liquidity provider.
  • The loan MUST be in CREATED state.
  • The collateral tokenIds and payments arrays MUST have the same length.
  • The payments MUST be ordered by maturity date.
  • The payments MUST NOT have matured.
  • The collateral tokenIds MUST NOT have been added before.
  • The collateralTokenIds MUST exist.
  • The owner of each collateral tokenId MUST have approved this contract to transfer it (if not the contract itself).

Effects:

  • The totalPayments is incremented by the length of the payments array.
  • The collateralTokenIds are transferred to this contract.
  • The payment function will return the added payments at their corresponding indexes starting at totalPayments.
  • Emits a PaymentCreated event for each payment added.*
function pushPayments(uint256[] calldata collateralTokenIds, PaymentLib.Payment[] calldata payment_)
    external
    onlyLiquidityProvider
    returns (uint256 principalRequested);

pushTranches

*Adds a list of tranches to the loan.

Requirements:

  • The caller MUST be the liquidity provider.
  • The loan MUST be in CREATED state.
  • The paymentIndexes and recipients arrays MUST have the same length.
  • The tranche indexes MUST be strictly increasing.
  • The total number of tranches MUST be less than the total number of payments.

Effects:

  • The totalTranches is incremented by the length of the tranches array.
  • The tranche function will return the added tranches at their corresponding indexes starting at totalTranches.
  • The tranches are added to the loan.
  • Emits a TrancheCreated event for each tranche added.*
function pushTranches(uint96[] calldata paymentIndexes, address[] calldata recipients) external onlyLiquidityProvider;

fundN

*Funds n payments from the loan.

Requirements:

  • The loan MUST be in CREATED or FUNDING state.
  • Tranches MUST include all payments.
  • The caller MUST have enough funds to fund the payments
  • This contract mus have been approved to transfer the principal amount from the caller.

Effects:

  • Moves to FUNDING state
  • Moves to ONGOING state if all payments are funded.
  • The currentFundingIndex is incremented by n or the remaining payments.
  • The principal of the funded payments is transferred from the liquidity provider to the beneficiary.
  • Sets the fundedAt field of the funded payments to the current block timestamp.
  • Emits a PaymentsFunded event with the range of funded payments.*
function fundN(uint256 n) external returns (uint256 totalPrincipal);

withdrawPaymentCollateral

*Withdraws the collateral to the beneficiary.

Requirements:

  • The loan MUST be in CREATED or CANCELED state.
  • Each payment collateral MUST be owned by this contract.
  • The caller MUST be the beneficiary.

Effects:

  • Moves to CANCELED state.
  • Each payment collateral is transferred to the beneficiary.
  • Emits a PaymentsWithdrawn with the range of payments withdrawn*
function withdrawPaymentCollateral(uint256 start, uint256 end) external onlyBeneficiary;

repayCurrent

Same as repayN(0, collateralReceiver).

function repayCurrent(address collateralReceiver) external;

repayN

*Repays the current loan and n future payments.

Requirements:

  • The loan MUST be in ONGOING or DEFAULTED state.
  • The sender MUST have enough funds to repay the principal of the specified payments
  • The sender MUST have approved this contract to transfer the principal amount
  • The collateral MUST be owned by this contract.

Effects:

  • Moves to ONGOING if paid until below the default threshold.
  • Moves to PAID state if all payments are repaid.
  • The currentPaymentIndex is incremented by n or the remaining payments.
  • The principal of the repaid payments is transferred from the sender to the receiver of each payment tranche
  • The collateral is transferred to the collateralReceiver if provided, otherwise it is burned.
  • Emits a PaymentsRepaid event with the range of repaid payments.*
function repayN(uint256 n, address collateralReceiver) public;

repossess

*Repossess the collateral from payments.

Requirements:

  • The loan MUST be in DEFAULTED or REPOSSESSED state.
  • The caller MUST be the liquidity provider.
  • The collateral MUST be owned by this contract.
  • The receiver MUST implement IERC721Receiver to receive the collateral.

Effects:

  • Moves to REPOSSESSED state.
  • The collateral is transferred to the receiver.
  • Emits a PaymentsRepossessed event with the range of repossessed payments.*
function repossess(uint256 start, uint256 end, address receiver) external onlyLiquidityProvider;

_validatePushPaymentsAndCollectCollateral

*Performs validations on the payments to be added to the loan.

Requirements:

  • The collateral tokenIds and payments arrays MUST have the same length.
  • The payments MUST be ordered by maturity date.
  • The payments fundedAt field MUST be 0.
  • The payments MUST NOT have matured.
  • The collateral tokenIds MUST NOT have been added before.
  • The collateralTokenIds MUST exist.
  • The owner of each collateral tokenId MUST have approved this contract to transfer it (if not the contract itself).

Effects:

  • The totalPayments is incremented by the length of the payments array.
  • The collateralTokenIds are transferred to this contract.
  • The payment function will return the added payments at their corresponding indexes starting at totalPayments.
  • Emits a PaymentCreated event for each payment added.*
function _validatePushPaymentsAndCollectCollateral(
    uint256[] calldata collateralTokenIds_,
    PaymentLib.Payment[] calldata payments_
) internal returns (uint256);

_validateAndPushTranches

*Validates the tranches and adds them to the loan.

Requirements:

  • The paymentIndexes and recipients arrays MUST have the same length.
  • The tranche indexes MUST be strictly increasing.
  • The total number of tranches MUST be less than the total number of payments.

Effects:

  • The totalTranches is incremented by the length of the tranches array.
  • The tranche function will return the added tranches at their corresponding indexes starting at totalTranches.
  • The tranches are added to the loan.
  • Emits a TrancheCreated event for each tranche added.*
function _validateAndPushTranches(uint96[] calldata paymentIndexes_, address[] calldata recipients_) internal;

_validatePushPayment

*Validates the payment and adds it to the loan.

Requirements:

  • The payment fundedAt field MUST be 0.
  • The payment maturity date MUST NOT be before the latest maturity.
  • The payment MUST NOT have matured.
  • The collateral tokenId MUST not have been added before.

Effects:

  • The totalPayments is incremented by 1.
  • The payment function will return the added _payment after the current totalPayments.
  • Emits a PaymentCreated event.*
function _validatePushPayment(
    uint256 i,
    uint256 largestMaturityPeriod,
    uint256 collateralTokenId,
    PaymentLib.Payment calldata payment_
) internal returns (uint256);

_collectCollateral

*Checks if the tokenId is owned by this contract and transfers it to this contract otherwise.

Requirements:

  • The collateralTokenIds MUST exist.
  • The owner of each collateral tokenId MUST have approved this contract to transfer it (if not the contract itself).

Effects:

  • The collateralTokenIds are transferred to this contract.*
function _collectCollateral(ERC721Burnable asset, uint256 tokenId) internal;

_fundN

*Funds n payments from the loan. Returns the total principal to fund. The end index is capped to the total number of payments.

Effects:

  • Moves to FUNDING state
  • Moves to ONGOING state if all payments are funded.
  • The currentFundingIndex is incremented by n or the remaining payments.
  • The principal of the funded payments is transferred from the liquidity provider to the beneficiary.
  • Sets the fundedAt field of the funded payments to the current block timestamp.*
function _fundN(uint256 n) internal returns (uint256);

_withdrawPaymentCollateral

*Withdraws the collateral to the beneficiary.

Requirements:

  • Each payment collateral MUST be owned by this contract.

Effects:

  • Moves to CANCELED state.
  • The payment collateral is transferred to the beneficiary.
  • Emits a PaymentsWithdrawn event.*
function _withdrawPaymentCollateral(LoanState state_, uint256 start, uint256 end) internal;

_repay

*Repays the current loan and n future payments.

Requirements:

  • The sender MUST have enough funds to repay the principal of the specified payments
  • The sender MUST have approved this contract to transfer the principal amount
  • The collateral MUST be owned by this contract.

Effects:

  • Moves to ONGOING if paid until below the default threshold.
  • Moves to PAID state if all payments are repaid.
  • The currentPaymentIndex is incremented by n or the remaining payments.
  • The principal of the repaid payments is transferred from the sender to the receiver of each payment tranche
  • The collateral is transferred to the collateralReceiver if provided, otherwise it is burned.
  • Emits a PaymentsRepaid event with the range of repaid payments.*
function _repay(uint256 start, uint256 end, address collateralReceiver) internal;

_repossess

*Repossess the collateral from payments.

Requirements:

  • The collateral MUST be owned by this contract.
  • The receiver MUST implement IERC721Receiver to receive the collateral.

Effects:

  • Moves to REPOSSESSED state.
  • The collateral is transferred to the receiver.
  • Emits a PaymentsRepossessed event with the range of repossessed payments.*
function _repossess(LoanState state_, uint256 start, uint256 end, address receiver) internal;

upgradeLoan

Upgrades the loan to a new implementation. Useful for renegotiating terms.

function upgradeLoan(address newImplementation, bytes calldata data) external;

_authorizeUpgrade

function _authorizeUpgrade(address newImplementation) internal override onlyLiquidityProvider;

_validateStateBitmap

Check that the current state of the loan matches the requirements described by the allowedStates bitmap. Otherwise, reverts with an UnexpectedLoanState error. This bitmap should be built using _encodeStateBitmap.

function _validateStateBitmap(bytes32 allowedStates) private view returns (LoanState currentState);

_encodeStateBitmap

*Encodes a LoanState into a bytes32 representation where each bit enabled corresponds to the underlying position in the LoanState enum. For example:

0x000...10000
  ^^^^^^----- ...
        ^---- ONGOING
         ^--- DEFAULTED
          ^-- REPOSSESSED
           ^- PAID
function _encodeStateBitmap(LoanState loanState) private pure returns (bytes32);

_repayByTranches

*Repays the payments from start to end by doing a single transfer per tranche. Assumes end is not greater than the total number of payments.

Requirements:

  • The sender MUST have enough funds to repay the principal of the specified payments
  • The sender MUST have approved this contract to transfer the principal amount

Effects:

  • The principal of the repaid payments is transferred from the sender to the receiver of each payment tranche
  • Moves to ONGOING if paid until below the default threshold.
  • Moves to PAID state if all payments are repaid.
  • Emits a PaymentsRepaid event with the range of repaid payments.*
function _repayByTranches(uint256 start, uint256 end) private returns (uint256 principalPaid);

_prepareToPay

*Prepares the loan for repayment of n payments. Returns the total amount to pay. Assumes end is not greater than the total number of payments.

Effects:

  • Moves to ONGOING if paid until below the default threshold.
  • Moves to PAID state if all payments are repaid.
  • Emits a PaymentsRepaid event with the range of repaid payments.*
function _prepareToPay(uint256 start, uint256 end) private returns (uint256 toPay, uint256 principalPaid);

_debitCollateral

*Disposes the collateral from payments. Burns the collateral if collateralReceiver is the zero address.

Requirements:

  • Each payment collateral MUST be owned by this contract.

Effects:

  • The collateral is transferred to the collateralReceiver if provided, otherwise it is burned.*
function _debitCollateral(uint256 start, uint256 end, address collateralReceiver, uint256 discountedPrincipal)
    private;

TinteroLoanStorage

Inherits: ITinteroLoan

State Variables

TINTERO_LOAN_STORAGE

bytes32 private constant TINTERO_LOAN_STORAGE = 0x1f389b2eba3c6a855b1e43cd4e972600e00166178892203933678c6474e96300;

Functions

getTinteroLoanStorage

Get EIP-7201 storage

function getTinteroLoanStorage() internal pure returns (LoanStorage storage $);

Structs

LoanStorage

struct LoanStorage {
    Checkpoints.Trace160 _tranches;
    address liquidityProvider;
    bool _canceled;
    bool _repossessed;
    address collateralAsset;
    PaymentLib.Payment[] payments;
    uint256[] collateralTokenIds;
    EnumerableSet.UintSet heldTokenIds;
    address beneficiary;
    uint24 defaultThreshold;
    uint24 currentPaymentIndex;
    uint24 currentFundingIndex;
}

TinteroLoanView

Inherits: AccessManaged

State Variables

INITIAL_ERC721_COLLATERAL_LOAN_IMPLEMENTATION

address public immutable INITIAL_ERC721_COLLATERAL_LOAN_IMPLEMENTATION = address(new TinteroLoan());

Functions

predictLoanAddress

Predict the address of a Loan contract using the provided parameters.

function predictLoanAddress(
    address collateralCollection_,
    address beneficiary_,
    uint24 defaultThreshold_,
    bytes32 salt,
    address caller_
) public view returns (address loan, bytes memory bytecode, bytes32 bytecodeHash);

_deployLoan

Deploy a new Loan contract using the provided parameters.

function _deployLoan(address collateralCollection_, address beneficiary_, uint24 defaultThreshold_, bytes32 salt)
    internal
    returns (address loan);

_loanProxyBytecode

Returns the bytecode to be used when deploying a new Loan contract.

function _loanProxyBytecode(address collateralCollection_, address beneficiary_, uint24 defaultThreshold_)
    internal
    view
    returns (bytes memory);

_salt

Implementation of keccak256(abi.encode(a, b)) that doesn’t allocate or expand memory.

function _salt(bytes32 salt, address caller_) internal pure returns (bytes32 value);

TinteroLoanFactory

Inherits: TinteroLoanStorage

Functions

lendingAsset

Address of the ERC20 token lent.

function lendingAsset() public view returns (IERC20);

collateralAsset

Address of the ERC721 token used as collateral.

function collateralAsset() public view returns (ERC721Burnable);

liquidityProvider

Address of the liquidity provider funding the loan.

function liquidityProvider() public view returns (ITinteroVault);

tranche

Get the index at which the tranche starts and its recipient. A tranche is a collection of payments from [previousPaymentIndex ?? 0, paymentIndex).

function tranche(uint256 trancheIndex) public view returns (uint96 paymentIndex, address recipient);

currentTrancheIndex

Get the index of the current tranche.

function currentTrancheIndex() public view returns (uint256);

totalTranches

Total tranches in the loan.

function totalTranches() public view returns (uint256);

payment

Get payment details. A Payment is a struct with a principal and interest terms.

function payment(uint256 index) public view returns (PaymentLib.Payment memory);

collateralId

Get the collateral tokenId for a payment.

function collateralId(uint256 index) public view returns (uint256);

currentPaymentIndex

Get the index of the current payment yet to be repaid.

function currentPaymentIndex() public view returns (uint256);

totalPayments

Get the total number of payments.

function totalPayments() public view returns (uint256);

currentFundingIndex

Get the index of the current payment yet to be funded.

function currentFundingIndex() public view returns (uint256);

beneficiary

Address of the beneficiary of the loan.

function beneficiary() public view returns (address);

defaultThreshold

Amount of missed payments at which the loan is defaulted.

function defaultThreshold() public view returns (uint256);

state

Get the current state of the loan.

function state() public view returns (LoanState);

_defaulted

function _defaulted(uint256 current) internal view returns (bool);