TinteroLoan
A smart contract that manages the lifecycle of a loan
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
onlyBeneficiary
Reverts if the caller is not the loan’s beneficiary
constructor
Note: oz-upgrades-unsafe-allow: constructor
initialize
Initializes the loan with the provided parameters.
Parameters
Name | Type | Description |
---|---|---|
liquidityProvider_ | address | The address funding the loan. |
collateralAsset_ | address | The ERC721 token used as collateral. |
beneficiary_ | address | The address to receive the principal once funded. |
defaultThreshold_ | uint24 | The 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 attotalPayments
. - Emits a
PaymentCreated
event for each payment added.*
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 attotalTranches
. - The tranches are added to the loan.
- Emits a
TrancheCreated
event for each tranche added.*
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 byn
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.*
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*
repayCurrent
Same as repayN(0, collateralReceiver)
.
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 byn
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.*
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.*
_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 attotalPayments
. - Emits a
PaymentCreated
event for each payment added.*
_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 attotalTranches
. - The tranches are added to the loan.
- Emits a
TrancheCreated
event for each tranche added.*
_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 currenttotalPayments
. - Emits a
PaymentCreated
event.*
_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.*
_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 byn
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.*
_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.*
_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 byn
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.*
_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.*
upgradeLoan
Upgrades the loan to a new implementation. Useful for renegotiating terms.
_authorizeUpgrade
_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
.
_encodeStateBitmap
*Encodes a LoanState
into a bytes32
representation where each bit enabled corresponds to
the underlying position in the LoanState
enum. For example:
_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.*
_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.*
_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.*
TinteroLoanStorage
Inherits: ITinteroLoan
State Variables
TINTERO_LOAN_STORAGE
Functions
getTinteroLoanStorage
Get EIP-7201 storage
Structs
LoanStorage
TinteroLoanView
Inherits: AccessManaged
State Variables
INITIAL_ERC721_COLLATERAL_LOAN_IMPLEMENTATION
Functions
predictLoanAddress
Predict the address of a Loan contract using the provided parameters.
_deployLoan
Deploy a new Loan contract using the provided parameters.
_loanProxyBytecode
Returns the bytecode to be used when deploying a new Loan contract.
_salt
Implementation of keccak256(abi.encode(a, b)) that doesn’t allocate or expand memory.
TinteroLoanFactory
Inherits: TinteroLoanStorage
Functions
lendingAsset
Address of the ERC20 token lent.
collateralAsset
Address of the ERC721 token used as collateral.
liquidityProvider
Address of the liquidity provider funding the loan.
tranche
Get the index at which the tranche starts and its recipient. A tranche is a collection of payments from [previousPaymentIndex ?? 0, paymentIndex).
currentTrancheIndex
Get the index of the current tranche.
totalTranches
Total tranches in the loan.
payment
Get payment details. A Payment is a struct with a principal and interest terms.
collateralId
Get the collateral tokenId for a payment.
currentPaymentIndex
Get the index of the current payment yet to be repaid.
totalPayments
Get the total number of payments.
currentFundingIndex
Get the index of the current payment yet to be funded.
beneficiary
Address of the beneficiary of the loan.
defaultThreshold
Amount of missed payments at which the loan is defaulted.
state
Get the current state of the loan.