From ba3a7504eeca18ab575e28b6445a0446bbfcffa7 Mon Sep 17 00:00:00 2001 From: Vito Date: Fri, 18 Oct 2024 18:24:23 +0100 Subject: [PATCH 01/41] adds lending protocol spec --- XLS-0066d-lending-protocol/README.md | 1435 ++++++++++++++++++++++++++ 1 file changed, 1435 insertions(+) create mode 100644 XLS-0066d-lending-protocol/README.md diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md new file mode 100644 index 00000000..95bad938 --- /dev/null +++ b/XLS-0066d-lending-protocol/README.md @@ -0,0 +1,1435 @@ +
    
+Title:        Lending Protocol
+Revision:     1 (2024-10-18)
+
+
Authors: + Vytautas Vito Tumas + Aanchal Malhotra + +Affiliation: + Ripple +
+ +# Lending Protocol + +## _Abstract_ + +Decentralized Finance (DeFi) lending represents a transformative force within the blockchain ecosystem. It revolutionizes traditional financial services by offering a peer-to-peer alternative without intermediaries like banks or financial institutions. At its core, DeFi lending platforms empower users to borrow and lend digital assets directly, fostering financial inclusion, transparency, and efficiency. + +This proposal introduces fundamental primitives for an XRP Ledger-native Lending Protocol. The protocol offers straightforward on-chain uncollateralized fixed-term loans, utilizing pooled funds with pre-set terms for interest-accruing loans. The design relies on off-chain underwriting and risk management to assess the creditworthiness of the borrowers. However, the First-Loss Capital protection scheme absorbs some of the losses in case of a Loan Default. + +This version intentionally skips the complex mechanisms of automated on-chain collateral and liquidation management. Instead, it focuses on the primitives and the essential components for on-chain credit origination. Therefore, the primary design principle is flexibility and reusability to enable the introduction of additional complex features in the future. + +## Index + +- [**1. Introduction**](#1-introduction) + - [**1.1. Overview**](#11-overview) + - [**1.2. Compliance Features**](#12-compliance-features) + - [**1.3. Risk Management**](#13-risk-management) + - [**1.4. Interest Rates**](#14-interest-rates) + - [**1.5. Fees**](#15-fees) + - [**1.6. Terminology**](#16-terminology) + - [**1.7. System Diagram**](#17-system-diagram) +- [**2. Ledger Entries**](#2-ledger-entries) + - [**2.1. LoanBroker Ledger Entry**](#21-loanbroker-ledger-entry) + - [**2.1.1. Object Identifier**](#211-object-identifier) + - [**2.1.2. Fields**](#212-fields) + - [**2.1.3. LoanBroker _pseudo-account_**](#213-loanbroker-pseudo-account) + - [**2.1.4. Ownership**](#214-ownership) + - [**2.1.5. Reserves**](#215-reserves) + - [**2.1.6. Accounting**](#216-accounting) + - [**2.1.7. First-Loss Capital**](#217-first-loss-capital) + - [**2.2. Loan Ledger Entry**](#22-loan-ledger-entry) + - [**2.2.1. Object Identifier**](#221-object-identifier) + - [**2.2.2. Fields**](#222-fields) + - [**2.2.3. Ownership**](#223-ownership) + - [**2.2.4. Reserves**](#224-reserves) + - [**2.2.5. Impairment**](#225-impairment) +- [**3. Transactions**](#3-transactions) + - [**3.1. LoanBroker Transactions**](#31-loanbroker-transactions) + - [**3.1.1. LoanBrokerSet Transaction**](#311-loanbrokerset) + - [**3.1.2. LoanBrokerDelete Transaction**](#312-loanbrokerdelete) + - [**3.1.3. LoanBrokerCovereposit Transaction**](#313-loanbrokercoverdeposit) + - [**3.1.4. LoanBrokerCoverWithdraw Transaction**](#314-loanbrokercoverwithdraw) + - [**3.2 Loan Transactions**](#32-loan-transactions) + - [**3.2.1. LoanSet Transaction**](#321-loanset-transaction) + - [**3.2.2. LoanDelete Transaction**](#322-loandelete-transaction) + - [**3.2.3. LoanManage Transaction**](#323-loanmanage-transaction) + - [**3.2.4. LoanDraw Transaction**](#324-loandraw-transaction) + - [**3.2.5 LoanPay Transaction**](#325-loanpay-transaction) +- [**Appendix**](#appendix) + +## 1. Introduction + +### 1.1 Overview + +The Lending Protocol uses the [Vault](https://github.com/XRPLF/XRPL-Standards/discussions/192) on-chain object to provision assets from one or more depositors. A Loan Broker is responsible for managing the Lending Protocol and the associated Vault. The Vault Owner and Loan Broker must be on the same account, but this may change in the future. + +The specification introduces two new ledger entries: `LoanBroker` and `Loan`. The `LoanBroker` object captures the Lending Protocol-specific details, such as fees and First-Loss Capital cover. Furthermore, it tracks the funds taken from the `Vault`. The `Loan` object captures the Loan agreement between the Loan Broker and the Borrower. + +The specification introduces the following transactions: + +- **`LoanBrokerSet`**: A transaction to create a new `LoanBroker` object. +- **`LoanBrokerDelete`**: A transaction to delete an existing `LoanBroker` object. +- **`LoanBrokerCoverDeposit`**: A transaction to deposit First-Loss Capital. +- **`LoanBrokerCoverWithdraw`**: A transaction to withdraw First-Loss Capital. +- **`LoanSet`**: A transaction to create a new `Loan` object. +- **`LoanDelete`**: A transaction to delete an existing `Loan` object. +- **`LoanManage`**: A transaction to manage an existing `Loan`. +- **`LoanDraw`**: A transaction to drawdown `Loan` funds. +- **`LoanPay`**: A transaction to make a `Loan` payment. + +The flow of the lending protocol is as follows: + +1. The Loan Broker creates a `Vault` ledger entry. +2. The Loan Broker creates a `LoanBroker` ledger entry with a `LoanBrokerSet` transaction. +3. The Depositors deposit assets into the `Vault`. +4. Optionally, the Loan Broker deposits First-Loss Capital into the `LoanBroker` with the `LoanBrokerCoverDeposit` transaction. +5. The Loan Broker and Borrower create a `Loan` object with a `LoanSet` transaction. +6. The Borrower can draw funds with the `LoanDraw` transaction and make payments with the `LoanPay`. +7. If the Borrower fails to pay the Loan, the Loan Broker can default the `Loan` using the `LoanManage` transaction. +8. Once the Loan has matured (or defaulted), the Borrower or the Loan Broker can delete it using a `LoanDelete` transaction. +9. Optionally, the Loan Broker can withdraw the First-Loss Capital using the `LoanBrokerCoverWithdraw` transaction. +10. When all `Loan` objects are deleted, the Loan Broker can delete the `LoanBroker` object with a `LoanBrokerDelete` transaction. +11. When all `LoanBroker` objects are deleted, the Loan Broker can delete the `Vault` object. + +### 1.2 Compliance Features + +### 1.2.1 Clawback + +Clawback is a mechanism by which an asset Issuer (IOU or MPT, not XRP) claws back the funds. It can be performed on the Vault, not the Lending Protocol. + +### 1.2.2 Freeze + +Freeze is a mechanism by which an asset Issuer (IOUT or MPT, not XRP) freezes an `Account`, preventing that account from sending or receiving the Asset. Furthermore, an Issuer may enact a global freeze, which prevents everyone from sending or receiving the Asset. Note that in both single-account and global freezes, the Asset can be sent to the Issuer. + +If the Issuer freezes a Borrower's account, the Borrower cannot make loan payments or draw down funds. A frozen account does not lift the obligation to repay a Loan. +If a Loan Broker's account is frozen, the Broker will not receive any Loan fees. They will be able to create new loans, and existing loans will not be affected. However, the Loan Broker cannot deposit or withdraw First-Loss Capital. + +Finally, the exact behaviour has yet to be defined in a global freeze. **TBD** + +### 1.3 Risk Management + +Risk management involves mechanisms that mitigate the risks associated with lending. To protect investors' assets, we have introduced an optional first-loss capital protection scheme. This scheme requires the Loan Broker to deposit a fund that can be partially liquidated to cover losses in the event of a loan default. The amount of first-loss capital required is a percentage of the total debt owed to the Vault. In case of a default, a portion of the first-loss capital will be liquidated based on the minimum required cover. The liquidated capital is placed back into the Vault to cover some of the loss. + +### 1.4 Interest Rates + +There are three basic interest rates associated with a Loan: + +- **`Interest Rate`**: The regular interest rate based on the principal amount. It is the cost of borrowing funds. +- **`Late Interest Rate`**: A higher interest rate charged for a late payment. +- **`Full Payment Rate`**: An interest rate charged for repaying the total Loan early. + +### 1.5 Fees + +The lending protocol charges a number of fees that the Loan Broker can configure. The protocol will not charge the fees if the Loan Broker has not deposited enough First-Loss Capital. + +- **`Management Fee`**: This is a percentage of interest charged by the Loan Broker. Intuitively, the Vault depositors pay this fee. +- **`Loan Origination Fee`**: A nominal fee paid to the Loan Broker taken from the principal lent. +- **`Loan Service Fee`**: A nominal fee paid on top of each loan payment. +- **`Late Payment Fee`**: A nominal fee paid on top of a late payment. +- **`Early Payment Fee`**: A nominal fee paid on top of an early payment. + +### 1.6 Terminology + +#### 1.6.1 Terms + +- **`Fixed-Term Loan`**: A type of Loan with a known end date and a constant periodic payment schedule. +- **`Principal`**: The original sum of money borrowed that must be repaid, excluding interest or other fees. +- **`Interest`**: The cost of borrowing the Asset, calculated as a percentage of the loan principal, which the Borrower pays to the Lender over time. +- **`Drawdown`**: The process where a borrower accesses part or all of the loan funds after the Loan has been created. +- **`Default`**: The failure by the Borrower to meet the obligations of a loan, such as missing payments. +- **`First-Loss Capital`**: The portion of capital that absorbs initial losses in case of a Default, protecting the Vault from loss. +- **`Term`**: The period over which a Borrower must repay the Loan. +- **`Amortization`**: The gradual repayment of a loan through scheduled payments that cover both interest and principal over time. +- **`Repayment Schedule`**: A detailed plan that outlines when and how much a borrower must pay to repay the Loan fLoan. +- **`Grace Period`**: A set period after the Loan's due date after which the Loan Broker can default the Loan +- **`Origination Fee`**: A nominal one-time fee the loan broker charges for processing a new loan application. +- **`Service Fee`**: A recurring nominal charge the Borrower pays during Loan payment. +- **`Management Fee`**: A percentage fee charged by the Borrower on the loan interest before returning the interest to the Vault. +- **`Late Payment Fee`**: A penalty charged to the Borrower for failing to make a payment on or before its due date. +- **`Interest Rate`**: The percentage charged by the loan broker on the loan principal, representing the cost of borrowing. +- **`Late Interest Rate`**: A higher interest rate applied to overdue loan payments as a penalty for late repayment. +- **`Closing Interest Rate`**: The final interest rate charged when the Loan is closed or fully repaid. + +### 1.6.2 Actors + +- **`LoanBroker`**: The entity issuing the Loan. +- **`Borrower`**: The account that is borrowing funds. + +### 1.7 System Diagram + +``` ++-----------------+ +-----------------+ +-----------------+ +| Depositor | | LoanBroker | | Borrower | +| AccountRoot | | AccountRoot | | AccountRoot | +|-----------------| |-----------------| |-----------------| +| Owner Directory | | Owner Directory | | Owner Directory | ++-----------------+ +-----------------+ +-----------------+ + ^ | | | + | Reserve ____________Reserve____________ Reserve + Account | | | | + | V V V V ++-----------------+ +-----------------+ +-----------------+ +-----------------+ +| | | |1 N| |1 N| | +| MPToken | | Vault |--------->| LoanBroker |--------->| Loan | +| | | | |-----------------| | | ++-----------------+ +-----------------+ | Owner Directory | +-----------------+ + | ^ +-----------------+ ^ + Issuance | ___________ ____________^ |_________Link_________| + | | Account | + V ^ | ^ ++-----------------+ | +-----------------+ | +| Share | | | Pseudo-Account | | +| MPTokenIssuance |<------Issuer------| -----| AccountRoot | | +| | |_Link_|-----------------|_Link_| ++-----------------+ | Owner Directory | + +-----------------+ +``` + +[**Return to Index**](#index) + +## 2. Ledger Entries + +### 2.1. LoanBroker Ledger Entry + +The `LoanBroker` object captures attributes of the Lending Protocol. + +#### 2.1.1 Object Identifier + +The key of the `LoanBroker` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order: + +- The `LoanBroker` space key `0x006C` (Lower-case `l`). +- The `AccountID`(https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `LoanBrokerSet` transaction, i.e. `Lender`. +- The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value. + +#### 2.1.2 Fields + +The `LoanBroker` object has the following fields: + +| Field Name | Modifiable? | Required? | JSON Type | Internal Type | Default Value | Description | +| ---------------------- | :---------: | :----------------: | :-------: | :-----------: | :-----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `LedgerEntryType` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | +| `LedgerIndex` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | +| `Flags` | `Yes` | :heavy_check_mark: | `string` | `UINT32` | 0 | Ledger object flags. | +| `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | +| `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger containing the transaction that last modified this object. | +| `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the `LoanBroker`. | +| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `VaultID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | +| `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the Loan Broker. | +| `Data` | `Yes` | | `string` | `BLOB` | None | Arbitrary metadata about the `LoanBroker`. Limited to 256 bytes. | +| `ManagementFeeRate` | `No` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol. Valid values are between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001% | +| `OwnerCount` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | 0 | The number of active Loans issued by the `LoanBroker`. | +| `DebtTotal` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total asset amount the protocol owes the Vault, including interest. | +| `DebtMaximum` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | +| `CoverAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total amount of first-loss capital deposited into the Lending Protocol. | +| `CoverRateMinimum` | `No` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | +| `CoverRateLiquidation` | `No` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | + +#### 2.1.3 `LoanBroker `_pseudo-account_` + +The Lending Protocol uses the `_pseudo-account_` of the associated `Vault` object to hold the First-Loss Capital. + +#### 2.1.4 Ownership + +The lending protocol object is stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `LoanBrokerSet` transaction. Furthermore, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_. + +The `LoanBroker` requires tracking associated `Loan` objects to prevent the `LoanBroker` object from being deleted while loans are active as well as future RPC endpoints. Therefore, the `LoanBroker` has an associated [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) object. + +The `RootIndex` of the `DirectoryNode` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order: + +- The `OwnerDirectory` space key `0x004F` +- The `LoanBrokerID` + +#### 2.1.5 Reserves + +The `LoanBroker` object costs one owner reserve for the account creating it. + +#### 2.1.6 Accounting + +The Lending Protocol tracks the funds owed to the associated Vault in the `DebtTotal` attribute. It captures the principal amount taken from the Vault and the interest due, excluding all fees. The `DebtMaximum` attribute controls the maximum debt a Lending Protocol may incur. Whenever the Lender issues a Loan, `DebtTotal` is incremented by the Loan principal and interest, excluding fees. When $DebtTotal \geq DebtMaximum$, the Lender cannot issue new loans until some of the debt is cleared. Furthermore, the Lender may not issue a loan that would cause the `DebtTotal` to exceed `DebtMaximum`. + +**Example** + +``` +Example 1: # Issuing a Loan # + +** Initial States ** + +-- Vault -- +AssetsTotal = 100,000 Tokens +AssetsAvailable = 100,000 Tokens +SharesTotal = 100,000 Shares + +-- Lending Protocol -- +DebtTotal = 0 +# The fee charged by the Lending Protocol against any interest. +ManagementFeeRate = 0.1 (10%) + + +# The Lender issues the following Loan +-- Loan -- +LoanPrincipal = 1,000 Tokens +LoanInterestRate = 0.1 (10%) + +# SIMPLIfIED +LoanInterest = LoanPrincipal x LoanInterestRate + = 100 Tokens + +** State Changes ** + +-- Vault -- +# Increase the potential value of the Vault +AssetsTotal = AssetsTotal + ((LoanInterest - (LoanInterest x ManagementFeeRate))) + = 100,000 + (100 - (100 x 0.1)) = 100,000 + 90 + = 100,090 Tokens + +# Decrease Assets Available in the Vault +AssetsAvailable = AssetsAvailable - LoanPrincipal + = 100,000 - 1,000 + = 99,000 Tokens + +SharesTotal = (UNCHANGED) + +-- Lending Protocol -- +# Increase Lending Protocol Debt +DebtTotal = DebtTotal + LoanPrincipal + (LoanInterest - (LoanInterest x ManagementFeeRate)) + = 0 + 1,000 + (100 - (100 x 0.1)) = 1,000 + 90 + = 1,090 Tokens + + +--------------------------------------------------------------------------------------------------- + +Example 2: # Loan Payment # + +** Initial States ** + +-- Vault -- +AssetsTotal = 100,090 Tokens +AssetsAvailable = 99,000 Tokens +SharesTotal = 100,000 Shares + +-- Lending Protocol -- +DebtTotal = 1,090 Tokens +# The fee charged by the Lending Protocol against any interest. +ManagementFeeRate = 0.1 (10%) + +-- Loan -- +LoanPrincipal = 1,000 Tokens +LoanInterestRate = 0.1 (10%) +# SIMPLIfIED +LoanPayments = 2 + +# SIMPLIfIED +LoanInterest = LoanPrincipal x LoanInterestRate + = 100 Tokens + + +# The Borrower makes a single payment + +PaymentAmount = 550 Tokens +PaymentPrincipalPortion = 500 Tokens +PaymentInterestPortion = 50 Tokens + + +** State Changes ** + +-- Vault -- +AssetsTotal = (UNCHANGED) + +# Increase Assets Available in the Vault +AssetsAvailable = AssetsAvailable + PaymentPrincipalPortion + (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) + = 99,000 + 500 + (50 - (50 x 0.1)) + = 99,545 Tokens + +SharesTotal = (UNCHANGED) + +-- Lending Protocol -- + +# Decrease Lending Protocol Debt +DebtTotal = DebtTotal - PaymentPrincipalAmount - (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) + = 1,090 - 500 - (50 - (50 x 0.1)) + = 545 Tokens + +``` + +#### 2.1.7 First-Loss Capital + +The First-Loss Capital is an optional mechanism to protect the Vault depositors from incurring a loss in case of a Loan default. The first loss of capital absorbs some of the loss. The following parameters control the First-Loss Capital: + +- `CoverAvailable` - the total amount of cover deposited by the Lending Protocol Owner. +- `CoverRateMinimum` - the percentage of `DebtTotal` that must be covered by the `CoverAvailable`. +- `CoverRateLiquidation` - the maximum percentage of the minimum required cover ($DebtTotal \times CoverRateMinimum$) that will be liquidated to cover a Loan Default. + +Whenever the available cover falls below the minimum cover required, two consequences occur: + +- The Lender cannot issue new Loans. +- The Lender cannot receive fees. Borrower fees are ignored (i.e. the Borrower does not have to pay a Loan payment fee), and the Management Fee is instead deposited into a Vault. + +**Examples** + +``` +Example 1: Loan Default + +** Initial States ** + +-- Vault -- +AssetsTotal = 100,090 Tokens +AssetsAvailable = 99,000 Tokens +SharesTotal = 100,000 Tokens + +-- Lending Protocol -- +DebtTotal = 1,090 Tokens +CoverRateMinimum = 0.1 (10%) +CoverRateLiquidation = 0.1 (10%) +CoverAvailable = 1,000 Tokens + +-- Loan -- +DefaultAmount = 1,090 Tokens + + +# First-Loss Capital liquidation maths + +# The amount of the default that the first-loss capital scheme will cover +DefaultCovered = min((DebtTotal x CoverRateMinimum) x CoverRateLiquidation, DefaultAmount) + = min((1,090 * 0.1) * 0.1, 1,090) = min(10.9, 1,090) + = 10.9 Tokens + +DefaultRemaining = DefaultAmount - DefaultCovered + = 1,090 - 10.9 + = 1,079.1 Tokens + +** State Changes ** + +-- Vault -- +AssetsTotal = AssetsTotal - DefaultRemaining + = 100,090 - 1,079.1 + = 99,010.9 Tokens + +AssetsAvailable = AssetsAvailable + DefaultCovered + = 99,000 + 10.9 + = 99,010.9 Tokens + +SharesTotal = (UNCHANGED) + +-- Lending Protocol -- +DebtTotal = DebtTotal - DefaultAmount + = 1,090 - 1,090 + = 0 Tokens + +CoverAvailable = CoverAvailable - DefaultCovered + = 1,000 - 10.9 + = 989.1 Tokens +``` + +[**Return to Index**](#index) + +### 2.2. `Loan` Ledger Entry + +A Loan ledger entry captures various Loan terms on-chain. It is an agreement between the Borrower and the loan issuer. + +#### 2.2.1 Object Identifier + +The `LoanID` is calculated as follows: + +- Calculate [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values: + - The `Loan` space key `0x004C` (capital L) + - The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the Borrower account. + - The `LoanBrokerID` of the associated `LoanBroker` object. + - The `Sequence` number of the **`LoanSet`** transaction. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value. + +#### 2.2.2 Fields + +| Field Name | Modifiable? | Required? | JSON Type | Internal Type | Default Value | Description | +| ------------------------- | :---------: | :----------------: | :-------: | :-----------: | :-------------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `LedgerEntryType` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | +| `LedgerIndex` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | +| `Flags` | `Yes` | | `string` | `UINT32` | 0 | Ledger object flags. | +| `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | +| `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | +| `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | +| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `LoanBrokerID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | +| `Borrower` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | +| `LoanOriginationFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanServiceFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | +| `LatePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | +| `ClosePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | +| `OveraymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `InterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | +| `LateInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `CloseInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `StartDate` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentInterval` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Number of seconds between Loan payments. | +| `GracePeriod` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | +| `PreviousPaymentDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `NextPaymentDueDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentsRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentsTotal` | The number of payments remaining on the Loan. | +| `AssetsAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `PrincipalOutstanding` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | + +##### 2.2.2.1 Flags + +The `Loan` object supports the following flags: + +| Flag Name | Flag Value | Modifiable? | Description | +| -------------------- | :--------: | :---------: | :----------------------------------------------------: | +| `lsfLoanDefault` | `0x0001` | `No` | If set, indicates that the Loan is defaulted. | +| `lsfLoanImpaired` | `0x0002` | `Yes` | If set, indicates that the Loan is impaired. | +| `lsfLoanOverpayment` | `0x0003` | `No` | If set, indicates that the Loan supports overpayments. | + +#### 2.2.3 Ownership + +The `Loan` objects are stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the `Borrower`. + +Furthermore, to facilitate the `Loan` object lookup from the `LoanBroker`, the object is also tracked in the `OwnerDirectory` associated with the `LoanBroker` object. + +#### 2.2.4 Reserves + +The `Loan` object costs one owner reserve for the `Borrower`. + +#### 2.2.5 Impairment + +When the Loan Broker discovers that the Borower cannot make an upcoming payment, impairment allows the Loan Broker to register a "paper loss" with the Vault. The impairment mechanism moves the Next Payment Due Date to the time the Loan was impaired, allowing to default the Loan more quickly. However, if the Borrower makes a payment, the impairment status is automatically cleared. + +[**Return to Index**](#index) + +## 3. Transactions + +### 3.1. `LoanBroker` Transactions + +In this section we specify the transactions associated with the `LoanBroker` ledger entry. + +#### 3.1.1 `LoanBrokerSet` + +The transaction creates a new `LoanBroker` object or updates an existing one. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ---------------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Vault ID that the Lending Protocol will use to access liquidity. | +| `LoanBrokerID` | | `string` | `HASH256` | `N/A` | The Loan Broker ID that the transaction is modifying. | +| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Lending Protocol. | +| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | +| `ManagementFeeRate` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol Owner. Valid values are between 0 and 10000 inclusive. | +| `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | +| `CoverRateMinimum` | | `number` | `UINT16` | 0 | The 1/10th basis point `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. | +| `CoverRateLiquidation` | | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. | + +##### 3.1.1.1 Failure Conditions + +- If `LoanBrokerID` is not specified: + + - `Vault` object with the specified `VaultID` does not exist on the ledger. + - The submitter `AccountRoot.Account != Vault(VaultID).Owner`. + +- If `LoanBrokerID` is specified: + + - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. + - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. + - The submitter is attempting to modify fixed fields. + +- Any of the fields are _invalid_. + +##### 3.1.1.2 State Changes + +- If `LoanBrokerID` is not specified: + + - Add `LoanBrokerID` to the `OwnerDirectory` of the submitting account. + - Add `LoanBrokerID` to the `OwnerDirectory` of the Vault's `_pseudo-account_`. + +- If `LoanBrokerID` is specified: + - Update appropriate fields. + +##### 3.1.1.3 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.1.2 `LoanBrokerDelete` + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanBrokerID` | | `string` | `HASH256` | `N/A` | The Loan Broker ID that the transaction is deleting. | + +##### 3.1.2.1 Failure Conditions + +- `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. +- The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. + +- The `OwnerCount` field is greater than zero. +- `CoverAvailable` is greater than zero. + +##### 3.1.2.2 State Changes + +- Delete `LoanBrokerID` from the `OwnerDirectory` of the submitting account. +- Delete `LoanBrokerID` from the `OwnerDirectory` of the Vault's `_pseudo-account_`. +- Delete the `OwnerDirectory` associated with the `LoanBroker` object. + +##### 3.1.2.3 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.1.3 `LoanBrokerCoverDeposit` + +The transaction creates a new `LoanBroker` object or updates an existing one. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------ | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID to deposit First-Loss Capital. | +| `Amount` | :heavy_check_mark: | `object` | `NUMBER` | 0 | The Fist-Loss Capital amount to deposit. | + +##### 3.1.3.1 Failure Conditions + +- `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. +- The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + - The trustline `Balance` < `Amount`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: + - Has `lsfMPTLocked` flag set. + - `MPTAmount` < `Amount`. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + +##### 3.1.3.2 State Changes + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + + - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Decrease the `Balance` field of the submitter `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Decrease the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Decrease the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. + +- Increase `LoanBroker.CoverAvailable` by `Amount`. + +##### 3.1.3.3 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.1.4 `LoanBrokerCoverWithdraw` + +The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from the `LoanBroker`. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------ | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. | +| `Amount` | :heavy_check_mark: | `object` | `NUMBER` | 0 | The amount of Vault asset to withdraw. | + +##### 3.2.2.1 Failure conditions + +- `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. +- The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: + - Has `lsfMPTLocked` flag set. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + +- The submitter is attempting to withdraw an Asset that does not match the Asset of the Vault. + +- The `LoanBroker.CoverAvailable` < `Amount`. + +##### 3.2.2.2 State Changes + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Decrease the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. + +- Decrease `LoanBroker.CoverAvailable` by `Amount`. + +[**Return to Index**](#index) + +### 3.2. `Loan` Transactions + +In this section we specify transactions associated with the `Loan` ledger entry. + +#### 3.2.1 `LoanSet` Transaction + +The transaction creates a new `Loan` object. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| -------------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID associated with the loan. | +| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | +| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | +| `Borrower` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | +| `LoanOriginationFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | +| `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | +| `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | +| `InterestRate` | | `number` | `UINT16` | 0 | Annualized interest rate of the Loan in basis points. | +| `LateInterestRate` | | `number` | `UINT16` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 10000 inclusive. (0 - 100%) | +| `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | +| `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentsTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | +| `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | +| `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | +| `Lender` | :heavy_check_mark: | `object` | `STObject` | `N/A` | An inner object that contains the signature of the Lender over the transaction. | + +##### 3.2.1.1 `Flags` + +| Flag Name | Flag Value | Description | +| ------------------- | :--------: | :---------------------------------------------- | +| `tfLoanOverpayment` | `0x0001` | Indicates that the vault supports overpayments. | + +##### 3.2.1.2 `Lender` + +An inner object that contains the signature of the Lender over the transaction. The fields contained in this object are: + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| --------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------------------------------------------------------------------------------------- | +| `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | +| `Signature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields, including the `Signature` of the Borrower. | +| `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `LoanBroker.Owner` signers to indicate their approval of this transaction. | + +The final transaction must include `Signature` or `Signers`. + +If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base\_fee$ + +The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base\_fee$. + +This field is not a signing field (it will not be included in transaction signatures, though the `Signature` or `Signers` field will be included in the stored transaction). + +##### 3.2.1.3 Multi-Signing + +The `LoanSet` transaction is a mutual agreement between the `Borrower` and the `LoanBroke.Owner` to create a Loan. Therefore, the `LoanSet` transaction must be signed by both parties. The multi-signature flow is as follows: + +1. The `Borrower` creates a new transaction with the pre-agreed terms of the Loan and signs the transaction. +2. The `Lender` signs over all signing fields, including the signature of the `Borrower`. + +##### 3.2.1.4 Failure Conditions + +- `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. +- The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. +- `Lender. Signature` is invalid. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: + - Has `lsfMPTLocked` flag set. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + +- Either of the `tfDefault`, `tfImpair` or `tfUnimpair` flags are set. + +- The `Borrower` `AccountRoot` object does not exist. + +- `PaymentInterval` is less than `60` seconds. +- `GracePeriod` is greater than the `PaymentInterval`. +- `Loan.StartDate < CurrentTime`. + +- Insufficient assets in the Vault: + + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable` < `Loan.PrincipalRequested`. + +- Exceeds maximum Debt of the LoanBroker: + + - `LoanBroker(LoanBrokerID).DebtMaximum` < `LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested` + +- Insufficient First-Loss Capital: + - `LoanBroker(LoanBrokerID).CoverAvailable` < `(LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested) x LoanBroker(LoanBrokerID).CoverRateMinimum` + +##### 3.2.1.5 State Changes + +- If the Loan Asset is an `IOU`: + + - Create a `Trustline` between the `Issuer` and the `Borrower` if one does not exist. + +- If the Loan Asset is an `MPT`: + + - Create an `MPToken` object for the `Borrower` if one does not exist. + +- `Vault(LoanBroker(LoanBrokerID).VaultID)` object state changes: + + - Decrease Assets Available in the Vault: + + - `Vault.AssetsAvailable -= Loan.PrincipalRequested`. + + - Increase the Total Value of the Vault: + - `Vault.AssetsTotal += LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` where `LoanInterest` is the Loan's total interest. + +- `LoanBroker(LoanBrokerID)` object changes: + + - `LoanBroker.DebtTotal += Loan.PrincipalRequested + (LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` + - `LoanBroker.OwnerCount += 1` + + - If the `DirectoryNode` for the `LoanBroker` does not exist, create one. + - Add `LoanID` to `DirectoryNode.Indexes`. + +##### 3.2.1.4 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.2.2 `LoanDelete` Transaction + +The transaction deletes an existing `Loan` object. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be deleted. | + +##### 3.2.2.1 Failure Conditions + +- A `Loan` object with the specified `LoanID` does not exist on the ledger. +- The Account submitting the `LoanDelete` is not the `LoanBroker.Owner` or the `Loan.Borrower`. +- The Loan is active: + - `Loan.PaymentsRemaining > 0` + +##### 3.2.2.2 State Changes + +- Remove `LoanID` from the Owner Directory associated with the `LoanBroker`. +- `LoanBroker.OwnerCount -= 1` +- Delete the `Loan` object. +- Release reserve funds back to the Borrower. + +[**Return to Index**](#index) + +#### 3.2.3 `LoanManage` Transaction + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be updated. | +| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | + +##### 3.2.3.1 `Flags` + +| Flag Name | Flag Value | Description | +| ---------------- | :--------: | :--------------------------------------------- | +| `tfLoanDefault` | `0x0001` | Indicates that the Loan should be defaulted. | +| `tfLoanImpair` | `0x0002` | Indicates that the Loan should be impaired. | +| `tfLoanUnimpair` | `0x0003` | Indicates that the Loan should be un-impaired. | + +##### 3.2.3.1 Failure Conditions + +- A `Loan` object with the specified `LoanID` does not exist on the ledger. +- The `Account` submitting the transaction is not the `LoanBroker.Owner`. +- The `lsfLoanDefault` flag is set on the Loan object. Once a Loan is defaulted, it cannot be modified. + +- If `Loan(LoanID).Flags == lsfLoanImpaired` AND `tfLoanImpair` flag is provided. + +- `Loan.PaymentsRemaining == 0`. + +- The `tfDefault` flag is specified and: + - `CurrentTime` < `Loan.NextPaymentDueDate + Loan.GracePeriod`. + +##### 3.2.3.2 State Changes + +- If the `tfDefault` flag is specified: + + - Calculate the amount of the Default that First-Loss Capital covers: + + - The default Amount equals the outstanding principal and interest, excluding any funds unclaimed by the Borrower. + - `DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - Loan.AssetsAvailable`. + - Apply the First-Loss Capital to the Default Amount + - `DefaultCovered = min((LoanBroker(Loan.LoanBrokerID).DebtTotal x LoanBroker(Loan.LoanBrokerID).CoverRateMinimum) x LoanBroker(Loan.LoanBrokerID).CoverRateLiquidation, DefaultAmount)` + - `DefaultAmount -= DefaultCovered` + + - Update the `Vault` object: + + - Decrease the Total Value of the Vault: + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsTotal -= DefaultAmount`. + - Increase the Assets Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += DefaultCovered + Loan.AssetsAvailable`. + +- Update the `LoanBroker` object: + + - Decrease the Debt of the LoanBroker: + - `LoanBroker(LoanBrokerID).DebtTotal -= ` + - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetsAvailable` + - Decrease the First-Loss Capital Cover Available: + - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` + - Decrease the number of active Loans: + - `LoanBroker(LoanBrokerID).OwnerCount -= 1` + +- Update the `Loan` object: + + - `Loan(LoanID).Flags = lsfLoanDefault` + - `Loan(LoanID).PaymentsRemaining = 0` + - `Loan(LoanID).AssetsAvailable = 0` + - `Loan(LoanID).PrincipalOutstanding = 0` + +- If `tfLoanImpair` flag is specified: + + - Update the `Vault` object (set "paper loss"): + + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1. Payment Types**](#3251-payment-types), which outlines how to calculate total interest outstanding) + + - Update the `Loan` object: + - `Loan(LoanID).Flags = lsfLoanImpaired` + - If `currentTime < Loan(LoanID).NextPaymentDueDate` (if the loan payment is not yet late): + - `Loan(LoanID).NextPaymentDueDate = currentTime` (move the next payment due date to now) + +- If the `tfLoanUnimpair` flag is specified: + + - Update the `Vault` object (clear "paper loss"): + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1. Payment Types**](#3251-payment-types), which outlines how to calculate total interest outstanding) + + - Update the `Loan` object: + + - `Loan(LoanID).Flags = 0` + - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval > currentTime` (the loan was unimpaired within the payment interval): + + - `Loan(LoanID).NextPaymentDueDate = Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval` + + - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval < currentTime` (the loan was unimpaired after the original payment due date): + - `Loan(LoanID).NextPaymentDueDate = currentTime + Loan(LoanID).PaymentInterval` + +##### 3.2.3.3 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.2.4 `LoanDraw` Transaction + +The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------ | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be drawn from. | +| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to drawdown. | + +##### 3.2.4.1 Failure Conditions + +- A `Loan` object with the specified `LoanID` does not exist on the ledger. +- The `AccountRoot.Account` of the submitter is not `Loan.Borrower`. +- The Loan has not started: + - `Loan.StartDate > CurrentTime`. +- There are insufficient assets: + + - `Loan.AssetsAvailable` < `Amount`. + +- The `Loan` has `lsfLoanImpaired` or `lsfLoanDefault` flags set. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: + - Has `lsfMPTLocked` flag set. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + +- The `Borrower` missed a payment: + - `CurrentTime > Loan.NextPaymentDueDate`. + +##### 3.2.4.2 State Changes + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Decrease the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. + +- Decrease `Loan.AssetsAvailable` by `Amount`. + +##### 3.2.4.3 Invariants + +**TBD** + +[**Return to Index**](#index) + +#### 3.2.5 `LoanPay` Transaction + +The Borrower submits a `LoanPay` transaction to make a Payment on the Loan. + +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------ | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be drawn from. | +| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to pay. | + +##### 3.2.5.1 Payment Types + +A Loan payment has four types: + +- Regular payment is made on time, where the payment size and schedule are calculated with a standard amortization [formula](https://en.wikipedia.org/wiki/Amortization_calculator). + +- A _late_ payment, when a Borrower makes a payment after `netxPaymentDueDate`. Late payments include a `LatePaymentFee` and `LateInterestRate`. + +- An early _full_ payment is when a Borrower pays the outstanding principal. A `CloseInterestRate` is charged on the outstanding principal. + +- An overpayment occurs when a borrower makes a payment that exceeds the required minimum payment amount. + +The payment amount and timing determine the type of payment. A payment made before the `Loan.NextPaymentDueDate` is a regular payment and follows the standard amortization calculation. Any payment made after this date is considered a late payment. + +The following diagram depicts how a payment is handled based on the amount paid. + +``` + Rejected Overpayment Overpayment Overpayment Not charged +|------------|---------------|---------------|---------------|-------------| + Periodic/Late Periodic Periodic Full + Payment Amount Payment Amount Payment Amount Payment Amount + I II N - 1 + + Payment Amount +``` + +The minimum payment required is determined by whether the borrower makes the payment before or on the `NextPaymentDueDate` or if it is late. Any payment below the minimum amount required is rejected. With a single `LoanPay` transaction, the Borrower can make multiple loan payments. For example, if the periodic payment amount is 400 Tokens and the Borrower makes a payment of 900 Tokens, the payment will be treated as two periodic payments, moving the NextPaymentDueDate forward by two payment intervals, and the remaining 100 Tokens will be an overpayment. + +If the Loan Broker and the borrower have agreed to allow overpayments, any amount above the periodic payment is treated as an overpayment. However, if overpayments are not supported, the excess amount will not be charged and will remain with the borrower. + +Each payment comprises three parts, `principal`, `interest` and `fee`. The `principal` is an amount paid against the principal of the Loan, `interest` is the interest portion of the Loan, and `fee` is the fee part paid by the Borrower on top of `principal` and `interest`. + +###### 3.2.5.1.1 Regular Payment + +A periodic payment amount is calculated using the amortization payment formula: + +$$ +totalDue = periodicPayment + loanServiceFee +$$ + +$$ +periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentsRemaining}}{(1 + periodicRate)^{PaymentsRemaining} - 1} +$$ + +where the periodic interest rate is the interest rate charged per payment period: + +$$ +periodicRate = \frac{interestRate \times paymentInterval}{365 \times 24 \times 60 \times 60} +$$ + +The `principal` and `interest` portions can be derived as follows: + +$$ +interest = principalOutstanding \times periodicRate +$$ + +$$ +principal = periodicPayment - interest +$$ + +###### 3.2.5.1.2 Late Payment + +When a Borrower makes a payment after `NextPaymentDueDate`, they must pay a nominal late payment fee and an additional interest rate charged on the overdue amount for the unpaid period. The formula is as follows: + +$$ +totalDue = periodicPayment + latePaymentFee + latePaymentInterest +$$ + +A special, late payment interest rate is applied for the over-due period: + +$$ +latePaymentInterest = principalOutstanding \times \frac{lateInterestRate \times secondsSinceLastPayment}{365 \times 24 \times 60 \times 60} +$$ + +A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetsTotal` must be recalculate +d. +Assume the function `PeriodicPayment()` returns the expected periodic payment, split into `principalPeriodic` and `interestPeriodic`. Furthermore, assume the function `LatePayment()` that implements the Late Payment formula. The function returns the late payment split into `principalLate` and `interestLate`, where `interestLate` is calculated using the formula above. Note that `principalPeriodic == principalLate` and `interestLate > interestPeriodic` are used only when the payment is late. Otherwise, `interestLate == interestPeriodic`. + +$$ +valueChange = interestLate - interestPeriodic +$$ + +Note that `valueChange >= 0`. + +###### 3.2.5.1.3 Loan Overpayment + +- Let $\mathcal{P}$ and $\mathcal{p}$ represent the total and outstanding Loan principal. +- Let $\mathcal{I}$ and $\mathcal{i}$ represent the total and outstanding Loan interest computed from $\mathcal{P}$ and $\mathcal{p}$ respectively. + +$$ +excess = min(\mathcal{p}, paymentAmountMade - minimumPaymentAmount) +$$ + +$$ +interestPortion = excess \times overpaymentInterestRate +$$ + +$$ +feePortion = excess \times overpaymentFee +$$ + +$$ +principalPortion = excess - interestPortion - feePortion +$$ + +$$ +\mathcal{p'} = \mathcal{p} - principalPortion +$$ + +Let $\mathcal{i}$ denote the outstanding interest computed from $\mathcal{p}$. Simillarly, let $\mathcal{i'}$ denote the outstanding interest computed from $\mathcal{p'}$. We compute the loan interest change as follows: + +$$ +valueChange = \mathcal{i} - \mathcal{i'} +$$ + +###### 3.2.5.1.4 Early Full Repayment + +A Borrower can close a Loan early by submitting the total amount needed to do so. This amount is the sum of the remaining balance, any accrued interest, a prepayment penalty, and a prepayment fee. + +$$ +totalDue = principalOutstanding + accruedInterest + prepaymentPenalty + ClosePaymentFee +$$ + +Accrued interest up to the point of early closure is calculated as follows: + +$$ +accruedInterest = principalOutstanding \times periodicRate \times \frac{secondsSinceLastPayment}{paymentInterval} +$$ + +Finally, the Lender may charge a prepayment penalty for paying a loan early, which is calculated as follows: + +$$ +prepaymentPenalty = principalOutstanding \times closeInterestRate +$$ + +An early payment pays less interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the Vault value (captured by `Vault.AssetsTotal`) must be recalculated after an early payment. + +Assume a function `CurrentValue()` that returns `principalOutstanding` and `interestOutstanding` of the Loan. Furthermore, assume a function `ClosePayment()` that implements the Full Payment calculation. The function returns the total full payment due split into `principal` and `interest`. + +The value change for an early full repayment is calculated as follows: + +$$ +valueChange = (prepaymentPenalty) - (interestOutstanding - accruedInterest) +$$ + +Note that `valueChange <= 0` as an early repayment reduces the total value of the Loan. + +###### 3.2.5.1.5 Management Fee Calculations + +The `LoanBroker` Management fee is charged against the interest portion of the Loan and subtracted from the total Loan value at Loan creation. However, the fee is charged only during Loan payments. Early and Late payments change the total value of the Loan by decreasing or increasing the value of total interest. Therefore, when an early, late or an overpayment payment is made, the management fee must be updated. + +To update the management fee, we need to compute the new total management fee based on the new total interest after executing the early or late payment. Therefore, we need to capture the Loan value before the payment is made and the new value after the payment is made. + +For the calculation, assume the following variables: + +- Let $\mathcal{P}$ and $\mathcal{p}$ represent the total and outstanding Loan principal. +- Let $\mathcal{I}$ and $\mathcal{i}$ represent the total and outstanding Loan interest computed from $\mathcal{P}$ and $\mathcal{p}$ respectively. +- Let $\mathcal{V}$ and $\mathcal{v}$ represent the total and outstanding value of the Loan. $\mathcal{V} = \mathcal{P} + \mathcal{I}$ and $\mathcal{v} = \mathcal{p} + \mathcal{i}$. +- Finally, let $\mathcal{m}$ represent the management fee rate of the Loan Broker. + +Assume $f(\mathcal{v})$ is a Loan payment, $f(\mathcal{v}) = \mathcal{v'}$, the new outstanding loan value is equal to the application of the payment transaction to the current outstanding value. Furthermore, assume $\mathcal{V} \xrightarrow{f(\mathcal{v})} \mathcal{V'}$, is the change in the Loan total value as the result of applying $f(\mathcal{v})$. + +we say that $\mathcal{V'} = \mathcal{P'} + \mathcal{I'}$. It's important to note that a payment transaction must never change the total principal. I.e. $\mathcal{P} = \mathcal{P'}$, the change in total value is caused by the change in total principal only. + +$\Delta_{\mathcal{V}} = \mathcal{I'} - \mathcal{I}$ is the total value change of the Loan. When $\Delta_{\mathcal{V}} > 0$ the total value of the Loan increased, when $\Delta_{\mathcal{V}} < 0$ the total value decreased, and if $\Delta_{\mathcal{V}} = 0$ the value remained the same. + +The total management fee is calculated as follows: + +$$ +managementFeeTotal = \mathcal{I} \times \mathcal{m} +$$ + +We compute the management fee paid so far as follows: + +$$ +managementFeePaid = (\mathcal{I} - \mathcal{i}) \times \mathcal{m} +$$ + +$$ +managementFeeDue = managementFeeTotal - managementFeePaid +$$ + +Finally, we compute the change in management fee as follows: + +$$ +managementFeeChange = \mathcal{i'} \times \mathcal{m} - managementFeeDue +$$ + +The above calculation can be simplified to: + +$$ +managementFeeChange = \Delta_{\mathcal{V}} \times \mathcal{m} +$$ + +When the management fee change is negative, the Loan's value decreases, and thus, the Loan Broker's debt decreases. +Intuitively, a negative fee change suggests that the fee must be returned, increasing the loan broker's debt. + +In contrast, if the management fee change is positive, the Loan's value increases, and a further fee must be deducted from the debt. +Intuitively, a positive fee change suggests that an additional fee must be paid due to the increase in the interest paid. + +The LoanBroker debt is then updated as: + +$$ +LoanBroker.DebtTotal = LoanBroker.DebtTotal - managementFeeChange +$$ + +##### 3.2.5.2 Transaction Pseudo-code + +The following is the pseudo-code for handling a Loan payment transaction. + +``` +function make_payment(amount, current_time) -> (principal_paid, interest_paid, value_change, fee_paid): + if loan.payments_remaining is 0 || loan.principal_outstanding is 0 { + return "loan complete" error + } + + // the payment is late + if loan.next_payment_due_date < current_time { + let late_payment = loan.compute_late_payment(current_time) + if amount < late_payment { + return "insufficient amount paid" error + } + + loan.payments_remaining -= 1 + loan.principal_outstanding -= late_payment.principal + + loan.last_payment_date = loan.next_payment_due_date + loan.next_payment_due_date = loan.next_payment_due_date + loan.payment_interval + + let periodic_payment = loan.compute_periodic_payment() + + // A late payment increases the value of the loan by the difference between periodic and late payment interest + return (late_payment.principal, late_payment.interest, late_payment.interest - periodic_payment.interest, loan.late_payment_fee) + } + + let full_payment = loan.compute_full_payment(current_time) + + // if the payment is equal or higher than full payment amount + // and there is more than one payment remaining, make a full payment + if amount >= full_payment && loan.payments_remaining > 1 { + loan.payments_remaining = 0 + loan.principal_outstanding = 0 + + // A full payment decreases the value of the loan by the difference between the interest paid and the expected outstanding interest + return (full_payment.principal, full_payment.interest, full_payment.interest - loan.compute_current_value().interest, full_payment.fee) + } + + // if the payment is not late nor if it's a full payment, then it must be a periodic once + + let periodic_payment = loan.compute_periodic_payment() + + let full_periodic_payments = floor(amount / periodic_payment) + if full_periodic_payments < 1 { + return "insufficient amount paid" error + } + + loan.payments_remaining -= full_periodic_payments + loan.next_payment_due_date = loan.next_payment_due_date + loan.payment_interval * full_periodic_payments + loan.last_payment_date = loan.next_payment_due_date - loan.payment_interval + + + let total_principal_paid = 0 + let total_interest_paid = 0 + let loan_value_change = 0 + let total_fee_paid = loan.service_fee * full_periodic_payments + + while full_periodic_payments > 0 { + total_principal_paid += periodic_payment.principal + total_interest_paid += periodic_payment.interest + periodic_payment = loan.compute_periodic_payment() + full_periodic_payments -= 1 + } + + loan.principal_outstanding -= total_principal_paid + + let overpayment = min(loan.principal_outstanding, amount % periodic_payment) + if overpayment > 0 && is_set(lsfOverayment) { + let interest_portion = overpayment * loan.overpayment_interest_rate + let fee_portion = overpayment * loan.overpayment_fee + let remainder = overpayment - interest_portion - fee_portion + + total_principal_paid += remainder + total_interest_paid += interest_portion + total_fee_paid += fee_portion + + let current_value = loan.compute_current_value() + loan.principal_outstanding -= remainder + let new_value = loan.compute_current_value() + + loan_value_change = (new_value.interest - current_value.interest) + interest_portion + } + + return (total_principal_paid, total_interest_paid, loan_value_change, total_fee_paid) +``` + +##### 3.2.5.3 Failure Conditions + +- A `Loan` object with specified `LoanID` does not exist on the ledger. + +- The Loan has not started yet: `Loan.StartDate > CurrentTime`. + +- The submitter `AccountRoot.Account` is not equal to `Loan.Borrower`. + +- `Loan.PaymentsRemaining` or `Loan.PrincipalOutstanding` is `0`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: + - Has `lsfMPTLocked` flag set. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + +- If `CurrentTime > Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` + +- If `CurrentTime <= Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` + +##### 3.2.5.4 State Changes + +Assume the payment is split into `principal`, `interest` and `fee`, and `totalDue = principal + interest + fee`. + +Assume the payment is handled by a function that implements the [Pseudo-Code](#3252-transaction-pseudo-code) that returns `principal_paid`, `interest_paid`, `value_change` and `fee_paid`, where: + +- `principal_paid` is the amount of principal that the payment covered. +- `interest_paid` is the amount of interest that the payment covered. +- `fee_paid` is the amount of fee that the payment covered. +- `value_change` is the amount by which the total value of the Loan changed. + - If `value_change` < `0`, Loan value decreased. + - If `value_change` > `0`, Loan value increased. + +Furthermore, assume `full_periodic_payments` variable represents the number of payment intervals that the payment covered. + +- `Loan` object state changes: + + - If `Loan(LoanID).Flags == lsfLoanImpaired`: + + - `Loan(LoanID).Flags = 0` + + - Decrease `Loan.PaymentsRemaining` by `full_periodic_payments`. + - Decrease `Loan.PrincipalOutstanding` by `principal_paid`. + + - If `Loan.PaymentsRemaining > 0` and `LoanPrincipalOutstanding > 0`: + + - Set the next payment date: `Loan.NextPaymentDueDate += Loan.PaymentInterval * full_periodic_payments`. + - Set the previous payment date: `Loan.PreviousPaymentDate = Loan.NextPaymentDueDate - Loan.PaymentInterval`. + +- `LoanBroker(Loan.LoanBrokerID)` object state changes: + + - Compute the management fee: + + - `feeManagement = interest_paid x LoanBroker.ManagementFeeRate` + + - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Do not charge the management, add it to the total debt: + - `LoanBroker.DebtTotal += feeManagement` + + - If there is **enough** first-loss capital: `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Decrease the management fee from totalPaid amount: + - `totalPaid = totalPaid - feeManagement` + + - Decrease LoanBroker Debt by the amount paid: + + - `LoanBroker.DebtTotal -= totalPaid` + + - Update the LoanBroker Debt by the Loan value change: + + - `LoanBroker.DebtTotal += valueChange` + + - Update the LoanBroker Debt by the change in the management fee: + - `LoanBroker.DebtTotal -= (valueChange x LoanBroker.ManagementFeeRate)` + - If `LoanPaymentsRemaining == 0` and `LoanPrincipalOutstanding == 0`: + - Decrease active loans: + - `LoanBroker.OwnerCount = LoanBroker.OwnerCount - 1` + +- `Vault(LoanBroker(Loan.LoanBrokerID).VaultID)` state changes: + + - Increase available assets in the Vault by the amount paid: + + - `Vault.AssetsAvailable = Vault.AssetsAvailable + totalPaid` + + - Update the Vault total value by the change in the Loan total value: + + - `Vault.AssetsTotal = Vault.AssetsTotal + valueChange` + + - Update the Vault total value by the change in the management fee: + + - `Vault.AssetsTotal = Vault.AssetsTotal - (vaultChange x LoanBroker.managementFeeRate)` + + - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + - The management fee was not charged; decrease Vault TotalValue: + - `Loan.AssetsTotal = Loan.AssetsTotal + feeManagement` + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + + - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Increase the `Balance` field of the `LoanBroker.Owner` `AccountRoot` by `fee_paid + management_fee`. + + - Decrease the `Balance` field of the submitter `AccountRoot` by `principal_paid + interest_paid + fee_paid`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum` : + + - Increase the `RippleState` balance between the `LoanBroker.Owner` `AccountRoot` and the `Issuer` `AccountRoot` by `fee_paid + management_fee`. + + - Decrease the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + interest_paid + fee_paid`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Increase the `MPToken.MPTAmount` by `principal_paid + (interest_paid - management_fee)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Increase the `MPToken.MPTAmount` by `fee_paid + management_fee` of the `LoanBroker.Owner` `MPToken` object for the `Vault.Asset`. + + - Decrease the `MPToken.MPTAmount` by `principal_paid + interest_paid + fee_paid` of the submitter `MPToken` object for the `Vault.Asset`. + +[**Return to Index**](#index) + +##### 3.2.5.4 Invariants + +**TBD** + +# Appendix From d3e61159c4bb94b29528c44f9b316ef301687604 Mon Sep 17 00:00:00 2001 From: Vito Date: Tue, 19 Nov 2024 16:10:29 +0000 Subject: [PATCH 02/41] improves the description of the management fee --- XLS-0066d-lending-protocol/README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 95bad938..0f037e13 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -124,7 +124,7 @@ There are three basic interest rates associated with a Loan: The lending protocol charges a number of fees that the Loan Broker can configure. The protocol will not charge the fees if the Loan Broker has not deposited enough First-Loss Capital. -- **`Management Fee`**: This is a percentage of interest charged by the Loan Broker. Intuitively, the Vault depositors pay this fee. +- **`Management Fee`**: This is a fee charged by the Loan Broker, calculated as a percentage of the interest earned on loans. It's deducted from the interest that would otherwise go to the Vault depositors. Essentially, borrowers pay the full interest, but before that interest reaches depositors, the Loan Broker takes their cut. - **`Loan Origination Fee`**: A nominal fee paid to the Loan Broker taken from the principal lent. - **`Loan Service Fee`**: A nominal fee paid on top of each loan payment. - **`Late Payment Fee`**: A nominal fee paid on top of a late payment. @@ -144,13 +144,6 @@ The lending protocol charges a number of fees that the Loan Broker can configure - **`Amortization`**: The gradual repayment of a loan through scheduled payments that cover both interest and principal over time. - **`Repayment Schedule`**: A detailed plan that outlines when and how much a borrower must pay to repay the Loan fLoan. - **`Grace Period`**: A set period after the Loan's due date after which the Loan Broker can default the Loan -- **`Origination Fee`**: A nominal one-time fee the loan broker charges for processing a new loan application. -- **`Service Fee`**: A recurring nominal charge the Borrower pays during Loan payment. -- **`Management Fee`**: A percentage fee charged by the Borrower on the loan interest before returning the interest to the Vault. -- **`Late Payment Fee`**: A penalty charged to the Borrower for failing to make a payment on or before its due date. -- **`Interest Rate`**: The percentage charged by the loan broker on the loan principal, representing the cost of borrowing. -- **`Late Interest Rate`**: A higher interest rate applied to overdue loan payments as a penalty for late repayment. -- **`Closing Interest Rate`**: The final interest rate charged when the Loan is closed or fully repaid. ### 1.6.2 Actors From b297e6ea0a5d16de070f32e68ca9cb144da480a5 Mon Sep 17 00:00:00 2001 From: Vito Date: Tue, 7 Jan 2025 17:41:20 +0000 Subject: [PATCH 03/41] renames field names from plural to singular --- XLS-0066d-lending-protocol/README.md | 98 ++++++++++++++-------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 0f037e13..b7111705 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -251,9 +251,9 @@ Example 1: # Issuing a Loan # ** Initial States ** -- Vault -- -AssetsTotal = 100,000 Tokens -AssetsAvailable = 100,000 Tokens -SharesTotal = 100,000 Shares +AssetTotal = 100,000 Tokens +AssetAvailable = 100,000 Tokens +ShareTotal = 100,000 Shares -- Lending Protocol -- DebtTotal = 0 @@ -274,16 +274,16 @@ LoanInterest = LoanPrincipal x LoanInterestRate -- Vault -- # Increase the potential value of the Vault -AssetsTotal = AssetsTotal + ((LoanInterest - (LoanInterest x ManagementFeeRate))) +AssetTotal = AssetTotal + ((LoanInterest - (LoanInterest x ManagementFeeRate))) = 100,000 + (100 - (100 x 0.1)) = 100,000 + 90 = 100,090 Tokens -# Decrease Assets Available in the Vault -AssetsAvailable = AssetsAvailable - LoanPrincipal +# Decrease Asset Available in the Vault +AssetAvailable = AssetAvailable - LoanPrincipal = 100,000 - 1,000 = 99,000 Tokens -SharesTotal = (UNCHANGED) +ShareTotal = (UNCHANGED) -- Lending Protocol -- # Increase Lending Protocol Debt @@ -299,9 +299,9 @@ Example 2: # Loan Payment # ** Initial States ** -- Vault -- -AssetsTotal = 100,090 Tokens -AssetsAvailable = 99,000 Tokens -SharesTotal = 100,000 Shares +AssetTotal = 100,090 Tokens +AssetAvailable = 99,000 Tokens +ShareTotal = 100,000 Shares -- Lending Protocol -- DebtTotal = 1,090 Tokens @@ -329,14 +329,14 @@ PaymentInterestPortion = 50 Tokens ** State Changes ** -- Vault -- -AssetsTotal = (UNCHANGED) +AssetTotal = (UNCHANGED) -# Increase Assets Available in the Vault -AssetsAvailable = AssetsAvailable + PaymentPrincipalPortion + (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) +# Increase Asset Available in the Vault +AssetAvailable = AssetAvailable + PaymentPrincipalPortion + (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) = 99,000 + 500 + (50 - (50 x 0.1)) = 99,545 Tokens -SharesTotal = (UNCHANGED) +ShareTotal = (UNCHANGED) -- Lending Protocol -- @@ -368,9 +368,9 @@ Example 1: Loan Default ** Initial States ** -- Vault -- -AssetsTotal = 100,090 Tokens -AssetsAvailable = 99,000 Tokens -SharesTotal = 100,000 Tokens +AssetTotal = 100,090 Tokens +AssetAvailable = 99,000 Tokens +ShareTotal = 100,000 Tokens -- Lending Protocol -- DebtTotal = 1,090 Tokens @@ -396,15 +396,15 @@ DefaultRemaining = DefaultAmount - DefaultCovered ** State Changes ** -- Vault -- -AssetsTotal = AssetsTotal - DefaultRemaining +AssetTotal = AssetTotal - DefaultRemaining = 100,090 - 1,079.1 = 99,010.9 Tokens -AssetsAvailable = AssetsAvailable + DefaultCovered +AssetAvailable = AssetAvailable + DefaultCovered = 99,000 + 10.9 = 99,010.9 Tokens -SharesTotal = (UNCHANGED) +ShareTotal = (UNCHANGED) -- Lending Protocol -- DebtTotal = DebtTotal - DefaultAmount @@ -459,8 +459,8 @@ The `LoanID` is calculated as follows: | `GracePeriod` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | | `PreviousPaymentDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `NextPaymentDueDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentsRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentsTotal` | The number of payments remaining on the Loan. | -| `AssetsAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `PaymentRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | +| `AssetAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | | `PrincipalOutstanding` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | ##### 2.2.2.1 Flags @@ -697,7 +697,7 @@ The transaction creates a new `Loan` object. | `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | | `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentsTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | +| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | | `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | | `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | | `Lender` | :heavy_check_mark: | `object` | `STObject` | `N/A` | An inner object that contains the signature of the Lender over the transaction. | @@ -760,7 +760,7 @@ The `LoanSet` transaction is a mutual agreement between the `Borrower` and the ` - Insufficient assets in the Vault: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable` < `Loan.PrincipalRequested`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetAvailable` < `Loan.PrincipalRequested`. - Exceeds maximum Debt of the LoanBroker: @@ -781,12 +781,12 @@ The `LoanSet` transaction is a mutual agreement between the `Borrower` and the ` - `Vault(LoanBroker(LoanBrokerID).VaultID)` object state changes: - - Decrease Assets Available in the Vault: + - Decrease Asset Available in the Vault: - - `Vault.AssetsAvailable -= Loan.PrincipalRequested`. + - `Vault.AssetAvailable -= Loan.PrincipalRequested`. - Increase the Total Value of the Vault: - - `Vault.AssetsTotal += LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` where `LoanInterest` is the Loan's total interest. + - `Vault.AssetTotal += LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` where `LoanInterest` is the Loan's total interest. - `LoanBroker(LoanBrokerID)` object changes: @@ -816,7 +816,7 @@ The transaction deletes an existing `Loan` object. - A `Loan` object with the specified `LoanID` does not exist on the ledger. - The Account submitting the `LoanDelete` is not the `LoanBroker.Owner` or the `Loan.Borrower`. - The Loan is active: - - `Loan.PaymentsRemaining > 0` + - `Loan.PaymentRemaining > 0` ##### 3.2.2.2 State Changes @@ -851,7 +851,7 @@ The transaction deletes an existing `Loan` object. - If `Loan(LoanID).Flags == lsfLoanImpaired` AND `tfLoanImpair` flag is provided. -- `Loan.PaymentsRemaining == 0`. +- `Loan.PaymentRemaining == 0`. - The `tfDefault` flag is specified and: - `CurrentTime` < `Loan.NextPaymentDueDate + Loan.GracePeriod`. @@ -863,7 +863,7 @@ The transaction deletes an existing `Loan` object. - Calculate the amount of the Default that First-Loss Capital covers: - The default Amount equals the outstanding principal and interest, excluding any funds unclaimed by the Borrower. - - `DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - Loan.AssetsAvailable`. + - `DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - Loan.AssetAvailable`. - Apply the First-Loss Capital to the Default Amount - `DefaultCovered = min((LoanBroker(Loan.LoanBrokerID).DebtTotal x LoanBroker(Loan.LoanBrokerID).CoverRateMinimum) x LoanBroker(Loan.LoanBrokerID).CoverRateLiquidation, DefaultAmount)` - `DefaultAmount -= DefaultCovered` @@ -871,15 +871,15 @@ The transaction deletes an existing `Loan` object. - Update the `Vault` object: - Decrease the Total Value of the Vault: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsTotal -= DefaultAmount`. - - Increase the Assets Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += DefaultCovered + Loan.AssetsAvailable`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetTotal -= DefaultAmount`. + - Increase the Asset Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetAvailable += DefaultCovered + Loan.AssetAvailable`. - Update the `LoanBroker` object: - Decrease the Debt of the LoanBroker: - `LoanBroker(LoanBrokerID).DebtTotal -= ` - - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetsAvailable` + - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetAvailable` - Decrease the First-Loss Capital Cover Available: - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` - Decrease the number of active Loans: @@ -888,8 +888,8 @@ The transaction deletes an existing `Loan` object. - Update the `Loan` object: - `Loan(LoanID).Flags = lsfLoanDefault` - - `Loan(LoanID).PaymentsRemaining = 0` - - `Loan(LoanID).AssetsAvailable = 0` + - `Loan(LoanID).PaymentRemaining = 0` + - `Loan(LoanID).AssetAvailable = 0` - `Loan(LoanID).PrincipalOutstanding = 0` - If `tfLoanImpair` flag is specified: @@ -942,7 +942,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - `Loan.StartDate > CurrentTime`. - There are insufficient assets: - - `Loan.AssetsAvailable` < `Amount`. + - `Loan.AssetAvailable` < `Amount`. - The `Loan` has `lsfLoanImpaired` or `lsfLoanDefault` flags set. @@ -977,7 +977,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - Decrease the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. -- Decrease `Loan.AssetsAvailable` by `Amount`. +- Decrease `Loan.AssetAvailable` by `Amount`. ##### 3.2.4.3 Invariants @@ -1036,7 +1036,7 @@ totalDue = periodicPayment + loanServiceFee $$ $$ -periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentsRemaining}}{(1 + periodicRate)^{PaymentsRemaining} - 1} +periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentRemaining}}{(1 + periodicRate)^{PaymentRemaining} - 1} $$ where the periodic interest rate is the interest rate charged per payment period: @@ -1069,7 +1069,7 @@ $$ latePaymentInterest = principalOutstanding \times \frac{lateInterestRate \times secondsSinceLastPayment}{365 \times 24 \times 60 \times 60} $$ -A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetsTotal` must be recalculate +A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetTotal` must be recalculate d. Assume the function `PeriodicPayment()` returns the expected periodic payment, split into `principalPeriodic` and `interestPeriodic`. Furthermore, assume the function `LatePayment()` that implements the Late Payment formula. The function returns the late payment split into `principalLate` and `interestLate`, where `interestLate` is calculated using the formula above. Note that `principalPeriodic == principalLate` and `interestLate > interestPeriodic` are used only when the payment is late. Otherwise, `interestLate == interestPeriodic`. @@ -1130,7 +1130,7 @@ $$ prepaymentPenalty = principalOutstanding \times closeInterestRate $$ -An early payment pays less interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the Vault value (captured by `Vault.AssetsTotal`) must be recalculated after an early payment. +An early payment pays less interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the Vault value (captured by `Vault.AssetTotal`) must be recalculated after an early payment. Assume a function `CurrentValue()` that returns `principalOutstanding` and `interestOutstanding` of the Loan. Furthermore, assume a function `ClosePayment()` that implements the Full Payment calculation. The function returns the total full payment due split into `principal` and `interest`. @@ -1298,7 +1298,7 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - The submitter `AccountRoot.Account` is not equal to `Loan.Borrower`. -- `Loan.PaymentsRemaining` or `Loan.PrincipalOutstanding` is `0`. +- `Loan.PaymentRemaining` or `Loan.PrincipalOutstanding` is `0`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: @@ -1336,10 +1336,10 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - `Loan(LoanID).Flags = 0` - - Decrease `Loan.PaymentsRemaining` by `full_periodic_payments`. + - Decrease `Loan.PaymentRemaining` by `full_periodic_payments`. - Decrease `Loan.PrincipalOutstanding` by `principal_paid`. - - If `Loan.PaymentsRemaining > 0` and `LoanPrincipalOutstanding > 0`: + - If `Loan.PaymentRemaining > 0` and `LoanPrincipalOutstanding > 0`: - Set the next payment date: `Loan.NextPaymentDueDate += Loan.PaymentInterval * full_periodic_payments`. - Set the previous payment date: `Loan.PreviousPaymentDate = Loan.NextPaymentDueDate - Loan.PaymentInterval`. @@ -1370,7 +1370,7 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - Update the LoanBroker Debt by the change in the management fee: - `LoanBroker.DebtTotal -= (valueChange x LoanBroker.ManagementFeeRate)` - - If `LoanPaymentsRemaining == 0` and `LoanPrincipalOutstanding == 0`: + - If `LoanPaymentRemaining == 0` and `LoanPrincipalOutstanding == 0`: - Decrease active loans: - `LoanBroker.OwnerCount = LoanBroker.OwnerCount - 1` @@ -1378,19 +1378,19 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - Increase available assets in the Vault by the amount paid: - - `Vault.AssetsAvailable = Vault.AssetsAvailable + totalPaid` + - `Vault.AssetAvailable = Vault.AssetAvailable + totalPaid` - Update the Vault total value by the change in the Loan total value: - - `Vault.AssetsTotal = Vault.AssetsTotal + valueChange` + - `Vault.AssetTotal = Vault.AssetTotal + valueChange` - Update the Vault total value by the change in the management fee: - - `Vault.AssetsTotal = Vault.AssetsTotal - (vaultChange x LoanBroker.managementFeeRate)` + - `Vault.AssetTotal = Vault.AssetTotal - (vaultChange x LoanBroker.managementFeeRate)` - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - The management fee was not charged; decrease Vault TotalValue: - - `Loan.AssetsTotal = Loan.AssetsTotal + feeManagement` + - `Loan.AssetTotal = Loan.AssetTotal + feeManagement` - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: From aac7f3ece9b8dbc2ce8ca214b97b4fc855d41b0a Mon Sep 17 00:00:00 2001 From: Vito Date: Mon, 27 Jan 2025 16:40:28 +0000 Subject: [PATCH 04/41] This commit introduces the following changes: - Adds VaultNode to LoanBroker object to track in which owner directory of the Vaults pseudo-account the LoanBroker object is referenced. - Adds LoanBrokerNode to Loan object to track in which owner directory of the LoanBroker object the Loan is references. - Replaces CurrentTime to LastClosedLedger.CloseTime. - Changes the LoanBroker.Delete transaction to automatically return any outstanding Cover to the LoanBroker.Owner. - Adds a balance check to the LoanBrokerCoverDeposit transaction when depositing XRP. - Adds a check to LoanBrokerCoverWithdraw to ensure the CoverAvailable does not drop below Mimimum Cover Required. --- XLS-0066d-lending-protocol/README.md | 55 +++++++++++++++++++--------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index b7111705..87be5c2f 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -209,6 +209,7 @@ The `LoanBroker` object has the following fields: | `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger containing the transaction that last modified this object. | | `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the `LoanBroker`. | | `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `VaultNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the Vault's _pseudo-account_ owner's directory. | | `VaultID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | | `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the Loan Broker. | | `Data` | `Yes` | | `string` | `BLOB` | None | Arbitrary metadata about the `LoanBroker`. Limited to 256 bytes. | @@ -226,7 +227,7 @@ The Lending Protocol uses the `_pseudo-account_` of the associated `Vault` objec #### 2.1.4 Ownership -The lending protocol object is stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `LoanBrokerSet` transaction. Furthermore, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_. +The lending protocol object is stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `LoanBrokerSet` transaction. Furthermore, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_. The `_pseudo_account_` `OwnerDirectory` page is captured by the `VaultNode` field. The `LoanBroker` requires tracking associated `Loan` objects to prevent the `LoanBroker` object from being deleted while loans are active as well as future RPC endpoints. Therefore, the `LoanBroker` has an associated [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) object. @@ -443,6 +444,7 @@ The `LoanID` is calculated as follows: | `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | | `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | | `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `LoanBrokerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | | `LoanBrokerID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | | `Borrower` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | | `LoanOriginationFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | @@ -459,8 +461,8 @@ The `LoanID` is calculated as follows: | `GracePeriod` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | | `PreviousPaymentDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `NextPaymentDueDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | -| `AssetAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `PaymentRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | +| `AssetAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | | `PrincipalOutstanding` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | ##### 2.2.2.1 Flags @@ -471,13 +473,13 @@ The `Loan` object supports the following flags: | -------------------- | :--------: | :---------: | :----------------------------------------------------: | | `lsfLoanDefault` | `0x0001` | `No` | If set, indicates that the Loan is defaulted. | | `lsfLoanImpaired` | `0x0002` | `Yes` | If set, indicates that the Loan is impaired. | -| `lsfLoanOverpayment` | `0x0003` | `No` | If set, indicates that the Loan supports overpayments. | +| `lsfLoanOverpayment` | `0x0004` | `No` | If set, indicates that the Loan supports overpayments. | #### 2.2.3 Ownership The `Loan` objects are stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the `Borrower`. -Furthermore, to facilitate the `Loan` object lookup from the `LoanBroker`, the object is also tracked in the `OwnerDirectory` associated with the `LoanBroker` object. +Furthermore, to facilitate the `Loan` object lookup from the `LoanBroker`, the object is also tracked in the `OwnerDirectory` associated with the `LoanBroker` object. The `OwnerDirectory` page of the `LoanBroker` is captured by the `LoanBrokerNode` field. #### 2.2.4 Reserves @@ -563,6 +565,22 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - Delete `LoanBrokerID` from the `OwnerDirectory` of the Vault's `_pseudo-account_`. - Delete the `OwnerDirectory` associated with the `LoanBroker` object. +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `CoverAvailable`. + - Increase the `Balance` field of the submitter `AccountRoot` by `CoverAvailable`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `CoverAvailable`. + - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `CoverAvailable`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Decrease the `MPToken.MPTAmount` by `CoverAvailable` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Increase the `MPToken.MPTAmount` by `CoverAvailable` of the submitter `MPToken` object for the `Vault.Asset`. + + ##### 3.1.2.3 Invariants **TBD** @@ -571,7 +589,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. #### 3.1.3 `LoanBrokerCoverDeposit` -The transaction creates a new `LoanBroker` object or updates an existing one. +The transaction deposits First Loss Capital into the `LoanBroker` object. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------ | @@ -584,6 +602,9 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + - `AccountRoot(LoanBroker.Owner).Balance < Amount` (LoanBroker does not have sufficient funds to deposit the First Loss Capital). + - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. @@ -648,10 +669,10 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - Has `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. -- The submitter is attempting to withdraw an Asset that does not match the Asset of the Vault. - - The `LoanBroker.CoverAvailable` < `Amount`. +- `LoanBroker.CoverAvailable - Amount` < `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum` + ##### 3.2.2.2 State Changes - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: @@ -697,7 +718,7 @@ The transaction creates a new `Loan` object. | `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | | `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | +| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | | `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | | `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | | `Lender` | :heavy_check_mark: | `object` | `STObject` | `N/A` | An inner object that contains the signature of the Lender over the transaction. | @@ -756,7 +777,7 @@ The `LoanSet` transaction is a mutual agreement between the `Borrower` and the ` - `PaymentInterval` is less than `60` seconds. - `GracePeriod` is greater than the `PaymentInterval`. -- `Loan.StartDate < CurrentTime`. +- `Loan.StartDate < LastClosedLedger.CloseTime`. - Insufficient assets in the Vault: @@ -841,7 +862,7 @@ The transaction deletes an existing `Loan` object. | ---------------- | :--------: | :--------------------------------------------- | | `tfLoanDefault` | `0x0001` | Indicates that the Loan should be defaulted. | | `tfLoanImpair` | `0x0002` | Indicates that the Loan should be impaired. | -| `tfLoanUnimpair` | `0x0003` | Indicates that the Loan should be un-impaired. | +| `tfLoanUnimpair` | `0x0004` | Indicates that the Loan should be un-impaired. | ##### 3.2.3.1 Failure Conditions @@ -854,7 +875,7 @@ The transaction deletes an existing `Loan` object. - `Loan.PaymentRemaining == 0`. - The `tfDefault` flag is specified and: - - `CurrentTime` < `Loan.NextPaymentDueDate + Loan.GracePeriod`. + - `LastClosedLedger.CloseTime` < `Loan.NextPaymentDueDate + Loan.GracePeriod`. ##### 3.2.3.2 State Changes @@ -939,7 +960,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - A `Loan` object with the specified `LoanID` does not exist on the ledger. - The `AccountRoot.Account` of the submitter is not `Loan.Borrower`. - The Loan has not started: - - `Loan.StartDate > CurrentTime`. + - `Loan.StartDate > LastClosedLedger.CloseTime`. - There are insufficient assets: - `Loan.AssetAvailable` < `Amount`. @@ -958,7 +979,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - The `Borrower` missed a payment: - - `CurrentTime > Loan.NextPaymentDueDate`. + - `LastClosedLedger.CloseTime > Loan.NextPaymentDueDate`. ##### 3.2.4.2 State Changes @@ -1294,7 +1315,7 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - A `Loan` object with specified `LoanID` does not exist on the ledger. -- The Loan has not started yet: `Loan.StartDate > CurrentTime`. +- The Loan has not started yet: `Loan.StartDate > LastClosedLedger.CloseTime`. - The submitter `AccountRoot.Account` is not equal to `Loan.Borrower`. @@ -1311,9 +1332,9 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - Has `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. -- If `CurrentTime > Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` +- If `LastClosedLedger.CloseTime > Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` -- If `CurrentTime <= Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` +- If `LastClosedLedger.CloseTime <= Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` ##### 3.2.5.4 State Changes From f18e8f13379bff0f93460de982ebae73ade57937 Mon Sep 17 00:00:00 2001 From: Vito Date: Mon, 27 Jan 2025 16:46:54 +0000 Subject: [PATCH 05/41] removes CoverAvailable check from LoanBrokerDelete transaction --- XLS-0066d-lending-protocol/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 87be5c2f..da2e6e3d 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -557,7 +557,6 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. - The `OwnerCount` field is greater than zero. -- `CoverAvailable` is greater than zero. ##### 3.1.2.2 State Changes From 44e1b579323256e44c9f2a7dec91fc0a93414b68 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:52:18 +0000 Subject: [PATCH 06/41] Apply suggestions from code review Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index da2e6e3d..8b467871 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -740,9 +740,9 @@ An inner object that contains the signature of the Lender over the transaction. The final transaction must include `Signature` or `Signers`. -If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base\_fee$ +If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ -The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base\_fee$. +The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base_fee$. This field is not a signing field (it will not be included in transaction signatures, though the `Signature` or `Signers` field will be included in the stored transaction). @@ -1012,7 +1012,7 @@ The Borrower submits a `LoanPay` transaction to make a Payment on the Loan. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------ | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | -| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be drawn from. | +| `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be paid to. | | `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to pay. | ##### 3.2.5.1 Payment Types @@ -1177,7 +1177,7 @@ For the calculation, assume the following variables: Assume $f(\mathcal{v})$ is a Loan payment, $f(\mathcal{v}) = \mathcal{v'}$, the new outstanding loan value is equal to the application of the payment transaction to the current outstanding value. Furthermore, assume $\mathcal{V} \xrightarrow{f(\mathcal{v})} \mathcal{V'}$, is the change in the Loan total value as the result of applying $f(\mathcal{v})$. -we say that $\mathcal{V'} = \mathcal{P'} + \mathcal{I'}$. It's important to note that a payment transaction must never change the total principal. I.e. $\mathcal{P} = \mathcal{P'}$, the change in total value is caused by the change in total principal only. +we say that $\mathcal{V'} = \mathcal{P'} + \mathcal{I'}$. It's important to note that a payment transaction must never change the total principal. I.e. $\mathcal{P} = \mathcal{P'}$, the change in total value is caused by the change in total interest only. $\Delta_{\mathcal{V}} = \mathcal{I'} - \mathcal{I}$ is the total value change of the Loan. When $\Delta_{\mathcal{V}} > 0$ the total value of the Loan increased, when $\Delta_{\mathcal{V}} < 0$ the total value decreased, and if $\Delta_{\mathcal{V}} = 0$ the value remained the same. From 1a437ee6421e63659ad3191e8ae7a23626a56d78 Mon Sep 17 00:00:00 2001 From: Vito Date: Mon, 27 Jan 2025 16:53:05 +0000 Subject: [PATCH 07/41] cleans up latex code --- XLS-0066d-lending-protocol/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 8b467871..f35be922 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1175,7 +1175,7 @@ For the calculation, assume the following variables: - Let $\mathcal{V}$ and $\mathcal{v}$ represent the total and outstanding value of the Loan. $\mathcal{V} = \mathcal{P} + \mathcal{I}$ and $\mathcal{v} = \mathcal{p} + \mathcal{i}$. - Finally, let $\mathcal{m}$ represent the management fee rate of the Loan Broker. -Assume $f(\mathcal{v})$ is a Loan payment, $f(\mathcal{v}) = \mathcal{v'}$, the new outstanding loan value is equal to the application of the payment transaction to the current outstanding value. Furthermore, assume $\mathcal{V} \xrightarrow{f(\mathcal{v})} \mathcal{V'}$, is the change in the Loan total value as the result of applying $f(\mathcal{v})$. +Assume $f(\mathcal{v})$ is a Loan payment, $f(\mathcal{v}) = \mathcal{v'}$, the new outstanding loan value is equal to the application of the payment transaction to the current outstanding value. Furthermore, assume $\mathcal{V} \xrightarrow{f(\mathcal{v})}$ $\mathcal{V'}$, is the change in the Loan total value as the result of applying $f(\mathcal{v})$. we say that $\mathcal{V'} = \mathcal{P'} + \mathcal{I'}$. It's important to note that a payment transaction must never change the total principal. I.e. $\mathcal{P} = \mathcal{P'}$, the change in total value is caused by the change in total interest only. From 9f47ea2c4f30c29de098d0fb51cacf30195bc49e Mon Sep 17 00:00:00 2001 From: Vito Date: Tue, 28 Jan 2025 11:45:04 +0000 Subject: [PATCH 08/41] updates LoanDelete transaction to delete the LoanBroker DirectoryNode when the last Loan is delete --- XLS-0066d-lending-protocol/README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index f35be922..5ae9b2c0 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -443,7 +443,7 @@ The `LoanID` is calculated as follows: | `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | | `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | | `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | -| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `Borrower` owner's directory. | | `LoanBrokerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | | `LoanBrokerID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | | `Borrower` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | @@ -477,9 +477,11 @@ The `Loan` object supports the following flags: #### 2.2.3 Ownership -The `Loan` objects are stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the `Borrower`. +The `Loan` objects are stored in the ledger and tracked in two [Owner Directories](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode). + +- The `OwnerNode` is the `Owner Directory` of the `Borrower` who is the main `Owner` of the `Loan` object, and therefore is responsible for the owner reserve. +- The `LoanBroker` `_pseudo-account_` `Owner Directory` to track all loans associated with the same `LoanBroker` object. -Furthermore, to facilitate the `Loan` object lookup from the `LoanBroker`, the object is also tracked in the `OwnerDirectory` associated with the `LoanBroker` object. The `OwnerDirectory` page of the `LoanBroker` is captured by the `LoanBrokerNode` field. #### 2.2.4 Reserves @@ -562,7 +564,6 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - Delete `LoanBrokerID` from the `OwnerDirectory` of the submitting account. - Delete `LoanBrokerID` from the `OwnerDirectory` of the Vault's `_pseudo-account_`. -- Delete the `OwnerDirectory` associated with the `LoanBroker` object. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: @@ -579,10 +580,9 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - Decrease the `MPToken.MPTAmount` by `CoverAvailable` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `CoverAvailable` of the submitter `MPToken` object for the `Vault.Asset`. - ##### 3.1.2.3 Invariants -**TBD** +- If `LoanBroker.OwnerCount = 0` the `DirectoryNode` for the `LoanBroker` does not exist [**Return to Index**](#index) @@ -602,6 +602,7 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + - `AccountRoot(LoanBroker.Owner).Balance < Amount` (LoanBroker does not have sufficient funds to deposit the First Loss Capital). - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: @@ -844,6 +845,13 @@ The transaction deletes an existing `Loan` object. - `LoanBroker.OwnerCount -= 1` - Delete the `Loan` object. - Release reserve funds back to the Borrower. +- Remove `LoanID` from the `DirectoryNode.Indexes`. +- If `LoanBroker.OwnerCount = 0` + - Delete the `LoanBroker` `DirectoryNode`. + +##### 3.2.2.3 Invariants + +- If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0` [**Return to Index**](#index) @@ -1009,11 +1017,11 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. The Borrower submits a `LoanPay` transaction to make a Payment on the Loan. -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------ | -| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | | `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be paid to. | -| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to pay. | +| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to pay. | ##### 3.2.5.1 Payment Types From 60a311ede00c5c428bed3f0642a1af083ba82914 Mon Sep 17 00:00:00 2001 From: Vito Date: Tue, 28 Jan 2025 14:42:25 +0000 Subject: [PATCH 09/41] in case insufficient first loss capital add loan broker fees to cover for the missing flc --- XLS-0066d-lending-protocol/README.md | 52 +++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 5ae9b2c0..fc46d626 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -359,7 +359,7 @@ The First-Loss Capital is an optional mechanism to protect the Vault depositors Whenever the available cover falls below the minimum cover required, two consequences occur: - The Lender cannot issue new Loans. -- The Lender cannot receive fees. Borrower fees are ignored (i.e. the Borrower does not have to pay a Loan payment fee), and the Management Fee is instead deposited into a Vault. +- The Lender cannot directly receive fees. The fees are instead added to the First Loss Capital to cover the deficit. **Examples** @@ -482,7 +482,6 @@ The `Loan` objects are stored in the ledger and tracked in two [Owner Directorie - The `OwnerNode` is the `Owner Directory` of the `Borrower` who is the main `Owner` of the `Loan` object, and therefore is responsible for the owner reserve. - The `LoanBroker` `_pseudo-account_` `Owner Directory` to track all loans associated with the same `LoanBroker` object. - #### 2.2.4 Reserves The `Loan` object costs one owner reserve for the `Borrower`. @@ -582,7 +581,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. ##### 3.1.2.3 Invariants -- If `LoanBroker.OwnerCount = 0` the `DirectoryNode` for the `LoanBroker` does not exist +- If `LoanBroker.OwnerCount = 0` the `DirectoryNode` for the `LoanBroker` does not exist [**Return to Index**](#index) @@ -1378,29 +1377,27 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - `feeManagement = interest_paid x LoanBroker.ManagementFeeRate` - - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + - Decrease the management fee from totalPaid amount: - - Do not charge the management, add it to the total debt: - - `LoanBroker.DebtTotal += feeManagement` + - `totalPaid = totalPaid - feeManagement` + + - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - - If there is **enough** first-loss capital: `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + - Add the fee to to First Loss Cover Pool: - - Decrease the management fee from totalPaid amount: - - `totalPaid = totalPaid - feeManagement` + - `LoanBroker.CoverAvailable = LoanBroker.CoverAvailable + (feeManagement + fee_paid)` - Decrease LoanBroker Debt by the amount paid: - - `LoanBroker.DebtTotal -= totalPaid` + - `LoanBroker.DebtTotal = LoanBroker.DebtTotal - totalPaid` - Update the LoanBroker Debt by the Loan value change: - - `LoanBroker.DebtTotal += valueChange` + - `LoanBroker.DebtTotal = LoanBroker.DebtTotal + valueChange` - Update the LoanBroker Debt by the change in the management fee: - - `LoanBroker.DebtTotal -= (valueChange x LoanBroker.ManagementFeeRate)` - - If `LoanPaymentRemaining == 0` and `LoanPrincipalOutstanding == 0`: - - Decrease active loans: - - `LoanBroker.OwnerCount = LoanBroker.OwnerCount - 1` + + - `LoanBroker.DebtTotal = LoanBroker.DebtTotal - (valueChange x LoanBroker.ManagementFeeRate)` - `Vault(LoanBroker(Loan.LoanBrokerID).VaultID)` state changes: @@ -1416,35 +1413,42 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - `Vault.AssetTotal = Vault.AssetTotal - (vaultChange x LoanBroker.managementFeeRate)` - - If there is **not enough** first-loss capital: `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - - The management fee was not charged; decrease Vault TotalValue: - - `Loan.AssetTotal = Loan.AssetTotal + feeManagement` - - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `Balance` field of the `LoanBroker.Owner` `AccountRoot` by `fee_paid + management_fee`. + - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + + - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + interest_paid + fee_paid` (the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_). - Decrease the `Balance` field of the submitter `AccountRoot` by `principal_paid + interest_paid + fee_paid`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. - - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum` : + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `RippleState` balance between the `LoanBroker.Owner` `AccountRoot` and the `Issuer` `AccountRoot` by `fee_paid + management_fee`. + - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + + - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + + - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + interest_paid + fee_paid` (the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_). - Decrease the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + interest_paid + fee_paid`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - Increase the `MPToken.MPTAmount` by `principal_paid + (interest_paid - management_fee)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `MPToken.MPTAmount` by `fee_paid + management_fee` of the `LoanBroker.Owner` `MPToken` object for the `Vault.Asset`. - + - Increase the `MPToken.MPTAmount` by `principal_paid + (interest_paid - management_fee)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + + - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: + - Increase the `MPToken.MPTAmount` by `principal_paid + interest_paid + fee_paid` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`(the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_) . + - Decrease the `MPToken.MPTAmount` by `principal_paid + interest_paid + fee_paid` of the submitter `MPToken` object for the `Vault.Asset`. [**Return to Index**](#index) From 9090bd31ea8cb1547e8990cfed54038fd40bb27f Mon Sep 17 00:00:00 2001 From: Vito Date: Tue, 28 Jan 2025 14:44:03 +0000 Subject: [PATCH 10/41] fixes loan object ownership --- XLS-0066d-lending-protocol/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index fc46d626..6b876337 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -480,7 +480,7 @@ The `Loan` object supports the following flags: The `Loan` objects are stored in the ledger and tracked in two [Owner Directories](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode). - The `OwnerNode` is the `Owner Directory` of the `Borrower` who is the main `Owner` of the `Loan` object, and therefore is responsible for the owner reserve. -- The `LoanBroker` `_pseudo-account_` `Owner Directory` to track all loans associated with the same `LoanBroker` object. +- The `LoanBrokerNode` is the `Owner Directory` for the `LoanBroker` to track all loans associated with the same `LoanBroker` object. #### 2.2.4 Reserves From 885162053db967cca804747a36d33d53fda16598 Mon Sep 17 00:00:00 2001 From: Vito Date: Wed, 29 Jan 2025 13:33:09 +0000 Subject: [PATCH 11/41] adds pseudo-account to the LoanBroker --- XLS-0066d-lending-protocol/README.md | 190 ++++++++++++++++++--------- 1 file changed, 131 insertions(+), 59 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 6b876337..b6be52e0 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -157,27 +157,27 @@ The lending protocol charges a number of fees that the Loan Broker can configure | Depositor | | LoanBroker | | Borrower | | AccountRoot | | AccountRoot | | AccountRoot | |-----------------| |-----------------| |-----------------| -| Owner Directory | | Owner Directory | | Owner Directory | -+-----------------+ +-----------------+ +-----------------+ - ^ | | | - | Reserve ____________Reserve____________ Reserve - Account | | | | - | V V V V +| Owner Directory | | Owner Directory | <-----OwnerNode | Owner Directory | <--------- ++-----------------+ +-----------------+ | +-----------------+ | + ^ | | | | | + | Reserve ____________Reserve____________ | Reserve | + Account | | | | | | + | V V V | V | ++-----------------+ +-----------------+ +-----------------+ +-----------------+ | +| | | |1 N| |1 N| | | +| MPToken | | Vault |--------->| LoanBroker |--------->| Loan |-OwnerNode- +| | | | | | | | +-----------------+ +-----------------+ +-----------------+ +-----------------+ -| | | |1 N| |1 N| | -| MPToken | | Vault |--------->| LoanBroker |--------->| Loan | -| | | | |-----------------| | | -+-----------------+ +-----------------+ | Owner Directory | +-----------------+ - | ^ +-----------------+ ^ - Issuance | ___________ ____________^ |_________Link_________| - | | Account | - V ^ | ^ -+-----------------+ | +-----------------+ | -| Share | | | Pseudo-Account | | -| MPTokenIssuance |<------Issuer------| -----| AccountRoot | | -| | |_Link_|-----------------|_Link_| -+-----------------+ | Owner Directory | - +-----------------+ + | ^ | ^ | + Issuance | | | | + | Account | Account | + V | -VaultNode- | | ++-----------------+ +-----------------+ | +-----------------+ | +| Share | | Pseudo-Account | | | Pseudo-Account | | +| MPTokenIssuance |<--Issuer-| AccountRoot | | | AccountRoot | | +| | |-----------------| | |-----------------| | ++-----------------+ | Owner Directory | <---- | Owner Directory | <- LoanBrokerNode--- + +-----------------+ +-----------------+ ``` [**Return to Index**](#index) @@ -211,7 +211,8 @@ The `LoanBroker` object has the following fields: | `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | | `VaultNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the Vault's _pseudo-account_ owner's directory. | | `VaultID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | -| `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the Loan Broker. | +| `Account` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the `LoanBroker` _pseudo-account_. | +| `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the Loan Broker account. | | `Data` | `Yes` | | `string` | `BLOB` | None | Arbitrary metadata about the `LoanBroker`. Limited to 256 bytes. | | `ManagementFeeRate` | `No` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol. Valid values are between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001% | | `OwnerCount` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | 0 | The number of active Loans issued by the `LoanBroker`. | @@ -223,13 +224,11 @@ The `LoanBroker` object has the following fields: #### 2.1.3 `LoanBroker `_pseudo-account_` -The Lending Protocol uses the `_pseudo-account_` of the associated `Vault` object to hold the First-Loss Capital. +The `LoanBroker` _pseudo-account_ holds the First-Loss Capital deposited by the LoanBroker, as well as Loan funds. The _pseudo-account_ follows the XLS-64d specification for pseudo accounts. The `AccountRoot` object is created when creating the `Vault` object. #### 2.1.4 Ownership -The lending protocol object is stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `LoanBrokerSet` transaction. Furthermore, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_. The `_pseudo_account_` `OwnerDirectory` page is captured by the `VaultNode` field. - -The `LoanBroker` requires tracking associated `Loan` objects to prevent the `LoanBroker` object from being deleted while loans are active as well as future RPC endpoints. Therefore, the `LoanBroker` has an associated [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) object. +The lending protocol object is stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `LoanBrokerSet` transaction. Furthermore, the object is also tracked in the `OwnerDirectory` of the `Vault` _`pseudo-account`_. The `_pseudo_account_` `OwnerDirectory` page is captured by the `VaultNode` field. The `RootIndex` of the `DirectoryNode` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order: @@ -359,7 +358,7 @@ The First-Loss Capital is an optional mechanism to protect the Vault depositors Whenever the available cover falls below the minimum cover required, two consequences occur: - The Lender cannot issue new Loans. -- The Lender cannot directly receive fees. The fees are instead added to the First Loss Capital to cover the deficit. +- The Lender cannot directly receive fees. The fees are instead added to the First Loss Capital to cover the deficit. **Examples** @@ -480,7 +479,7 @@ The `Loan` object supports the following flags: The `Loan` objects are stored in the ledger and tracked in two [Owner Directories](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode). - The `OwnerNode` is the `Owner Directory` of the `Borrower` who is the main `Owner` of the `Loan` object, and therefore is responsible for the owner reserve. -- The `LoanBrokerNode` is the `Owner Directory` for the `LoanBroker` to track all loans associated with the same `LoanBroker` object. +- The `LoanBrokerNode` is the `Owner Directory` for the `LoanBroker` _pseudo-account_ to track all loans associated with the same `LoanBroker` object. #### 2.2.4 Reserves @@ -533,8 +532,20 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - If `LoanBrokerID` is not specified: + - Create a new `LoanBroker` ledger object. + + - Create a new `AccountRoot` _pseudo-account_ object, setting the `AccountRoot.LoanBrokerID` to `LoanBrokerID`. + + - If the `Vault(VaultID).Asset` is an `IOU`: + + - Create a `Trustline` between the `Issuer` and the `LoanBroker` _pseudo-account_. + + - If the `Vault(VaultID).Asset` is an `MPT`: + + - Create an `MPToken` object for the `LoanBroker` _pseudo-account_. + - Add `LoanBrokerID` to the `OwnerDirectory` of the submitting account. - - Add `LoanBrokerID` to the `OwnerDirectory` of the Vault's `_pseudo-account_`. + - Add `LoanBrokerID` to the `OwnerDirectory` of the Vault's _pseudo-account_. - If `LoanBrokerID` is specified: - Update appropriate fields. @@ -562,23 +573,26 @@ The transaction creates a new `LoanBroker` object or updates an existing one. ##### 3.1.2.2 State Changes - Delete `LoanBrokerID` from the `OwnerDirectory` of the submitting account. -- Delete `LoanBrokerID` from the `OwnerDirectory` of the Vault's `_pseudo-account_`. +- Delete `LoanBrokerID` from the `OwnerDirectory` of the Vault's _pseudo-account_. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `CoverAvailable`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `CoverAvailable`. - Increase the `Balance` field of the submitter `AccountRoot` by `CoverAvailable`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `CoverAvailable`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `CoverAvailable`. - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `CoverAvailable`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` by `CoverAvailable` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Decrease the `MPToken.MPTAmount` by `CoverAvailable` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `CoverAvailable` of the submitter `MPToken` object for the `Vault.Asset`. +- Delete the `LoanBroker` _pseudo-account_ `AccountRoot` object. +- Delete the `LoanBroker` ledger object. + ##### 3.1.2.3 Invariants - If `LoanBroker.OwnerCount = 0` the `DirectoryNode` for the `LoanBroker` does not exist @@ -621,17 +635,17 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Increase the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Amount`. - Decrease the `Balance` field of the submitter `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Increase the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - Decrease the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Increase the `MPToken.MPTAmount` by `Amount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Decrease the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. - Increase `LoanBroker.CoverAvailable` by `Amount`. @@ -676,17 +690,17 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Amount`. - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Decrease the `MPToken.MPTAmount` by `Amount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. - Decrease `LoanBroker.CoverAvailable` by `Amount`. @@ -791,14 +805,28 @@ The `LoanSet` transaction is a mutual agreement between the `Borrower` and the ` ##### 3.2.1.5 State Changes -- If the Loan Asset is an `IOU`: +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `Loan.PrincipalRequested`. + - Increase the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan.PrincipalRequested - Loan.LoanOriginationFee`. + - Increase the `Balance` field of `LoanBroker.Owner` `AccountRoot` by `Loan.LoanOriginationFee`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - Create a `Trustline` between the `Issuer` and the `Borrower` if one does not exist. -- If the Loan Asset is an `MPT`: + - Decrease the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.PrincipalRequested`. + - Increase the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.PrincipalRequested - Loan.LoanOriginationFee`. + - Increase the `RippleState` balance between the `LoanBroker.Owner` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.LoanOriginationFee`. + +- If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - Create an `MPToken` object for the `Borrower` if one does not exist. + - Decrease the `MPToken.MPTAmount` of the `Vault` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan.PrincipalRequested`. + - Increase the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan.PrincipalRequested - Loan.LoanOriginationFee`. + - Increase the `MPToken.MPTAmount` of the `LoanBroker.Owner` `MPToken` object for the `Vault.Asset` by `Loan.LoanOriginationFee` + - `Vault(LoanBroker(LoanBrokerID).VaultID)` object state changes: - Decrease Asset Available in the Vault: @@ -813,8 +841,8 @@ The `LoanSet` transaction is a mutual agreement between the `Borrower` and the ` - `LoanBroker.DebtTotal += Loan.PrincipalRequested + (LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` - `LoanBroker.OwnerCount += 1` - - If the `DirectoryNode` for the `LoanBroker` does not exist, create one. - - Add `LoanID` to `DirectoryNode.Indexes`. + - Add `LoanID` to `DirectoryNode.Indexes` of the `LoanBroker` _pseudo-account_ `AccountRoot`. + - Add `LoanID` to `DirectoryNode.Indexes` of the `Borrower` `AccountRoot`. ##### 3.2.1.4 Invariants @@ -840,13 +868,33 @@ The transaction deletes an existing `Loan` object. ##### 3.2.2.2 State Changes -- Remove `LoanID` from the Owner Directory associated with the `LoanBroker`. -- `LoanBroker.OwnerCount -= 1` +- If `Loan(LoanID).AssetAvailable > 0` (transfer remaining funds to the borrower): + + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan(LoanID).AssetAvailable`. + - Increase the `Balance` field of `Loan(LoanID).Borrower` `AccountRoot` by `Loan(LoanID).AssetAvailable`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetAvailable`. + - Increase the `RippleState` balance between the `Loan(LoanID).Borrower` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetAvailable`. + +- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetAvailable`. + - Increase the `MPToken.MPTAmount` of the `Loan(LoanID).Borrower` `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetAvailable` + - Delete the `Loan` object. -- Release reserve funds back to the Borrower. -- Remove `LoanID` from the `DirectoryNode.Indexes`. + +- Remove `LoanID` from `DirectoryNode.Indexes` of the `LoanBroker` _pseudo-account_ `AccountRoot`. - If `LoanBroker.OwnerCount = 0` - - Delete the `LoanBroker` `DirectoryNode`. + + - Delete the `LoanBroker` _pseudo-account_ `DirectoryNode`. + +- Remove `LoanID` from `DirectoryNode.Indexes` of the `Borrower` `AccountRoot`. + +- `LoanBroker.OwnerCount -= 1` ##### 3.2.2.3 Invariants @@ -919,6 +967,23 @@ The transaction deletes an existing `Loan` object. - `Loan(LoanID).AssetAvailable = 0` - `Loan(LoanID).PrincipalOutstanding = 0` +- Move the First-Loss Capital from the `LoanBroker` _pseudo-account_ to the `Vault` _pseudo-account_: + + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `DefaultCovered`. + - Increase the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `DefaultCovered`. + + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `DefaultCovered`. + - Increase the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `DefaultCovered`. + + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + + - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `DefaultCovered`. + - Increase the `MPToken.MPTAmount` of the `Vault` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `DefaultCovered`. + - If `tfLoanImpair` flag is specified: - Update the `Vault` object (set "paper loss"): @@ -967,7 +1032,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - The `AccountRoot.Account` of the submitter is not `Loan.Borrower`. - The Loan has not started: - `Loan.StartDate > LastClosedLedger.CloseTime`. -- There are insufficient assets: +- There are insufficient assets in the `Loan`: - `Loan.AssetAvailable` < `Amount`. @@ -991,17 +1056,17 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Amount`. - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - Decrease the `MPToken.MPTAmount` by `Amount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. - Decrease `Loan.AssetAvailable` by `Amount`. @@ -1415,40 +1480,47 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + - Increase the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `Balance` field of the `LoanBroker.Owner` `AccountRoot` by `fee_paid + management_fee`. - - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `principal_paid + interest_paid + fee_paid` (the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_). + - Increase the `Balance` field of the `LoanBroker` _pseudo-account_ `AccountRoot` by `fee_paid + management_fee`. (the payment and management fee was added to First Loss Capital, and thus transfered to the `LoanBroker` _pseudo-account_). + - Increase `LoanBroker.CoverAvailable` by `fee_paid + management_fee`. - Decrease the `Balance` field of the submitter `AccountRoot` by `principal_paid + interest_paid + fee_paid`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + - Increase the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `RippleState` balance between the `LoanBroker.Owner` `AccountRoot` and the `Issuer` `AccountRoot` by `fee_paid + management_fee`. - - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + interest_paid + fee_paid` (the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_). + - Increase the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `fee_paid + management_fee` (the payment and management fee was added to First Loss Capital, and thus transfered to the `LoanBroker` _pseudo-account_). + - Increase `LoanBroker.CoverAvailable` by `fee_paid + management_fee`. - Decrease the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `principal_paid + interest_paid + fee_paid`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + - Increase the `MPToken.MPTAmount` by `principal_paid + (interest_paid - management_fee)` of the `Vault` _pseudo-account_ `MPToken` object for the `Vault.Asset`. + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `MPToken.MPTAmount` by `fee_paid + management_fee` of the `LoanBroker.Owner` `MPToken` object for the `Vault.Asset`. - - Increase the `MPToken.MPTAmount` by `principal_paid + (interest_paid - management_fee)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`. - + - If `LoanBroker.CoverAvailable < LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - - Increase the `MPToken.MPTAmount` by `principal_paid + interest_paid + fee_paid` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`(the payment and management fee was added to First Loss Capital, and thus transfered to the _pseudo-account_) . - + + - Increase the `MPToken.MPTAmount` by `fee_paid + management_fee` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` (the payment and management fee was added to First Loss Capital, and thus transfered to the `LoanBroker` _pseudo-account_). + - Increase `LoanBroker.CoverAvailable` by `fee_paid + management_fee`. + - Decrease the `MPToken.MPTAmount` by `principal_paid + interest_paid + fee_paid` of the submitter `MPToken` object for the `Vault.Asset`. [**Return to Index**](#index) From 0f6644ce2e2bd91a27a6f332689c4aa95a76efa0 Mon Sep 17 00:00:00 2001 From: Vito Date: Fri, 31 Jan 2025 12:51:50 +0000 Subject: [PATCH 12/41] adds counterparty to LoanSet transaction --- XLS-0066d-lending-protocol/README.md | 87 ++++++++++++++++++---------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index b6be52e0..171ae766 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -715,26 +715,26 @@ In this section we specify transactions associated with the `Loan` ledger entry. The transaction creates a new `Loan` object. -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| -------------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------- | -| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | -| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID associated with the loan. | -| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | -| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | -| `Borrower` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | -| `LoanOriginationFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | -| `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | -| `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | -| `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | -| `InterestRate` | | `number` | `UINT16` | 0 | Annualized interest rate of the Loan in basis points. | -| `LateInterestRate` | | `number` | `UINT16` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 10000 inclusive. (0 - 100%) | -| `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | -| `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | -| `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | -| `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | -| `Lender` | :heavy_check_mark: | `object` | `STObject` | `N/A` | An inner object that contains the signature of the Lender over the transaction. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID associated with the loan. | +| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | +| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | +| `Counterparty` | | `string` | `AccountID` | `N/A` | The address of the counterparty of the Loan. | +| `CounterpartySignature` | :heavy_check_mark: | `string` | `STObject` | `N/A` | The signature of the counterparty over the transaction. | +| `LoanOriginationFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | +| `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | +| `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | +| `InterestRate` | | `number` | `UINT16` | 0 | Annualized interest rate of the Loan in basis points. | +| `LateInterestRate` | | `number` | `UINT16` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 10000 inclusive. (0 - 100%) | +| `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | +| `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | +| `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | +| `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | ##### 3.2.1.1 `Flags` @@ -742,15 +742,15 @@ The transaction creates a new `Loan` object. | ------------------- | :--------: | :---------------------------------------------- | | `tfLoanOverpayment` | `0x0001` | Indicates that the vault supports overpayments. | -##### 3.2.1.2 `Lender` +##### 3.2.1.2 `CounterpartySignature` An inner object that contains the signature of the Lender over the transaction. The fields contained in this object are: -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| --------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------------------------------------------------------------------------------------- | -| `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | -| `Signature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields, including the `Signature` of the Borrower. | -| `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `LoanBroker.Owner` signers to indicate their approval of this transaction. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| --------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | +| `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | +| `Signature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | +| `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | The final transaction must include `Signature` or `Signers`. @@ -762,10 +762,33 @@ This field is not a signing field (it will not be included in transaction signat ##### 3.2.1.3 Multi-Signing -The `LoanSet` transaction is a mutual agreement between the `Borrower` and the `LoanBroke.Owner` to create a Loan. Therefore, the `LoanSet` transaction must be signed by both parties. The multi-signature flow is as follows: +The `LoanSet` transaction is a mutual agreement between the `Borrower` and the `LoanBroke.Owner` to create a Loan. Therefore, the `LoanSet` transaction must be signed by both parties. -1. The `Borrower` creates a new transaction with the pre-agreed terms of the Loan and signs the transaction. -2. The `Lender` signs over all signing fields, including the signature of the `Borrower`. +Either of the parties (Borrower or Loan Issuer) may initiate the transaction. The user flow is as follows: + +- `Borrower` initiates the transaction: + + 1. The `Borrower` creates the transaction from their account, setting the pre-agreed terms. + + - Optionally, the `Borrower` may set the `Counterparty` to `LoanBroker.Owner`. In case the `Counterparty` field is not set, it is assumed to be the `LoanBroker.Owner`. + + 2. The `Borrower` signs the transaction setting the `SigningPubKey`, `TxnSignature`, `Signers`, `Account`, `Fee`, `Sequence` fields. + 3. The `Borrower` sends the transaction to the `Loan Issuer`. + 4. The `Loan Issuer` verifies the loan-terms are as agreed upon and verifies the signature of the `Borrower`. + 5. The `Loan Issuer` signs the transaction, filling the `CounterpartySignature` field. + 6. The `Loan Issuer` submits the transaction. + +- `Loan Issuer` initiates the transaction: + + 1. The `Loan Issuer` creates the transaction from their account setting the pre-agreed terms. + + - The `Loan Issuer` must set the `Counterparty` to the `Borrower` account ID. + + 2. The `Loan Issuer` signs the transaction setting the `SigningPubKey`, `TxnSignature`, `Signers`, `Account`, `Fee`, `Sequence` fields. + 3. The `Loan Issuer` sends the transaction to the `Borrower`. + 4. The `Borrower` verifies the loan-terms are as agreed upon and verifies the signature of the `Loan Issuer`. + 5. The `Borrower` signs the transaction, filling the `CounterpartySignature` field. + 6. The `Borrower` submits the transaction. ##### 3.2.1.4 Failure Conditions @@ -1161,8 +1184,8 @@ $$ latePaymentInterest = principalOutstanding \times \frac{lateInterestRate \times secondsSinceLastPayment}{365 \times 24 \times 60 \times 60} $$ -A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetTotal` must be recalculate -d. +A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetTotal` must be recalculated. + Assume the function `PeriodicPayment()` returns the expected periodic payment, split into `principalPeriodic` and `interestPeriodic`. Furthermore, assume the function `LatePayment()` that implements the Late Payment formula. The function returns the late payment split into `principalLate` and `interestLate`, where `interestLate` is calculated using the formula above. Note that `principalPeriodic == principalLate` and `interestLate > interestPeriodic` are used only when the payment is late. Otherwise, `interestLate == interestPeriodic`. $$ @@ -1481,7 +1504,7 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - Increase the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `principal_paid + (interest_paid - management_fee)`. - + - If `LoanBroker.CoverAvailable >= LoanBroker.DebtTotal x LoanBroker.CoverRateMinimum`: - Increase the `Balance` field of the `LoanBroker.Owner` `AccountRoot` by `fee_paid + management_fee`. From 14a634f9fc34a72b650f93f135b6e267b3e8fdb5 Mon Sep 17 00:00:00 2001 From: Vito Date: Fri, 31 Jan 2025 13:07:00 +0000 Subject: [PATCH 13/41] updates failure conditions of LoanSet transaction --- XLS-0066d-lending-protocol/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 171ae766..44b4c39d 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -790,11 +790,16 @@ Either of the parties (Borrower or Loan Issuer) may initiate the transaction. Th 5. The `Borrower` signs the transaction, filling the `CounterpartySignature` field. 6. The `Borrower` submits the transaction. -##### 3.2.1.4 Failure Conditions +##### 3.2.1.4 Fees + +The account specified in the `Account` field pays the transaction fee. + +##### 3.2.1.5 Failure Conditions - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. -- The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. -- `Lender. Signature` is invalid. +- If neither the `Account` or the `Counterparty` field are the `LoanBroker.Owner`. +- If the `Counterparty` field is not specified and the submitting account is not `LoanBroker.Owner`. +- If the `Counterparty.Signature` is invalid. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: @@ -826,7 +831,7 @@ Either of the parties (Borrower or Loan Issuer) may initiate the transaction. Th - Insufficient First-Loss Capital: - `LoanBroker(LoanBrokerID).CoverAvailable` < `(LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested) x LoanBroker(LoanBrokerID).CoverRateMinimum` -##### 3.2.1.5 State Changes +##### 3.2.1.6 State Changes - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: @@ -867,7 +872,7 @@ Either of the parties (Borrower or Loan Issuer) may initiate the transaction. Th - Add `LoanID` to `DirectoryNode.Indexes` of the `LoanBroker` _pseudo-account_ `AccountRoot`. - Add `LoanID` to `DirectoryNode.Indexes` of the `Borrower` `AccountRoot`. -##### 3.2.1.4 Invariants +##### 3.2.1.7 Invariants **TBD** From b77401b6508f220c874b4c0a1f7fd47d17664e42 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 13 Mar 2025 09:44:09 +0100 Subject: [PATCH 14/41] Update XLS-0066d-lending-protocol/README.md Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 44b4c39d..027b24c2 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -450,7 +450,7 @@ The `LoanID` is calculated as follows: | `LoanServiceFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | | `LatePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | | `ClosePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | -| `OveraymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `InterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | | `LateInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `CloseInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | From aa50126a96d4064ff6a65bac76e878d345763d29 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:30:05 +0100 Subject: [PATCH 15/41] addresses PR comments --- XLS-0066d-lending-protocol/README.md | 134 +++++++++++++-------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 027b24c2..a869ecd7 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -200,27 +200,27 @@ The key of the `LoanBroker` object is the result of [`SHA512-Half`](https://xrpl The `LoanBroker` object has the following fields: -| Field Name | Modifiable? | Required? | JSON Type | Internal Type | Default Value | Description | -| ---------------------- | :---------: | :----------------: | :-------: | :-----------: | :-----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `LedgerEntryType` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | -| `LedgerIndex` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | -| `Flags` | `Yes` | :heavy_check_mark: | `string` | `UINT32` | 0 | Ledger object flags. | -| `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | -| `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger containing the transaction that last modified this object. | -| `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the `LoanBroker`. | -| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | -| `VaultNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the Vault's _pseudo-account_ owner's directory. | -| `VaultID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | -| `Account` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the `LoanBroker` _pseudo-account_. | -| `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the Loan Broker account. | -| `Data` | `Yes` | | `string` | `BLOB` | None | Arbitrary metadata about the `LoanBroker`. Limited to 256 bytes. | -| `ManagementFeeRate` | `No` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol. Valid values are between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001% | -| `OwnerCount` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | 0 | The number of active Loans issued by the `LoanBroker`. | -| `DebtTotal` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total asset amount the protocol owes the Vault, including interest. | -| `DebtMaximum` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | -| `CoverAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total amount of first-loss capital deposited into the Lending Protocol. | -| `CoverRateMinimum` | `No` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | -| `CoverRateLiquidation` | `No` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | +| Field Name | User Modifiable? | Constant? | Required? | JSON Type | Internal Type | Default Value | Description | +| ---------------------- | :--------------: | :-------: | :----------------: | :-------: | :-----------: | :-----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `LedgerEntryType` | `No` | `Yes` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | +| `LedgerIndex` | `No` | `Yes` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | +| `Flags` | `Yes` | `No` | :heavy_check_mark: | `string` | `UINT32` | 0 | Ledger object flags. | +| `PreviousTxnID` | `No` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | +| `PreviousTxnLgrSeq` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger containing the transaction that last modified this object. | +| `Sequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the `LoanBroker`. | +| `OwnerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | +| `VaultNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the Vault's _pseudo-account_ owner's directory. | +| `VaultID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | +| `Account` | `No` | `Yes` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the `LoanBroker` _pseudo-account_. | +| `Owner` | `No` | `Yes` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the Loan Broker account. | +| `Data` | `Yes` | `No` | | `string` | `BLOB` | None | Arbitrary metadata about the `LoanBroker`. Limited to 256 bytes. | +| `ManagementFeeRate` | `No` | `Yes` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol. Valid values are between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001% | +| `OwnerCount` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | 0 | The number of active Loans issued by the `LoanBroker`. | +| `DebtTotal` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total asset amount the protocol owes the Vault, including interest. | +| `DebtMaximum` | `Yes` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | +| `CoverAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total amount of first-loss capital deposited into the Lending Protocol. | +| `CoverRateMinimum` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | +| `CoverRateLiquidation` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | #### 2.1.3 `LoanBroker `_pseudo-account_` @@ -434,45 +434,45 @@ The `LoanID` is calculated as follows: #### 2.2.2 Fields -| Field Name | Modifiable? | Required? | JSON Type | Internal Type | Default Value | Description | -| ------------------------- | :---------: | :----------------: | :-------: | :-----------: | :-------------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `LedgerEntryType` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | -| `LedgerIndex` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | -| `Flags` | `Yes` | | `string` | `UINT32` | 0 | Ledger object flags. | -| `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | -| `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | -| `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | -| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `Borrower` owner's directory. | -| `LoanBrokerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | -| `LoanBrokerID` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | -| `Borrower` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | -| `LoanOriginationFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | -| `LoanServiceFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | -| `LatePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | -| `ClosePaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | -| `OverpaymentFee` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `InterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | -| `LateInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `CloseInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `OverpaymentInterestRate` | `No` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `StartDate` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentInterval` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Number of seconds between Loan payments. | -| `GracePeriod` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | -| `PreviousPaymentDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `NextPaymentDueDate` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentRemaining` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | -| `AssetAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | -| `PrincipalOutstanding` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | +| Field Name | User Modifiable? | Constant? | Required? | JSON Type | Internal Type | Default Value | Description | +| ------------------------- | :--------------: | :-------: | :----------------: | :-------: | :-----------: | :-------------------------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `LedgerEntryType` | `No` | `Yes` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Ledger object type. | +| `LedgerIndex` | `No` | `Yes` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. | +| `Flags` | `Yes` | `No` | | `string` | `UINT32` | 0 | Ledger object flags. | +| `PreviousTxnID` | `No` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | +| `PreviousTxnLgrSeq` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | +| `Sequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | +| `OwnerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `Borrower` owner's directory. | +| `LoanBrokerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | +| `LoanBrokerID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | +| `Borrower` | `No` | `Yes` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | +| `LoanOriginationFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanServiceFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | +| `LatePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | +| `ClosePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | +| `OveraymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `InterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | +| `LateInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `CloseInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `StartDate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentInterval` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Number of seconds between Loan payments. | +| `GracePeriod` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | +| `PreviousPaymentDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `NextPaymentDueDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentRemaining` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | +| `AssetAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `PrincipalOutstanding` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | ##### 2.2.2.1 Flags The `Loan` object supports the following flags: -| Flag Name | Flag Value | Modifiable? | Description | -| -------------------- | :--------: | :---------: | :----------------------------------------------------: | -| `lsfLoanDefault` | `0x0001` | `No` | If set, indicates that the Loan is defaulted. | -| `lsfLoanImpaired` | `0x0002` | `Yes` | If set, indicates that the Loan is impaired. | -| `lsfLoanOverpayment` | `0x0004` | `No` | If set, indicates that the Loan supports overpayments. | +| Flag Name | Flag Value | Modifiable? | Description | +| -------------------- | :----------: | :---------: | :----------------------------------------------------: | +| `lsfLoanDefault` | `0x00010000` | `No` | If set, indicates that the Loan is defaulted. | +| `lsfLoanImpaired` | `0x00020000` | `Yes` | If set, indicates that the Loan is impaired. | +| `lsfLoanOverpayment` | `0x00040000` | `No` | If set, indicates that the Loan supports overpayments. | #### 2.2.3 Ownership @@ -561,7 +561,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------------------- | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | -| `LoanBrokerID` | | `string` | `HASH256` | `N/A` | The Loan Broker ID that the transaction is deleting. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID that the transaction is deleting. | ##### 3.1.2.1 Failure Conditions @@ -738,9 +738,9 @@ The transaction creates a new `Loan` object. ##### 3.2.1.1 `Flags` -| Flag Name | Flag Value | Description | -| ------------------- | :--------: | :---------------------------------------------- | -| `tfLoanOverpayment` | `0x0001` | Indicates that the vault supports overpayments. | +| Flag Name | Flag Value | Description | +| ------------------- | :----------: | :---------------------------------------------- | +| `tfLoanOverpayment` | `0x00010000` | Indicates that the vault supports overpayments. | ##### 3.2.1.2 `CounterpartySignature` @@ -749,16 +749,16 @@ An inner object that contains the signature of the Lender over the transaction. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | --------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | | `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | -| `Signature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | +| `TxSignature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | | `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | -The final transaction must include `Signature` or `Signers`. +The final transaction must include `TxSignature` or `Signers` fields. If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base_fee$. -This field is not a signing field (it will not be included in transaction signatures, though the `Signature` or `Signers` field will be included in the stored transaction). +This field is not a signing field (it will not be included in transaction signatures, though the `TxSignature` or `Signers` field will be included in the stored transaction). ##### 3.2.1.3 Multi-Signing @@ -940,11 +940,11 @@ The transaction deletes an existing `Loan` object. ##### 3.2.3.1 `Flags` -| Flag Name | Flag Value | Description | -| ---------------- | :--------: | :--------------------------------------------- | -| `tfLoanDefault` | `0x0001` | Indicates that the Loan should be defaulted. | -| `tfLoanImpair` | `0x0002` | Indicates that the Loan should be impaired. | -| `tfLoanUnimpair` | `0x0004` | Indicates that the Loan should be un-impaired. | +| Flag Name | Flag Value | Description | +| ---------------- | :----------: | :--------------------------------------------- | +| `tfLoanDefault` | `0x00010000` | Indicates that the Loan should be defaulted. | +| `tfLoanImpair` | `0x00020000` | Indicates that the Loan should be impaired. | +| `tfLoanUnimpair` | `0x00040000` | Indicates that the Loan should be un-impaired. | ##### 3.2.3.1 Failure Conditions From 9acddc13041366dfcae1fbda44a7727859733c8f Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Wed, 19 Mar 2025 11:31:50 +0100 Subject: [PATCH 16/41] fixes signature field name --- XLS-0066d-lending-protocol/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index a869ecd7..51c259a0 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -749,16 +749,16 @@ An inner object that contains the signature of the Lender over the transaction. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | --------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | | `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | -| `TxSignature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | +| `TxnSignature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | | `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | -The final transaction must include `TxSignature` or `Signers` fields. +The final transaction must include `TxnSignature` or `Signers` fields. If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base_fee$. -This field is not a signing field (it will not be included in transaction signatures, though the `TxSignature` or `Signers` field will be included in the stored transaction). +This field is not a signing field (it will not be included in transaction signatures, though the `TxnSignature` or `Signers` field will be included in the stored transaction). ##### 3.2.1.3 Multi-Signing @@ -799,7 +799,7 @@ The account specified in the `Account` field pays the transaction fee. - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. - If neither the `Account` or the `Counterparty` field are the `LoanBroker.Owner`. - If the `Counterparty` field is not specified and the submitting account is not `LoanBroker.Owner`. -- If the `Counterparty.Signature` is invalid. +- If the `Counterparty.TxnSignature` is invalid. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: From 294f58280e92cb905df29b5351f7a736122e565c Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:42:47 +0200 Subject: [PATCH 17/41] changes internal type of cover rate variables --- XLS-0066d-lending-protocol/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 51c259a0..1e68126f 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -219,8 +219,8 @@ The `LoanBroker` object has the following fields: | `DebtTotal` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total asset amount the protocol owes the Vault, including interest. | | `DebtMaximum` | `Yes` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | | `CoverAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total amount of first-loss capital deposited into the Lending Protocol. | -| `CoverRateMinimum` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | -| `CoverRateLiquidation` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | +| `CoverRateMinimum` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | +| `CoverRateLiquidation` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | #### 2.1.3 `LoanBroker `_pseudo-account_` @@ -510,8 +510,8 @@ The transaction creates a new `LoanBroker` object or updates an existing one. | `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | | `ManagementFeeRate` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol Owner. Valid values are between 0 and 10000 inclusive. | | `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | -| `CoverRateMinimum` | | `number` | `UINT16` | 0 | The 1/10th basis point `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. | -| `CoverRateLiquidation` | | `number` | `UINT16` | 0 | The 1/10th basis point of minimum required first loss capital liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. | +| `CoverRateMinimum` | | `number` | `UINT32` | 0 | The 1/10th basis point `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. | +| `CoverRateLiquidation` | | `number` | `UINT32` | 0 | The 1/10th basis point of minimum required first loss capital liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. | ##### 3.1.1.1 Failure Conditions From 8740e2f01afc05c8740d6f110ea58cd24487ba23 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:43:26 +0200 Subject: [PATCH 18/41] renames singular vault attributes to plural --- XLS-0066d-lending-protocol/README.md | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 1e68126f..2c0ba596 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -251,9 +251,9 @@ Example 1: # Issuing a Loan # ** Initial States ** -- Vault -- -AssetTotal = 100,000 Tokens -AssetAvailable = 100,000 Tokens -ShareTotal = 100,000 Shares +AssetsTotal = 100,000 Tokens +AssetsAvailable = 100,000 Tokens +SharesTotal = 100,000 Shares -- Lending Protocol -- DebtTotal = 0 @@ -274,16 +274,16 @@ LoanInterest = LoanPrincipal x LoanInterestRate -- Vault -- # Increase the potential value of the Vault -AssetTotal = AssetTotal + ((LoanInterest - (LoanInterest x ManagementFeeRate))) +AssetsTotal = AssetsTotal + ((LoanInterest - (LoanInterest x ManagementFeeRate))) = 100,000 + (100 - (100 x 0.1)) = 100,000 + 90 = 100,090 Tokens # Decrease Asset Available in the Vault -AssetAvailable = AssetAvailable - LoanPrincipal +AssetsAvailable = AssetsAvailable - LoanPrincipal = 100,000 - 1,000 = 99,000 Tokens -ShareTotal = (UNCHANGED) +SharesTotal = (UNCHANGED) -- Lending Protocol -- # Increase Lending Protocol Debt @@ -299,9 +299,9 @@ Example 2: # Loan Payment # ** Initial States ** -- Vault -- -AssetTotal = 100,090 Tokens -AssetAvailable = 99,000 Tokens -ShareTotal = 100,000 Shares +AssetsTotal = 100,090 Tokens +AssetsAvailable = 99,000 Tokens +SharesTotal = 100,000 Shares -- Lending Protocol -- DebtTotal = 1,090 Tokens @@ -329,14 +329,14 @@ PaymentInterestPortion = 50 Tokens ** State Changes ** -- Vault -- -AssetTotal = (UNCHANGED) +AssetsTotal = (UNCHANGED) # Increase Asset Available in the Vault -AssetAvailable = AssetAvailable + PaymentPrincipalPortion + (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) +AssetsAvailable = AssetsAvailable + PaymentPrincipalPortion + (PaymentInterestPortion - (PaymentInterestPortion x ManagementFeeRate) = 99,000 + 500 + (50 - (50 x 0.1)) = 99,545 Tokens -ShareTotal = (UNCHANGED) +SharesTotal = (UNCHANGED) -- Lending Protocol -- @@ -368,9 +368,9 @@ Example 1: Loan Default ** Initial States ** -- Vault -- -AssetTotal = 100,090 Tokens -AssetAvailable = 99,000 Tokens -ShareTotal = 100,000 Tokens +AssetsTotal = 100,090 Tokens +AssetsAvailable = 99,000 Tokens +SharesTotal = 100,000 Tokens -- Lending Protocol -- DebtTotal = 1,090 Tokens @@ -396,15 +396,15 @@ DefaultRemaining = DefaultAmount - DefaultCovered ** State Changes ** -- Vault -- -AssetTotal = AssetTotal - DefaultRemaining +AssetsTotal = AssetsTotal - DefaultRemaining = 100,090 - 1,079.1 = 99,010.9 Tokens -AssetAvailable = AssetAvailable + DefaultCovered +AssetsAvailable = AssetsAvailable + DefaultCovered = 99,000 + 10.9 = 99,010.9 Tokens -ShareTotal = (UNCHANGED) +SharesTotal = (UNCHANGED) -- Lending Protocol -- DebtTotal = DebtTotal - DefaultAmount @@ -461,7 +461,7 @@ The `LoanID` is calculated as follows: | `PreviousPaymentDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `NextPaymentDueDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `PaymentRemaining` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | -| `AssetAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `AssetsAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | | `PrincipalOutstanding` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | ##### 2.2.2.1 Flags @@ -822,7 +822,7 @@ The account specified in the `Account` field pays the transaction fee. - Insufficient assets in the Vault: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetAvailable` < `Loan.PrincipalRequested`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable` < `Loan.PrincipalRequested`. - Exceeds maximum Debt of the LoanBroker: @@ -859,10 +859,10 @@ The account specified in the `Account` field pays the transaction fee. - Decrease Asset Available in the Vault: - - `Vault.AssetAvailable -= Loan.PrincipalRequested`. + - `Vault.AssetsAvailable -= Loan.PrincipalRequested`. - Increase the Total Value of the Vault: - - `Vault.AssetTotal += LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` where `LoanInterest` is the Loan's total interest. + - `Vault.AssetsTotal += LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` where `LoanInterest` is the Loan's total interest. - `LoanBroker(LoanBrokerID)` object changes: @@ -896,22 +896,22 @@ The transaction deletes an existing `Loan` object. ##### 3.2.2.2 State Changes -- If `Loan(LoanID).AssetAvailable > 0` (transfer remaining funds to the borrower): +- If `Loan(LoanID).AssetsAvailable > 0` (transfer remaining funds to the borrower): - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan(LoanID).AssetAvailable`. - - Increase the `Balance` field of `Loan(LoanID).Borrower` `AccountRoot` by `Loan(LoanID).AssetAvailable`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Increase the `Balance` field of `Loan(LoanID).Borrower` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetAvailable`. - - Increase the `RippleState` balance between the `Loan(LoanID).Borrower` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetAvailable`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Increase the `RippleState` balance between the `Loan(LoanID).Borrower` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetAvailable`. - - Increase the `MPToken.MPTAmount` of the `Loan(LoanID).Borrower` `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetAvailable` + - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable`. + - Increase the `MPToken.MPTAmount` of the `Loan(LoanID).Borrower` `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable` - Delete the `Loan` object. @@ -966,7 +966,7 @@ The transaction deletes an existing `Loan` object. - Calculate the amount of the Default that First-Loss Capital covers: - The default Amount equals the outstanding principal and interest, excluding any funds unclaimed by the Borrower. - - `DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - Loan.AssetAvailable`. + - `DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) - Loan.AssetsAvailable`. - Apply the First-Loss Capital to the Default Amount - `DefaultCovered = min((LoanBroker(Loan.LoanBrokerID).DebtTotal x LoanBroker(Loan.LoanBrokerID).CoverRateMinimum) x LoanBroker(Loan.LoanBrokerID).CoverRateLiquidation, DefaultAmount)` - `DefaultAmount -= DefaultCovered` @@ -974,15 +974,15 @@ The transaction deletes an existing `Loan` object. - Update the `Vault` object: - Decrease the Total Value of the Vault: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetTotal -= DefaultAmount`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsTotal -= DefaultAmount`. - Increase the Asset Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetAvailable += DefaultCovered + Loan.AssetAvailable`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += DefaultCovered + Loan.AssetsAvailable`. - Update the `LoanBroker` object: - Decrease the Debt of the LoanBroker: - `LoanBroker(LoanBrokerID).DebtTotal -= ` - - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetAvailable` + - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetsAvailable` - Decrease the First-Loss Capital Cover Available: - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` - Decrease the number of active Loans: @@ -992,7 +992,7 @@ The transaction deletes an existing `Loan` object. - `Loan(LoanID).Flags = lsfLoanDefault` - `Loan(LoanID).PaymentRemaining = 0` - - `Loan(LoanID).AssetAvailable = 0` + - `Loan(LoanID).AssetsAvailable = 0` - `Loan(LoanID).PrincipalOutstanding = 0` - Move the First-Loss Capital from the `LoanBroker` _pseudo-account_ to the `Vault` _pseudo-account_: @@ -1062,7 +1062,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - `Loan.StartDate > LastClosedLedger.CloseTime`. - There are insufficient assets in the `Loan`: - - `Loan.AssetAvailable` < `Amount`. + - `Loan.AssetsAvailable` < `Amount`. - The `Loan` has `lsfLoanImpaired` or `lsfLoanDefault` flags set. @@ -1097,7 +1097,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - Decrease the `MPToken.MPTAmount` by `Amount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. -- Decrease `Loan.AssetAvailable` by `Amount`. +- Decrease `Loan.AssetsAvailable` by `Amount`. ##### 3.2.4.3 Invariants @@ -1189,7 +1189,7 @@ $$ latePaymentInterest = principalOutstanding \times \frac{lateInterestRate \times secondsSinceLastPayment}{365 \times 24 \times 60 \times 60} $$ -A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetTotal` must be recalculated. +A late payment pays more interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the total Vault value captured by `Vault.AssetsTotal` must be recalculated. Assume the function `PeriodicPayment()` returns the expected periodic payment, split into `principalPeriodic` and `interestPeriodic`. Furthermore, assume the function `LatePayment()` that implements the Late Payment formula. The function returns the late payment split into `principalLate` and `interestLate`, where `interestLate` is calculated using the formula above. Note that `principalPeriodic == principalLate` and `interestLate > interestPeriodic` are used only when the payment is late. Otherwise, `interestLate == interestPeriodic`. @@ -1250,7 +1250,7 @@ $$ prepaymentPenalty = principalOutstanding \times closeInterestRate $$ -An early payment pays less interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the Vault value (captured by `Vault.AssetTotal`) must be recalculated after an early payment. +An early payment pays less interest than calculated when increasing the Vault value in the `LoanSet` transaction. Therefore, the Vault value (captured by `Vault.AssetsTotal`) must be recalculated after an early payment. Assume a function `CurrentValue()` that returns `principalOutstanding` and `interestOutstanding` of the Loan. Furthermore, assume a function `ClosePayment()` that implements the Full Payment calculation. The function returns the total full payment due split into `principal` and `interest`. @@ -1496,15 +1496,15 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - Increase available assets in the Vault by the amount paid: - - `Vault.AssetAvailable = Vault.AssetAvailable + totalPaid` + - `Vault.AssetsAvailable = Vault.AssetsAvailable + totalPaid` - Update the Vault total value by the change in the Loan total value: - - `Vault.AssetTotal = Vault.AssetTotal + valueChange` + - `Vault.AssetsTotal = Vault.AssetsTotal + valueChange` - Update the Vault total value by the change in the management fee: - - `Vault.AssetTotal = Vault.AssetTotal - (vaultChange x LoanBroker.managementFeeRate)` + - `Vault.AssetsTotal = Vault.AssetsTotal - (vaultChange x LoanBroker.managementFeeRate)` - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: From b1754024b33d91874ff1de67f9c515ed8aff1713 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:50:42 +0200 Subject: [PATCH 19/41] Apply suggestions from code review Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 2c0ba596..990ca44c 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -509,7 +509,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. | `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Lending Protocol. | | `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | | `ManagementFeeRate` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol Owner. Valid values are between 0 and 10000 inclusive. | -| `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. | +| `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. Must not be negative. | | `CoverRateMinimum` | | `number` | `UINT32` | 0 | The 1/10th basis point `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. | | `CoverRateLiquidation` | | `number` | `UINT32` | 0 | The 1/10th basis point of minimum required first loss capital liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. | @@ -595,7 +595,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. ##### 3.1.2.3 Invariants -- If `LoanBroker.OwnerCount = 0` the `DirectoryNode` for the `LoanBroker` does not exist +- If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one node (the root), which will only hold entries for `RippleState` or `MPToken` objects. [**Return to Index**](#index) @@ -616,7 +616,7 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - - `AccountRoot(LoanBroker.Owner).Balance < Amount` (LoanBroker does not have sufficient funds to deposit the First Loss Capital). + - `AccountRoot(LoanBroker.Owner).Balance - Reserve(AccountRoot(LoanBroker.Owner).OwnerCount) < Amount` (LoanBroker does not have sufficient funds to deposit the First Loss Capital). - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: @@ -666,7 +666,7 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from | `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. | | `Amount` | :heavy_check_mark: | `object` | `NUMBER` | 0 | The amount of Vault asset to withdraw. | -##### 3.2.2.1 Failure conditions +##### 3.1.4.1 Failure conditions - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. @@ -686,7 +686,7 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - `LoanBroker.CoverAvailable - Amount` < `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum` -##### 3.2.2.2 State Changes +##### 3.1.4.2 State Changes - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: @@ -748,15 +748,17 @@ An inner object that contains the signature of the Lender over the transaction. | Field Name | Required? | JSON Type | Internal Type | Default Value | Description | | --------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | -| `SigningPubKey` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | -| `TxnSignature` | :heavy_check_mark: | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | -| `Signers` | :heavy_check_mark: | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | +| `SigningPubKey` | | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | +| `TxnSignature` | | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | +| `Signers` | | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | -The final transaction must include `TxnSignature` or `Signers` fields. +The final transaction must include exactly one of +1. The `SigningPubKey` and `TxnSignature` fields, or +2. The `Signers` field. -If the `Signers` field is necessary, then the total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ +The total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ where $|signatures| == max(1, |tx.CounterPartySignature.Signers|)$ -The total fee calculation for signatures will now be $(1 + |tx.Signers| + |tx.Lender.Signers|) \times base_fee$. +The total fee calculation for signatures will now be $(1 + |tx.Signers| + |signatures|) \times base_fee$. In other words, even without a `tx.Signers` list, the minimum fee will be $2 \times base_fee$. This field is not a signing field (it will not be included in transaction signatures, though the `TxnSignature` or `Signers` field will be included in the stored transaction). From 36ec7cb78414760032a6cbf5c12bbb14531a6ad9 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:07:02 +0200 Subject: [PATCH 20/41] changes Amount type to AMOUNT --- XLS-0066d-lending-protocol/README.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 990ca44c..37016f06 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -461,7 +461,7 @@ The `LoanID` is calculated as follows: | `PreviousPaymentDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `0` | The timestamp of when the previous payment was made in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `NextPaymentDueDate` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.StartDate + LoanSet.PaymentInterval` | The timestamp of when the next payment is due in [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `PaymentRemaining` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `LoanSet.PaymentTotal` | The number of payments remaining on the Loan. | -| `AssetsAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | +| `AssetsAvailable` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.[PrincipalRequested - LoanOriginationFee]` | The asset amount that is available in the Loan. | | `PrincipalOutstanding` | `No` | `No` | :heavy_check_mark: | `number` | `NUMBER` | `LoanSet.PrincipalRequested` | The principal amount requested by the Borrower. | ##### 2.2.2.1 Flags @@ -509,7 +509,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. | `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Lending Protocol. | | `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | | `ManagementFeeRate` | | `number` | `UINT16` | 0 | The 1/10th basis point fee charged by the Lending Protocol Owner. Valid values are between 0 and 10000 inclusive. | -| `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. Must not be negative. | +| `DebtMaximum` | | `number` | `NUMBER` | 0 | The maximum amount the protocol can owe the Vault. The default value of 0 means there is no limit to the debt. Must not be negative. | | `CoverRateMinimum` | | `number` | `UINT32` | 0 | The 1/10th basis point `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. | | `CoverRateLiquidation` | | `number` | `UINT32` | 0 | The 1/10th basis point of minimum required first loss capital liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. | @@ -607,7 +607,7 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------ | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | | `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID to deposit First-Loss Capital. | -| `Amount` | :heavy_check_mark: | `object` | `NUMBER` | 0 | The Fist-Loss Capital amount to deposit. | +| `Amount` | :heavy_check_mark: | `object` | `AMOUNT` | 0 | The Fist-Loss Capital amount to deposit. | ##### 3.1.3.1 Failure Conditions @@ -664,7 +664,7 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------ | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Transaction type. | | `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. | -| `Amount` | :heavy_check_mark: | `object` | `NUMBER` | 0 | The amount of Vault asset to withdraw. | +| `Amount` | :heavy_check_mark: | `object` | `AMOUNT` | 0 | The amount of Vault asset to withdraw. | ##### 3.1.4.1 Failure conditions @@ -746,13 +746,14 @@ The transaction creates a new `Loan` object. An inner object that contains the signature of the Lender over the transaction. The fields contained in this object are: -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| --------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | -| `SigningPubKey` | | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | -| `TxnSignature` | | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | -| `Signers` | | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| --------------- | :-------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------- | +| `SigningPubKey` | | `string` | `STBlob` | `N/A` | The Public Key to be used to verify the validity of the signature. | +| `TxnSignature` | | `string` | `STBlob` | `N/A` | The signature of over all signing fields. | +| `Signers` | | `list` | `STArray` | `N/A` | An array of transaction signatures from the `Counterparty` signers to indicate their approval of this transaction. | + +The final transaction must include exactly one of -The final transaction must include exactly one of 1. The `SigningPubKey` and `TxnSignature` fields, or 2. The `Signers` field. @@ -1054,7 +1055,7 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------ | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | | `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be drawn from. | -| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to drawdown. | +| `Amount` | :heavy_check_mark: | `number` | `AMOUNT` | `N/A` | The amount of funds to drawdown. | ##### 3.2.4.1 Failure Conditions @@ -1115,7 +1116,7 @@ The Borrower submits a `LoanPay` transaction to make a Payment on the Loan. | ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------------- | | `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | | `LoanID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the Loan object to be paid to. | -| `Amount` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The amount of funds to pay. | +| `Amount` | :heavy_check_mark: | `number` | `AMOUNT` | `N/A` | The amount of funds to pay. | ##### 3.2.5.1 Payment Types From 6c9a815033dcd733a0c45ecb5a2c00f900301fe4 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:07:24 +0200 Subject: [PATCH 21/41] adds owner reserve chages to LoanSet --- XLS-0066d-lending-protocol/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 37016f06..e50dfe71 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -836,6 +836,9 @@ The account specified in the `Account` field pays the transaction fee. ##### 3.2.1.6 State Changes +- Create the `Loan` object. +- Increment `AccountRoot(Borrower).OwnerCount` by `1` + - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - Decrease the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `Loan.PrincipalRequested`. From ea0f369e51dd143d0c5d43f22c6c0af7c08c06ca Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:10:55 +0200 Subject: [PATCH 22/41] changes InterestRate type to uint32 --- XLS-0066d-lending-protocol/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index e50dfe71..c821b9f2 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -451,10 +451,10 @@ The `LoanID` is calculated as follows: | `LatePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | | `ClosePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | | `OveraymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `InterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | -| `LateInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `CloseInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `OverpaymentInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT16` | `N/A` | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `InterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | +| `LateInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `CloseInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `StartDate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `PaymentInterval` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Number of seconds between Loan payments. | | `GracePeriod` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The number of seconds after the Payment Due Date that the Loan can be Defaulted. | @@ -727,9 +727,9 @@ The transaction creates a new `Loan` object. | `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | | `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | | `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | -| `InterestRate` | | `number` | `UINT16` | 0 | Annualized interest rate of the Loan in basis points. | -| `LateInterestRate` | | `number` | `UINT16` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 10000 inclusive. (0 - 100%) | -| `CloseInterestRate` | | `number` | `UINT16` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `InterestRate` | | `number` | `UINT32` | 0 | Annualized interest rate of the Loan in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `LateInterestRate` | | `number` | `UINT32` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `CloseInterestRate` | | `number` | `UINT32` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | | `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | | `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | From 0b136098edb160d9efff34c3eff42ae75bd7d8ba Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:21:09 +0200 Subject: [PATCH 23/41] adds missing overpayment fields to LoanSet transaction --- XLS-0066d-lending-protocol/README.md | 46 +++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index c821b9f2..989e7f88 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -450,7 +450,7 @@ The `LoanID` is calculated as follows: | `LoanServiceFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | | `LatePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | | `ClosePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | -| `OveraymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `InterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | Annualized interest rate of the Loan in 1/10th basis points. | | `LateInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | A premium is added to the interest rate for late payments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | | `CloseInterestRate` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | An interest rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | @@ -715,26 +715,28 @@ In this section we specify transactions associated with the `Loan` ledger entry. The transaction creates a new `Loan` object. -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| ----------------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------- | -| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | -| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID associated with the loan. | -| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | -| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | -| `Counterparty` | | `string` | `AccountID` | `N/A` | The address of the counterparty of the Loan. | -| `CounterpartySignature` | :heavy_check_mark: | `string` | `STObject` | `N/A` | The signature of the counterparty over the transaction. | -| `LoanOriginationFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | -| `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | -| `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | -| `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | -| `InterestRate` | | `number` | `UINT32` | 0 | Annualized interest rate of the Loan in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `LateInterestRate` | | `number` | `UINT32` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `CloseInterestRate` | | `number` | `UINT32` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | -| `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | -| `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | -| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | -| `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | -| `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ------------------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | The transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID associated with the loan. | +| `Flags` | | `string` | `UINT32` | 0 | Specifies the flags for the Loan. | +| `Data` | | `string` | `BLOB` | None | Arbitrary metadata in hex format. The field is limited to 256 bytes. | +| `Counterparty` | | `string` | `AccountID` | `N/A` | The address of the counterparty of the Loan. | +| `CounterpartySignature` | :heavy_check_mark: | `string` | `STObject` | `N/A` | The signature of the counterparty over the transaction. | +| `LoanOriginationFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanServiceFee` | | `number` | `NUMBER` | 0 | A nominal amount paid to the `LoanBroker.Owner` with every Loan payment. | +| `LatePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | +| `ClosePaymentFee` | | `number` | `NUMBER` | 0 | A nominal funds amount paid to the `LoanBroker.Owner` when an early full repayment is made. | +| `OverpaymentFee` | | `number` | `UINT32` | 0 | A fee charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `InterestRate` | | `number` | `UINT32` | 0 | Annualized interest rate of the Loan in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `LateInterestRate` | | `number` | `UINT32` | 0 | A premium added to the interest rate for late payments in basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `CloseInterestRate` | | `number` | `UINT32` | 0 | A Fee Rate charged for repaying the Loan early in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `OverpaymentInterestRate` | | `number` | `UINT32` | 0 | An interest rate charged on overpayments in 1/10th basis points. Valid values are between 0 and 100000 inclusive. (0 - 100%) | +| `PrincipalRequested` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | The principal amount requested by the Borrower. | +| `StartDate` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The timestamp of when the Loan starts [Ripple Epoch](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#specifying-time). | +| `PaymentTotal` | | `number` | `UINT32` | 1 | The total number of payments to be made against the Loan. | +| `PaymentInterval` | | `number` | `UINT32` | 60 | Number of seconds between Loan payments. | +| `GracePeriod` | | `number` | `UINT32` | 60 | The number of seconds after the Loan's Payment Due Date can be Defaulted. | ##### 3.2.1.1 `Flags` @@ -1397,7 +1399,7 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v loan.principal_outstanding -= total_principal_paid let overpayment = min(loan.principal_outstanding, amount % periodic_payment) - if overpayment > 0 && is_set(lsfOverayment) { + if overpayment > 0 && is_set(lsfOverpayment) { let interest_portion = overpayment * loan.overpayment_interest_rate let fee_portion = overpayment * loan.overpayment_fee let remainder = overpayment - interest_portion - fee_portion From b631e671077952d015f04f056d365a5418fc7f3a Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:20:07 +0200 Subject: [PATCH 24/41] adds pseudo-account locked/frozen checks to LoanBrokerCoverWithdraw --- XLS-0066d-lending-protocol/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 989e7f88..eba50fbc 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -630,6 +630,7 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. - Has `lsfMPTLocked` flag set. - `MPTAmount` < `Amount`. - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set (the asset is not transferable). ##### 3.1.3.2 State Changes @@ -673,15 +674,17 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: - - Has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set (the asset is not transferable). + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). + - The `LoanBroker.CoverAvailable` < `Amount`. - `LoanBroker.CoverAvailable - Amount` < `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum` From ff058bfc615f5601acea0662f76b2f29c31a9392 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:06:54 +0200 Subject: [PATCH 25/41] Update XLS-0066d-lending-protocol/README.md Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index eba50fbc..d758e256 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -806,7 +806,7 @@ The account specified in the `Account` field pays the transaction fee. - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. - If neither the `Account` or the `Counterparty` field are the `LoanBroker.Owner`. -- If the `Counterparty` field is not specified and the submitting account is not `LoanBroker.Owner`. +- If the `Counterparty` field is not specified and the `CounterpartySignature` is not from the `LoanBroker.Owner`. - If the `Counterparty.TxnSignature` is invalid. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: From 13987664c50540b55a2a1fcd29f0ec1973309574 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Thu, 1 May 2025 11:48:49 +0200 Subject: [PATCH 26/41] Improves how Loan ID is calculated by introducing a new LoanSequence field --- XLS-0066d-lending-protocol/README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index d758e256..ba7f4821 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -208,6 +208,7 @@ The `LoanBroker` object has the following fields: | `PreviousTxnID` | `No` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | | `PreviousTxnLgrSeq` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger containing the transaction that last modified this object. | | `Sequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the `LoanBroker`. | +| `LoanSequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | 0 | A sequential identifier for Loan objects, incremented each time a new Loan is created by this LoanBroker instance. | | `OwnerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. | | `VaultNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the Vault's _pseudo-account_ owner's directory. | | `VaultID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `Vault` object associated with this Lending Protocol Instance. | @@ -430,7 +431,7 @@ The `LoanID` is calculated as follows: - The `Loan` space key `0x004C` (capital L) - The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the Borrower account. - The `LoanBrokerID` of the associated `LoanBroker` object. - - The `Sequence` number of the **`LoanSet`** transaction. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value. + - The `LoanSequence` of the `LoanBroker` object. #### 2.2.2 Fields @@ -442,6 +443,7 @@ The `LoanID` is calculated as follows: | `PreviousTxnID` | `No` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | | `PreviousTxnLgrSeq` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | | `Sequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | +| `LoanSequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence number of the Loan. | | `OwnerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `Borrower` owner's directory. | | `LoanBrokerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | | `LoanBrokerID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | @@ -842,7 +844,8 @@ The account specified in the `Account` field pays the transaction fee. ##### 3.2.1.6 State Changes - Create the `Loan` object. -- Increment `AccountRoot(Borrower).OwnerCount` by `1` +- Increment `AccountRoot(Borrower).OwnerCount` by `1`. +- Increment `LoanBroker.LoanSequence` by `1`. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: @@ -1569,3 +1572,9 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p **TBD** # Appendix + +## A-1 F.A.Q. + +### A-1.1 What is the `LoanBroker.LoanSequence` field? + +A sequential identifier for Loans associated with a LoanBroker object. This value increments with each new Loan created by the broker. Unlike `LoanBroker.OwnerCount`, which tracks the number of currently active Loans, `LoanBroker.LoanSequence` reflects the total number of Loans ever created. \ No newline at end of file From 01d89816658ea7e17d06fb5bdf9231625f2b047b Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Thu, 1 May 2025 17:50:38 +0200 Subject: [PATCH 27/41] removes sequence from the Loan object --- XLS-0066d-lending-protocol/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index ba7f4821..6c41bfc2 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -442,7 +442,6 @@ The `LoanID` is calculated as follows: | `Flags` | `Yes` | `No` | | `string` | `UINT32` | 0 | Ledger object flags. | | `PreviousTxnID` | `No` | `No` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the transaction that last modified this object. | | `PreviousTxnLgrSeq` | `No` | `No` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The ledger sequence containing the transaction that last modified this object. | -| `Sequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the loan. | | `LoanSequence` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence number of the Loan. | | `OwnerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `Borrower` owner's directory. | | `LoanBrokerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | From 7e923c2449c229c136cecd19c1907a3e92e6b271 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 5 May 2025 14:37:57 +0200 Subject: [PATCH 28/41] Apply suggestions from code review Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 6c41bfc2..df2a9916 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -974,7 +974,7 @@ The transaction deletes an existing `Loan` object. ##### 3.2.3.2 State Changes -- If the `tfDefault` flag is specified: +- If the `tfLoanDefault` flag is specified: - Calculate the amount of the Default that First-Loss Capital covers: @@ -998,12 +998,10 @@ The transaction deletes an existing `Loan` object. - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetsAvailable` - Decrease the First-Loss Capital Cover Available: - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` - - Decrease the number of active Loans: - - `LoanBroker(LoanBrokerID).OwnerCount -= 1` - Update the `Loan` object: - - `Loan(LoanID).Flags = lsfLoanDefault` + - `Loan(LoanID).Flags |= lsfLoanDefault` - `Loan(LoanID).PaymentRemaining = 0` - `Loan(LoanID).AssetsAvailable = 0` - `Loan(LoanID).PrincipalOutstanding = 0` @@ -1032,7 +1030,7 @@ The transaction deletes an existing `Loan` object. - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1. Payment Types**](#3251-payment-types), which outlines how to calculate total interest outstanding) - Update the `Loan` object: - - `Loan(LoanID).Flags = lsfLoanImpaired` + - `Loan(LoanID).Flags |= lsfLoanImpaired` - If `currentTime < Loan(LoanID).NextPaymentDueDate` (if the loan payment is not yet late): - `Loan(LoanID).NextPaymentDueDate = currentTime` (move the next payment due date to now) @@ -1043,7 +1041,7 @@ The transaction deletes an existing `Loan` object. - Update the `Loan` object: - - `Loan(LoanID).Flags = 0` + - `Loan(LoanID).Flags &= ~lsfLoanImpaired` - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval > currentTime` (the loan was unimpaired within the payment interval): - `Loan(LoanID).NextPaymentDueDate = Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval` From 5609061006734ae2cb46fec24b8ae37c9d78b2a2 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Tue, 6 May 2025 15:31:24 +0200 Subject: [PATCH 29/41] addresses PR comments --- XLS-0066d-lending-protocol/README.md | 114 ++++++++++++++++++--------- 1 file changed, 78 insertions(+), 36 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index df2a9916..6424e427 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -238,7 +238,7 @@ The `RootIndex` of the `DirectoryNode` object is the result of [`SHA512-Half`](h #### 2.1.5 Reserves -The `LoanBroker` object costs one owner reserve for the account creating it. +The `LoanBroker` object costs two owner reserve for the account creating it. #### 2.1.6 Accounting @@ -380,36 +380,48 @@ CoverRateLiquidation = 0.1 (10%) CoverAvailable = 1,000 Tokens -- Loan -- -DefaultAmount = 1,090 Tokens +AssetsAvailable = 500 Tokens +PrincipleOutstanding = 1,000 Tokens +InterestOutstanding = 90 Tokens # First-Loss Capital liquidation maths +DefaultAmount = PrincipleOutstanding + InterestOutstanding - AssetsAvailable + = 1,000 + 90 - 500 + = 590 + # The amount of the default that the first-loss capital scheme will cover -DefaultCovered = min((DebtTotal x CoverRateMinimum) x CoverRateLiquidation, DefaultAmount) - = min((1,090 * 0.1) * 0.1, 1,090) = min(10.9, 1,090) +DefaultCovered = min((DebtTotal x CoverRateMinimum) x CoverRateLiquidation, DefaultAmount) + = min((1,090 * 0.1) * 0.1, 1,090) = min(10.9, 590) = 10.9 Tokens -DefaultRemaining = DefaultAmount - DefaultCovered - = 1,090 - 10.9 - = 1,079.1 Tokens +Loss = DefaultAmount - DefaultCovered + = 590 - 10.9 + = 579.1 Tokens + +FundsReturned = DefaultCovered + AssetsAvailable + = 10.9 + 500 + = 510.9 + +# Note, Loss + FundsReturned MUST be equal to PrincipleOutstanding + InterestOutstanding ** State Changes ** -- Vault -- -AssetsTotal = AssetsTotal - DefaultRemaining - = 100,090 - 1,079.1 - = 99,010.9 Tokens +AssetsTotal = AssetsTotal - Loss + = 100,090 - 579.1 + = 99,510.9 Tokens -AssetsAvailable = AssetsAvailable + DefaultCovered - = 99,000 + 10.9 - = 99,010.9 Tokens +AssetsAvailable = AssetsAvailable + FundsReturned + = 99,000 + 510.9 + = 99,510.9 Tokens SharesTotal = (UNCHANGED) -- Lending Protocol -- -DebtTotal = DebtTotal - DefaultAmount - = 1,090 - 1,090 +DebtTotal = DebtTotal - PrincipleOutstanding + InterestOutstanding + = 1,090 - (1,000 + 90) = 0 Tokens CoverAvailable = CoverAvailable - DefaultCovered @@ -447,7 +459,7 @@ The `LoanID` is calculated as follows: | `LoanBrokerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. | | `LoanBrokerID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. | | `Borrower` | `No` | `Yes` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. | -| `LoanOriginationFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when the Loan is created. | +| `LoanOriginationFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal nds amount paid to the `LoanBroker.Owner` when the Loan is created. | | `LoanServiceFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` with every Loan payment. | | `LatePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment is late. | | `ClosePaymentFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal funds amount paid to the `LoanBroker.Owner` when a payment full payment is made. | @@ -685,7 +697,7 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set (the asset is not transferable). - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - + - The `LoanBroker.CoverAvailable` < `Amount`. - `LoanBroker.CoverAvailable - Amount` < `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum` @@ -913,18 +925,18 @@ The transaction deletes an existing `Loan` object. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan(LoanID).AssetsAvailable`. - - Increase the `Balance` field of `Loan(LoanID).Borrower` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Increase the `Balance` field of `Loan(LoanID).Borrower` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. -- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. - - Increase the `RippleState` balance between the `Loan(LoanID).Borrower` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. + - Increase the `RippleState` balance between the `Loan(LoanID).Borrower` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan(LoanID).AssetsAvailable`. -- If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable`. - - Increase the `MPToken.MPTAmount` of the `Loan(LoanID).Borrower` `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable` + - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable`. + - Increase the `MPToken.MPTAmount` of the `Loan(LoanID).Borrower` `MPToken` object for the `Vault.Asset` by `Loan(LoanID).AssetsAvailable` - Delete the `Loan` object. @@ -953,6 +965,8 @@ The transaction deletes an existing `Loan` object. ##### 3.2.3.1 `Flags` +`LoanManage` transaction `Flags` are mutually exclusive. + | Flag Name | Flag Value | Description | | ---------------- | :----------: | :--------------------------------------------- | | `tfLoanDefault` | `0x00010000` | Indicates that the Loan should be defaulted. | @@ -965,11 +979,12 @@ The transaction deletes an existing `Loan` object. - The `Account` submitting the transaction is not the `LoanBroker.Owner`. - The `lsfLoanDefault` flag is set on the Loan object. Once a Loan is defaulted, it cannot be modified. -- If `Loan(LoanID).Flags == lsfLoanImpaired` AND `tfLoanImpair` flag is provided. +- If `Loan(LoanID).Flags == lsfLoanImpaired` AND `tfLoanImpair` flag is provided (impairing an already impaired loan). +- If `Loan(LoanID).Flags == 0` AND `tfLoanUnimpair` flag is provided (clearning impairment for an uninpaired loan). - `Loan.PaymentRemaining == 0`. -- The `tfDefault` flag is specified and: +- The `tfLoanDefault` flag is specified and: - `LastClosedLedger.CloseTime` < `Loan.NextPaymentDueDate + Loan.GracePeriod`. ##### 3.2.3.2 State Changes @@ -990,12 +1005,14 @@ The transaction deletes an existing `Loan` object. - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsTotal -= DefaultAmount`. - Increase the Asset Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += DefaultCovered + Loan.AssetsAvailable`. + - If `Loan.lsfLoanImpaired` flag is set: + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding). - Update the `LoanBroker` object: - Decrease the Debt of the LoanBroker: - `LoanBroker(LoanBrokerID).DebtTotal -= ` - - `Loan.PrincipalOutstanding + Loan.InterestOutstanding + Loan.AssetsAvailable` + - `Loan.PrincipalOutstanding + Loan.InterestOutstanding` - Decrease the First-Loss Capital Cover Available: - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` @@ -1027,7 +1044,7 @@ The transaction deletes an existing `Loan` object. - Update the `Vault` object (set "paper loss"): - - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1. Payment Types**](#3251-payment-types), which outlines how to calculate total interest outstanding) + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - Update the `Loan` object: - `Loan(LoanID).Flags |= lsfLoanImpaired` @@ -1037,11 +1054,11 @@ The transaction deletes an existing `Loan` object. - If the `tfLoanUnimpair` flag is specified: - Update the `Vault` object (clear "paper loss"): - - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1. Payment Types**](#3251-payment-types), which outlines how to calculate total interest outstanding) - + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - Update the `Loan` object: - `Loan(LoanID).Flags &= ~lsfLoanImpaired` + - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval > currentTime` (the loan was unimpaired within the payment interval): - `Loan(LoanID).NextPaymentDueDate = Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval` @@ -1332,7 +1349,32 @@ $$ LoanBroker.DebtTotal = LoanBroker.DebtTotal - managementFeeChange $$ -##### 3.2.5.2 Transaction Pseudo-code +##### 3.2.5.2 Total Loan Value Calculation + +At any point in time the following forumula can be used to calculate the total remaining value of the loan: + +$$ +totalValueOutstanding = periodicPayment \times paymentsRemaining +$$ + +We calculate the total interest outstanding as follows: + +$$ +totalInterestOutstanding = totalValueOutstanding - principalOutstanding +$$ + +$$ +periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentRemaining}}{(1 + periodicRate)^{PaymentRemaining} - 1} +$$ + +where the periodic interest rate is the interest rate charged per payment period: + +$$ +periodicRate = \frac{interestRate \times paymentInterval}{365 \times 24 \times 60 \times 60} +$$ + + +##### 3.2.5.3 Transaction Pseudo-code The following is the pseudo-code for handling a Loan payment transaction. @@ -1421,7 +1463,7 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v return (total_principal_paid, total_interest_paid, loan_value_change, total_fee_paid) ``` -##### 3.2.5.3 Failure Conditions +##### 3.2.5.4 Failure Conditions - A `Loan` object with specified `LoanID` does not exist on the ledger. @@ -1446,7 +1488,7 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - If `LastClosedLedger.CloseTime <= Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` -##### 3.2.5.4 State Changes +##### 3.2.5.5 State Changes Assume the payment is split into `principal`, `interest` and `fee`, and `totalDue = principal + interest + fee`. @@ -1564,7 +1606,7 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p [**Return to Index**](#index) -##### 3.2.5.4 Invariants +##### 3.2.5.6 Invariants **TBD** @@ -1574,4 +1616,4 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p ### A-1.1 What is the `LoanBroker.LoanSequence` field? -A sequential identifier for Loans associated with a LoanBroker object. This value increments with each new Loan created by the broker. Unlike `LoanBroker.OwnerCount`, which tracks the number of currently active Loans, `LoanBroker.LoanSequence` reflects the total number of Loans ever created. \ No newline at end of file +A sequential identifier for Loans associated with a LoanBroker object. This value increments with each new Loan created by the broker. Unlike `LoanBroker.OwnerCount`, which tracks the number of currently active Loans, `LoanBroker.LoanSequence` reflects the total number of Loans ever created. From b16dad1cfcdf45efd20315e5979102b972295dd4 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Wed, 7 May 2025 15:34:38 +0200 Subject: [PATCH 30/41] Apply suggestions from code review Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 6424e427..6abcdc4a 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1351,26 +1351,30 @@ $$ ##### 3.2.5.2 Total Loan Value Calculation -At any point in time the following forumula can be used to calculate the total remaining value of the loan: +At any point in time the following formulae can be used to calculate the total remaining value of the loan. + +The periodic interest rate is the interest rate charged per payment period. $$ -totalValueOutstanding = periodicPayment \times paymentsRemaining +periodicRate = \frac{interestRate \times paymentInterval}{365 \times 24 \times 60 \times 60} $$ -We calculate the total interest outstanding as follows: +The payment is computed based on the periodic rate, principal outstanding, and number of payments remaining. (This means the payment amount can decrease if the borrow pays principal early.) $$ -totalInterestOutstanding = totalValueOutstanding - principalOutstanding +periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentRemaining}}{(1 + periodicRate)^{PaymentRemaining} - 1} $$ +The total loan value is simply: + $$ -periodicPayment = principalOutstanding \times \frac{periodicRate \times (1 + periodicRate)^{PaymentRemaining}}{(1 + periodicRate)^{PaymentRemaining} - 1} +totalValueOutstanding = periodicPayment \times paymentsRemaining $$ -where the periodic interest rate is the interest rate charged per payment period: +We calculate the total interest outstanding as follows: $$ -periodicRate = \frac{interestRate \times paymentInterval}{365 \times 24 \times 60 \times 60} +totalInterestOutstanding = totalValueOutstanding - principalOutstanding $$ From 65cc318eb0795579511915e4a969383fb55a222e Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 13:39:06 +0200 Subject: [PATCH 31/41] Apply suggestions from code review Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 6abcdc4a..4fe43933 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1488,9 +1488,9 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - Has `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. -- If `LastClosedLedger.CloseTime > Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` +- If `LastClosedLedger.CloseTime >= Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` -- If `LastClosedLedger.CloseTime <= Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` +- If `LastClosedLedger.CloseTime < Loan.NextPaymentDueDate` and `Amount` < `PeriodicPaymentAmount()` ##### 3.2.5.5 State Changes From 4ff2165ae01301122f9cabbe970b308cba2c4ab7 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:08:13 +0200 Subject: [PATCH 32/41] Update XLS-0066d-lending-protocol/README.md Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 4fe43933..b5f78db6 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1057,13 +1057,13 @@ The transaction deletes an existing `Loan` object. - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - Update the `Loan` object: - - `Loan(LoanID).Flags &= ~lsfLoanImpaired` + - `CandidateDueDate = max(Loan.PreviousPaymentDate, Loan.StartDate) + Loan.PaymentInterval` - - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval > currentTime` (the loan was unimpaired within the payment interval): + - If `CandidateDueDate > currentTime` (the loan was unimpaired within the payment interval): - - `Loan(LoanID).NextPaymentDueDate = Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval` + - `Loan(LoanID).NextPaymentDueDate = CandidateDueDate` - - If `Loan(LoanID).PreviousPaymentDate + Loan(LoanID).PaymentInterval < currentTime` (the loan was unimpaired after the original payment due date): + - If `CandidateDueDate <= currentTime` (the loan was unimpaired after the original payment due date): - `Loan(LoanID).NextPaymentDueDate = currentTime + Loan(LoanID).PaymentInterval` ##### 3.2.3.3 Invariants From 33908afd4efd55711149c6e55c8637158b31294b Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:12:30 +0200 Subject: [PATCH 33/41] Update XLS-0066d-lending-protocol/README.md Co-authored-by: Ed Hennis --- XLS-0066d-lending-protocol/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index b5f78db6..779f4d63 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1428,7 +1428,6 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v return "insufficient amount paid" error } - loan.payments_remaining -= full_periodic_payments loan.next_payment_due_date = loan.next_payment_due_date + loan.payment_interval * full_periodic_payments loan.last_payment_date = loan.next_payment_due_date - loan.payment_interval @@ -1441,12 +1440,13 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v while full_periodic_payments > 0 { total_principal_paid += periodic_payment.principal total_interest_paid += periodic_payment.interest + loan.payments_remaining -= full_periodic_payments + loan.principal_outstanding -= periodic_payment.principal + periodic_payment = loan.compute_periodic_payment() full_periodic_payments -= 1 } - loan.principal_outstanding -= total_principal_paid - let overpayment = min(loan.principal_outstanding, amount % periodic_payment) if overpayment > 0 && is_set(lsfOverpayment) { let interest_portion = overpayment * loan.overpayment_interest_rate From b8f8b6fbeebfc8792d1c7db4bf46491d7d40336f Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:54:39 +0200 Subject: [PATCH 34/41] adds checks for frozen LoanBroker pseudo-account --- XLS-0066d-lending-protocol/README.md | 35 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 779f4d63..b8b6bf3e 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -551,7 +551,7 @@ The transaction creates a new `LoanBroker` object or updates an existing one. - If the `Vault(VaultID).Asset` is an `IOU`: - - Create a `Trustline` between the `Issuer` and the `LoanBroker` _pseudo-account_. + - Create a `RippleState` object between the `Issuer` and the `LoanBroker` _pseudo-account_. - If the `Vault(VaultID).Asset` is an `MPT`: @@ -634,14 +634,16 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - - The trustline `Balance` < `Amount`. + - The `RippleState` object `Balance` < `Amount` (Depositor has insufficient funds). - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: - Has `lsfMPTLocked` flag set. - `MPTAmount` < `Amount`. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set (the asset is not transferable). @@ -689,7 +691,7 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: @@ -824,13 +826,14 @@ The account specified in the `Account` field pays the transaction fee. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: - - Has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - Either of the `tfDefault`, `tfImpair` or `tfUnimpair` flags are set. @@ -866,7 +869,7 @@ The account specified in the `Account` field pays the transaction fee. - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - Create a `Trustline` between the `Issuer` and the `Borrower` if one does not exist. + - Create a `RippleState` object between the `Issuer` and the `Borrower` if one does not exist. - Decrease the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.PrincipalRequested`. - Increase the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.PrincipalRequested - Loan.LoanOriginationFee`. @@ -1096,13 +1099,14 @@ The Borrower submits a `LoanDraw` transaction to draw funds from the Loan. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: - - Has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - The `Borrower` missed a payment: @@ -1477,16 +1481,19 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v - `Loan.PaymentRemaining` or `Loan.PrincipalOutstanding` is `0`. +- The Borrower paid insufficient amount: `full_periodic_payments < 0`. + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - The trustline between the submitter account and the `Issuer` of the asset is frozen. + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot`: - - Has `lsfMPTLocked` flag set. - - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - If `LastClosedLedger.CloseTime >= Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` From fe723a2c50652888a5bfab9518adf541f824a0b8 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:55:13 +0200 Subject: [PATCH 35/41] clarifies LoanPay totalDue and totalPaid --- XLS-0066d-lending-protocol/README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index b8b6bf3e..0cef704a 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1473,6 +1473,20 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v ##### 3.2.5.4 Failure Conditions +Assume the payment is split into `principal`, `interest` and `fee`, and `totalDue = principal + interest + fee`. `totalDue` is the minimum payment due by the borrower. + +Assume the payment is handled by a function that implements the [Pseudo-Code](#3252-transaction-pseudo-code) that returns `principal_paid`, `interest_paid`, `value_change` and `fee_paid`, where: + +- `principal_paid` is the amount of principal that the payment covered. +- `interest_paid` is the amount of interest that the payment covered. +- `fee_paid` is the amount of fee that the payment covered. +- `totalPaid = principal_paid + interest_paid + fee_paid` is the total amount the borrower paid. +- `value_change` is the amount by which the total value of the Loan changed. + - If `value_change` < `0`, Loan value decreased. + - If `value_change` > `0`, Loan value increased. + +Furthermore, assume `full_periodic_payments` variable represents the number of payment intervals that the payment covered. + - A `Loan` object with specified `LoanID` does not exist on the ledger. - The Loan has not started yet: `Loan.StartDate > LastClosedLedger.CloseTime`. @@ -1501,19 +1515,6 @@ function make_payment(amount, current_time) -> (principal_paid, interest_paid, v ##### 3.2.5.5 State Changes -Assume the payment is split into `principal`, `interest` and `fee`, and `totalDue = principal + interest + fee`. - -Assume the payment is handled by a function that implements the [Pseudo-Code](#3252-transaction-pseudo-code) that returns `principal_paid`, `interest_paid`, `value_change` and `fee_paid`, where: - -- `principal_paid` is the amount of principal that the payment covered. -- `interest_paid` is the amount of interest that the payment covered. -- `fee_paid` is the amount of fee that the payment covered. -- `value_change` is the amount by which the total value of the Loan changed. - - If `value_change` < `0`, Loan value decreased. - - If `value_change` > `0`, Loan value increased. - -Furthermore, assume `full_periodic_payments` variable represents the number of payment intervals that the payment covered. - - `Loan` object state changes: - If `Loan(LoanID).Flags == lsfLoanImpaired`: From 389dd2eb25c0217e703421cd4a102ded8162038b Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:55:57 +0200 Subject: [PATCH 36/41] corrects LoanSet debtMaximum and coverAvailable checks --- XLS-0066d-lending-protocol/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 0cef704a..690b9f0d 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -850,10 +850,10 @@ The account specified in the `Account` field pays the transaction fee. - Exceeds maximum Debt of the LoanBroker: - - `LoanBroker(LoanBrokerID).DebtMaximum` < `LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested` + - `LoanBroker(LoanBrokerID).DebtMaximum` < `Loan.PrincipalRequested + (LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)` - Insufficient First-Loss Capital: - - `LoanBroker(LoanBrokerID).CoverAvailable` < `(LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested) x LoanBroker(LoanBrokerID).CoverRateMinimum` + - `LoanBroker(LoanBrokerID).CoverAvailable` < `(LoanBroker(LoanBrokerID).DebtTotal + Loan.PrincipalRequested + (LoanInterest - (LoanInterest x LoanBroker.ManagementFeeRate)) x LoanBroker(LoanBrokerID).CoverRateMinimum` ##### 3.2.1.6 State Changes From fd20def2da0fa4983bf175d93bef15a862bbdd3c Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 14:56:15 +0200 Subject: [PATCH 37/41] improves LoanManage readability with ReturnToVault variable --- XLS-0066d-lending-protocol/README.md | 46 +++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 690b9f0d..b95596af 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1001,13 +1001,14 @@ The transaction deletes an existing `Loan` object. - Apply the First-Loss Capital to the Default Amount - `DefaultCovered = min((LoanBroker(Loan.LoanBrokerID).DebtTotal x LoanBroker(Loan.LoanBrokerID).CoverRateMinimum) x LoanBroker(Loan.LoanBrokerID).CoverRateLiquidation, DefaultAmount)` - `DefaultAmount -= DefaultCovered` + - `ReturnToVault = DefaultCovered + Loan.AssetsAvailable` - Update the `Vault` object: - Decrease the Total Value of the Vault: - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsTotal -= DefaultAmount`. - Increase the Asset Available of the Vault by liquidated First-Loss Capital and any unclaimed funds amount: - - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += DefaultCovered + Loan.AssetsAvailable`. + - `Vault(LoanBroker(LoanBrokerID).VaultID).AssetsAvailable += ReturnToVault`. - If `Loan.lsfLoanImpaired` flag is set: - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding). @@ -1026,39 +1027,40 @@ The transaction deletes an existing `Loan` object. - `Loan(LoanID).AssetsAvailable = 0` - `Loan(LoanID).PrincipalOutstanding = 0` -- Move the First-Loss Capital from the `LoanBroker` _pseudo-account_ to the `Vault` _pseudo-account_: + - Move the First-Loss Capital from the `LoanBroker` _pseudo-account_ to the `Vault` _pseudo-account_: - - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is `XRP`: - - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `DefaultCovered`. - - Increase the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `DefaultCovered`. + - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `ReturnToVault`. + - Increase the `Balance` field of `Vault` _pseudo-account_ `AccountRoot` by `ReturnToVault`. - - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `IOU`: - - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `DefaultCovered`. - - Increase the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `DefaultCovered`. + - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `ReturnToVault`. + - Increase the `RippleState` balance between the `Vault` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `ReturnToVault`. - - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: + - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `DefaultCovered`. - - Increase the `MPToken.MPTAmount` of the `Vault` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `DefaultCovered`. + - Decrease the `MPToken.MPTAmount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `ReturnToVault`. + - Increase the `MPToken.MPTAmount` of the `Vault` _pseudo-account_ `MPToken` object for the `Vault.Asset` by `ReturnToVault`. -- If `tfLoanImpair` flag is specified: + - If `tfLoanImpair` flag is specified: - - Update the `Vault` object (set "paper loss"): + - Update the `Vault` object (set "paper loss"): - - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized += Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - - Update the `Loan` object: - - `Loan(LoanID).Flags |= lsfLoanImpaired` - - If `currentTime < Loan(LoanID).NextPaymentDueDate` (if the loan payment is not yet late): - - `Loan(LoanID).NextPaymentDueDate = currentTime` (move the next payment due date to now) + - Update the `Loan` object: + - `Loan(LoanID).Flags |= lsfLoanImpaired` + - If `currentTime < Loan(LoanID).NextPaymentDueDate` (if the loan payment is not yet late): + - `Loan(LoanID).NextPaymentDueDate = currentTime` (move the next payment due date to now) -- If the `tfLoanUnimpair` flag is specified: + - If the `tfLoanUnimpair` flag is specified: - - Update the `Vault` object (clear "paper loss"): - - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - - Update the `Loan` object: + - Update the `Vault` object (clear "paper loss"): + - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) + + - Update the `Loan` object: - `CandidateDueDate = max(Loan.PreviousPaymentDate, Loan.StartDate) + Loan.PaymentInterval` From 321319e772f7f43b40b2173e71ace7ea506bb6aa Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Mon, 12 May 2025 15:02:07 +0200 Subject: [PATCH 38/41] adds checks to LoanPay to ensure both Vault pseudo-account and LoanbRoker pseudo-accounts are not funded --- XLS-0066d-lending-protocol/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index b95596af..ba848971 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1503,13 +1503,15 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). + - The `RippleState` between the `Vault(LoanBroker(Loan.LoanBrokerID).VaultID).Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Vault _pseudo-account_ is frozen). - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. - If the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. - - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). + - The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Vault _pseudo-account_ is locked). + - The `MPTokenIssuance` object of the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - If `LastClosedLedger.CloseTime >= Loan.NextPaymentDueDate` and `Amount` < `LatePaymentAmount()` From d45aeb5a6c6d3eb3f3c19b4c38c33a9acfd7ebaa Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Tue, 20 May 2025 10:26:52 +0200 Subject: [PATCH 39/41] adds calculations for secondsSinceLastPayment --- XLS-0066d-lending-protocol/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index ba848971..52fc93bb 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1217,6 +1217,10 @@ $$ totalDue = periodicPayment + latePaymentFee + latePaymentInterest $$ +$$ +secondsSinceLastPayment = lastLedgerCloseTime - max(Loan.previousPaymentDate, Loan.startDate) +$$ + A special, late payment interest rate is applied for the over-due period: $$ @@ -1272,6 +1276,10 @@ $$ totalDue = principalOutstanding + accruedInterest + prepaymentPenalty + ClosePaymentFee $$ +$$ +secondsSinceLastPayment = lastLedgerCloseTime - max(Loan.previousPaymentDate, Loan.startDate) +$$ + Accrued interest up to the point of early closure is calculated as follows: $$ @@ -1294,8 +1302,6 @@ $$ valueChange = (prepaymentPenalty) - (interestOutstanding - accruedInterest) $$ -Note that `valueChange <= 0` as an early repayment reduces the total value of the Loan. - ###### 3.2.5.1.5 Management Fee Calculations The `LoanBroker` Management fee is charged against the interest portion of the Loan and subtracted from the total Loan value at Loan creation. However, the fee is charged only during Loan payments. Early and Late payments change the total value of the Loan by decreasing or increasing the value of total interest. Therefore, when an early, late or an overpayment payment is made, the management fee must be updated. From 81e2ff8954763fa572ba0bb937333391327dca63 Mon Sep 17 00:00:00 2001 From: Vito <5780819+Tapanito@users.noreply.github.com> Date: Tue, 20 May 2025 11:20:13 +0200 Subject: [PATCH 40/41] adds destination field to the LoanBrokerCoverWithdraw transaction --- XLS-0066d-lending-protocol/README.md | 69 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 52fc93bb..1ad2e7aa 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -1,8 +1,8 @@ -
    
+
 Title:        Lending Protocol
 Revision:     1 (2024-10-18)
 
-
Authors: +
Authors: Vytautas Vito Tumas Aanchal Malhotra @@ -193,7 +193,7 @@ The `LoanBroker` object captures attributes of the Lending Protocol. The key of the `LoanBroker` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order: - The `LoanBroker` space key `0x006C` (Lower-case `l`). -- The `AccountID`(https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `LoanBrokerSet` transaction, i.e. `Lender`. +- The `AccountID`() of the account submitting the `LoanBrokerSet` transaction, i.e. `Lender`. - The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value. #### 2.1.2 Fields @@ -223,7 +223,7 @@ The `LoanBroker` object has the following fields: | `CoverRateMinimum` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | 0 | The 1/10th basis point of the `DebtTotal` that the first loss capital must cover. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | | `CoverRateLiquidation` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT32` | 0 | The 1/10th basis point of minimum required first loss capital that is liquidated to cover a Loan default. Valid values are between 0 and 100000 inclusive. A value of 1 is equivalent to 1/10 bps or 0.001%. | -#### 2.1.3 `LoanBroker `_pseudo-account_` +#### 2.1.3 `LoanBroker`_pseudo-account_` The `LoanBroker` _pseudo-account_ holds the First-Loss Capital deposited by the LoanBroker, as well as Loan funds. The _pseudo-account_ follows the XLS-64d specification for pseudo accounts. The `AccountRoot` object is created when creating the `Vault` object. @@ -676,28 +676,41 @@ The transaction deposits First Loss Capital into the `LoanBroker` object. The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from the `LoanBroker`. -| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | -| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------ | -| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Transaction type. | -| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. | -| `Amount` | :heavy_check_mark: | `object` | `AMOUNT` | 0 | The amount of Vault asset to withdraw. | +| Field Name | Required? | JSON Type | Internal Type | Default Value | Description | +| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :---------------------------------------------------------------------- | +| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | **TODO** | Transaction type. | +| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. | +| `Amount` | :heavy_check_mark: | `object` | `AMOUNT` | 0 | The amount of Vault asset to withdraw. | +| `Destination` | | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. | ##### 3.1.4.1 Failure conditions - `LoanBroker` object with the specified `LoanBrokerID` does not exist on the ledger. - The submitter `AccountRoot.Account != LoanBroker(LoanBrokerID).Owner`. +- The `Destination` account is specified and it does not have permission to receive the asset. + - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. - - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set. + - If the `Destination` field is not specified: + - The `RippleState` object between the submitter account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + + - If the `Destination` field is specified: + - If `Destination` is not the `Issuer` and the `RippleState` object between the `Destination` account and the `Issuer` of the asset has the `lsfLowFreeze` or `lsfHighFreeze` flag set. + + - The `AccountRoot` object of the `Issuer` has the `lsfGlobalFreeze` flag set and `Destination` is not the `Issuer` of the asset. - The `RippleState` between the `LoanBroker.Account` and the `Issuer` has the `lsfLowFreeze` or `lsfHighFreeze` flag set. (The Loan Broker _pseudo-account_ is frozen). - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. - - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set. - - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set (the asset is not transferable). + - If the `Destination` field is not specified: + - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has `lsfMPTLocked` flag set. + + - If the `Destination` field specified: + - If the `Destination` is not the `Issuer` and the `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `Destination` `AccountRoot` has `lsfMPTLocked` flag set. + + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` has the `lsfMPTLocked` flag set and `Destination` is not the `Issuer` of the asset. + - The `MPTokenIssuance` object of the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` does not have the `lsfMPTCanTransfer` flag set and `Destination` is not the `Issuer` of the asset (the asset is not transferable). - The `MPToken` object for the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` of the `LoanBroker.Account` `AccountRoot` has `lsfMPTLocked` flag set. (The Loan Broker _pseudo-account_ is locked). - The `LoanBroker.CoverAvailable` < `Amount`. @@ -709,17 +722,31 @@ The `LoanBrokerCoverWithdraw` transaction withdraws the First-Loss Capital from - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is `XRP`: - Decrease the `Balance` field of `LoanBroker` _pseudo-account_ `AccountRoot` by `Amount`. - - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. + - If `Destination` field is not specified: + - Increase the `Balance` field of the submitter `AccountRoot` by `Amount`. + - If `Destination` field is specified: + - Increase the `Balance` field of the `Destination` `AccountRoot` by `Amount`. + - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `IOU`: - Decrease the `RippleState` balance between the `LoanBroker` _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. - - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - If `Destination` field is not specified: + - Increase the `RippleState` balance between the submitter `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - If `Destination` field is specified: + - Create a `RippleState` object if it does not exist between the `Destination` `AccountRoot` and the `Issuer` `AccountRoot`. + - Increase the `RippleState` balance between the `Destination` `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`. + - If the `Vault(LoanBroker(LoanBrokerID).VaultID).Asset` is an `MPT`: - Decrease the `MPToken.MPTAmount` by `Amount` of the `LoanBroker` _pseudo-account_ `MPToken` object for the `Vault.Asset`. - - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. + - If `Destination` field is not specified: + - Increase the `MPToken.MPTAmount` by `Amount` of the submitter `MPToken` object for the `Vault.Asset`. + + - If `Destination` field is specified: + - Create a `MPToken` object for the `Destination` `AccountRoot` if it does not exist. + - Increase the `MPToken.MPTAmount` by `Amount` of the `Destination` `MPToken` object for the `Vault.Asset`. - Decrease `LoanBroker.CoverAvailable` by `Amount`. @@ -1015,8 +1042,7 @@ The transaction deletes an existing `Loan` object. - Update the `LoanBroker` object: - Decrease the Debt of the LoanBroker: - - `LoanBroker(LoanBrokerID).DebtTotal -= ` - - `Loan.PrincipalOutstanding + Loan.InterestOutstanding` + - `LoanBroker(LoanBrokerID).DebtTotal -= Loan.PrincipalOutstanding + Loan.InterestOutstanding` - Decrease the First-Loss Capital Cover Available: - `LoanBroker(LoanBrokerID).CoverAvailable -= DefaultCovered` @@ -1059,7 +1085,7 @@ The transaction deletes an existing `Loan` object. - Update the `Vault` object (clear "paper loss"): - `Vault(LoanBroker(LoanBrokerID).VaultID).LossUnrealized -= Loan.PrincipalOutstanding + TotalInterestOutstanding()` (Please refer to section [**3.2.5.1.5 Total Value Calculation**](#3252-total-loan-value-calculation), which outlines how to calculate total interest outstanding) - + - Update the `Loan` object: - `CandidateDueDate = max(Loan.PreviousPaymentDate, Loan.StartDate) + Loan.PaymentInterval` @@ -1389,7 +1415,6 @@ $$ totalInterestOutstanding = totalValueOutstanding - principalOutstanding $$ - ##### 3.2.5.3 Transaction Pseudo-code The following is the pseudo-code for handling a Loan payment transaction. @@ -1634,7 +1659,7 @@ Furthermore, assume `full_periodic_payments` variable represents the number of p # Appendix -## A-1 F.A.Q. +## A-1 F.A.Q ### A-1.1 What is the `LoanBroker.LoanSequence` field? From 3eee2bd4a0f4471689cc989e203d7635b0d91db3 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 30 May 2025 11:40:19 +0200 Subject: [PATCH 41/41] Update XLS-0066d-lending-protocol/README.md Co-authored-by: Mayukha Vadari --- XLS-0066d-lending-protocol/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XLS-0066d-lending-protocol/README.md b/XLS-0066d-lending-protocol/README.md index 1ad2e7aa..d3cbdd1f 100644 --- a/XLS-0066d-lending-protocol/README.md +++ b/XLS-0066d-lending-protocol/README.md @@ -49,7 +49,7 @@ This version intentionally skips the complex mechanisms of automated on-chain co - [**3.1. LoanBroker Transactions**](#31-loanbroker-transactions) - [**3.1.1. LoanBrokerSet Transaction**](#311-loanbrokerset) - [**3.1.2. LoanBrokerDelete Transaction**](#312-loanbrokerdelete) - - [**3.1.3. LoanBrokerCovereposit Transaction**](#313-loanbrokercoverdeposit) + - [**3.1.3. LoanBrokerCoverDeposit Transaction**](#313-loanbrokercoverdeposit) - [**3.1.4. LoanBrokerCoverWithdraw Transaction**](#314-loanbrokercoverwithdraw) - [**3.2 Loan Transactions**](#32-loan-transactions) - [**3.2.1. LoanSet Transaction**](#321-loanset-transaction)