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.
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
initialize
Initializes the loan with the provided parameters.
function initialize(
address liquidityProvider_,
address collateralAsset_,
address beneficiary_,
uint24 defaultThreshold_
) public initializer;
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 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);
Responses are generated using AI and may contain mistakes.