Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions quicklendx-contracts/docs/investment-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Investment Lifecycle and State Machine

This developer guide describes the lifecycle of an investment in the QuickLendX protocol, detailing the `InvestmentStatus` state machine, legal transitions enforced by `InvestmentStatus::validate_transition()`, driving entrypoints, and coupling to invoice and escrow states.

## Overview

The `Investment` state tracks an investor's funded position in the protocol. It is governed by a strict state machine to prevent orphaned active investments, double-refunds, and other transition exploits.

## Investment Statuses

`InvestmentStatus` is defined as:

```rust
pub enum InvestmentStatus {
Active,
Withdrawn,
Completed,
Defaulted,
Refunded,
}
```

* **`Active`**: The investment is active, funded, and tracked in the active investment index. This is the only non-terminal state.
* **`Withdrawn`** (Terminal): The investment has been withdrawn by the investor.
* **`Completed`** (Terminal): The associated invoice has been successfully settled/paid in full, and the investor has received their expected principal and returns.
* **`Defaulted`** (Terminal): The associated invoice has failed to settle within its grace period, transitioning the investment to a defaulted state and triggering insurance payouts.
* **`Refunded`** (Terminal): The associated invoice/escrow has been cancelled or refunded, returning the investment funds to the investor.

---

## State Diagram

```mermaid
stateDiagram-v2
[*] --> Active : accept_bid_and_fund

state Active {
[*] --> ActiveState
}

Active --> Completed : settlement
Active --> Refunded : refund_escrow_funds
Active --> Defaulted : default handling
Active --> Withdrawn : withdrawal

Completed --> [*]
Refunded --> [*]
Defaulted --> [*]
Withdrawn --> [*]

note right of Active : Only non-terminal state
note right of Completed : Terminal state
note right of Refunded : Terminal state
note right of Defaulted : Terminal state
note right of Withdrawn : Terminal state
```

---

## Legal Transitions

All state transitions are validated through `InvestmentStatus::validate_transition()`.

* **`Active`** is the only state that can transition. It can transition to any of the terminal states: `Completed`, `Defaulted`, `Refunded`, or `Withdrawn`.
* All other states (`Completed`, `Defaulted`, `Refunded`, `Withdrawn`) are **terminal** and immutable. No transitions from these states are permitted.

| From State | To State | Driving Entrypoint / Trigger | Description |
| :--- | :--- | :--- | :--- |
| **`Active`** | `Completed` | **`settlement`** (`settle_invoice` / `process_partial_payment` finalization) | Full invoice settlement is completed. Funds are distributed to investor (return) and platform (fees). |
| **`Active`** | `Refunded` | **`refund_escrow_funds`** | Escrow funds are refunded back to the investor. |
| **`Active`** | `Defaulted` | **`default handling`** (`handle_default` / `mark_invoice_defaulted`) | Invoice due date + grace period expires. Triggers default handling and deactivates/claims insurance policies. |
| **`Active`** | `Withdrawn` | **`withdrawal`** (e.g. withdrawal/cancellation flow simulation) | Investor initiates withdrawal of their investment funds. |
| **`Completed`** | *(none)* | *(immutable)* | Terminal state. |
| **`Refunded`** | *(none)* | *(immutable)* | Terminal state. |
| **`Defaulted`** | *(none)* | *(immutable)* | Terminal state. |
| **`Withdrawn`** | *(none)* | *(immutable)* | Terminal state. |

---

## Status Coupling

The investment state is coupled directly to the corresponding `InvoiceStatus` and escrow state:

| Investment Status | Invoice Status | Escrow Status / State | Description |
| :--- | :--- | :--- | :--- |
| **`Active`** | `Funded` | `Held` | The bid has been accepted and escrow is funded. |
| **`Completed`** | `Paid` | `Released` (or empty) | Invoice is fully paid, and escrow is released to the business owner. |
| **`Refunded`** | `Refunded` | `Refunded` | Escrow is returned to the investor, and invoice status is updated to `Refunded`. |
| **`Defaulted`** | `Defaulted` | `Held` (or claimed/empty) | Grace period expired. Escrow is frozen or processed, and insurance claims are paid. |
| **`Withdrawn`** | `Cancelled` | `Refunded` | Invoice is cancelled, and investment funds are withdrawn/refunded. |

---

## Storage & Invariants

1. **Active Investment Index (`act_inv`)**:
* Contains ONLY investments with `status == InvestmentStatus::Active`.
* During any transition leaving the `Active` state, the investment is atomically removed from this index via `remove_from_active_index()`.
2. **Orphan Prevention**:
* `validate_no_orphan_investments()` runs checks to ensure that all investments in the active index are indeed in the `Active` state, protecting against index drift.
25 changes: 18 additions & 7 deletions quicklendx-contracts/src/investment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,30 @@ impl InvestmentStatus {
/// Terminal states are immutable. Once an investment reaches Completed,
/// Defaulted, Refunded, or Withdrawn, no further transition is permitted.
///
/// Detailed state machine design and couplings are documented in
/// [investment-lifecycle.md](file:///Users/backenddevopsdeveloper/Downloads/DRIPS/vida-quicklendx-protocol/quicklendx-contracts/docs/investment-lifecycle.md).
///
/// ### Allowed transitions
/// | From | To |
/// |-----------|----------------------------------|
/// | Active | Completed, Defaulted, Refunded, Withdrawn |
/// | Withdrawn | (terminal - no further moves) |
/// | Completed | (terminal) |
/// | Defaulted | (terminal) |
/// | Refunded | (terminal) |
/// | From | To | Driving Entrypoint |
/// |-----------|----------------------------------|--------------------|
/// | Active | Completed, Defaulted, Refunded, Withdrawn | accept_bid_and_fund -> Active;<br>settlement -> Completed;<br>refund_escrow_funds -> Refunded;<br>default handling -> Defaulted;<br>withdrawal -> Withdrawn |
/// | Withdrawn | (terminal - no further moves) | - |
/// | Completed | (terminal) | - |
/// | Defaulted | (terminal) | - |
/// | Refunded | (terminal) | - |
///
/// ### Security
/// Calling code **must** invoke this before persisting a status change so
/// that no path (settlement, default, refund, or future code) can produce
/// an orphan `Active` investment or an impossible backward transition.
///
/// # Arguments
/// * `from` - The current status of the investment.
/// * `to` - The target status for the transition.
///
/// # Returns
/// * `Ok(())` if the transition is legal.
/// * `Err(QuickLendXError::InvalidStatus)` if the transition is invalid.
pub fn validate_transition(
from: &InvestmentStatus,
to: &InvestmentStatus,
Expand Down
8 changes: 7 additions & 1 deletion quicklendx-contracts/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,20 @@ pub enum BidStatus {
Cancelled,
}

/// Investment status enumeration
/// Investment status enumeration tracking the lifecycle of investor positions.
#[contracttype]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InvestmentStatus {
/// The investment is active, funded, and tracked in the active investment index.
/// This is the only non-terminal state.
Active,
/// Investment funds were withdrawn by the investor (terminal status).
Withdrawn,
/// Investment completed successfully, and the investor has received their payouts (terminal status).
Completed,
/// The associated invoice defaulted due to non-payment, triggering default/insurance logic (terminal status).
Defaulted,
/// Investment was refunded due to invoice cancellation (terminal status).
Refunded,
}

Expand Down
Loading