> For the complete documentation index, see [llms.txt](https://docs.trilobyte.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trilobyte.finance/technical-documentation/factory-contract.md).

# Factory Contract

Deploys and registers vault contracts. Validates all parameters against the Globals contract before deployment, including the tranche leverage cap and protocol-wide exposure limits.

**Source:** `contracts/factory/`

## Source Modules

| File            | Purpose                                                                              |
| --------------- | ------------------------------------------------------------------------------------ |
| `contract.rs`   | Constructor, `set_vault_wasm_hash`, `create_vault`, queries, inline `GlobalsClient`  |
| `storage.rs`    | Data keys (globals, wasm\_hash, 3-index vault registry), getters/setters, pagination |
| `validation.rs` | Pure leverage/exposure-cap checks, unit-testable without deploying a real vault      |
| `errors.rs`     | `#[contracterror]` enum (16 variants)                                                |
| `events.rs`     | `VaultDeployed`, `WasmHashUpdated` events                                            |
| `test.rs`       | Vault deployment, validation, registry, leverage-cap and exposure-cap unit tests     |

## How It Works

1. Governor deploys the Factory with a Globals address and an initial vault WASM hash.
2. When a Pool Manager calls `create_vault(params, salt)`, the factory:
   * Validates the manager is approved and active (via `GlobalsClient::get_manager`)
   * Checks the manager is **not delinquent** (`is_delinquent`)
   * Checks the compliance gate for the manager (action `Manage`), if Globals has one configured
   * Checks the manager's **credit limit** (`outstanding + principal ≤ max_credit`)
   * Validates the lending token is whitelisted
   * Validates interest rate, loan term, and principal against global settings
   * Validates `leverage_ratio` against the Globals cap for the vault's collateral type (`max_leverage_uncollateralized` or `max_leverage_collateralized`, depending on whether `collateral_escrow` is set)
   * Checks the loan against **borrower and manager exposure caps** (`total_outstanding`, gated by `concentration_floor`)
   * Rejects the vault outright if `borrower == manager` (`BorrowerIsManager`)
   * **Locks manager collateral** via `GlobalsClient::lock_collateral`
   * **Increments the manager's AND borrower's outstanding principal** via `GlobalsClient::increment_outstanding`
   * Deploys a new vault using `e.deployer().with_current_contract(salt).deploy_v2(wasm_hash, (globals, params))`
   * **Registers the vault in Globals** via `GlobalsClient::register_vault`, then in three Factory indexes: all vaults, per-manager, per-borrower
3. The governor can update the vault WASM hash via `set_vault_wasm_hash` (for future vault template upgrades).

The pure leverage/exposure-cap checks live in `validation.rs` so they are unit-testable without deploying a real vault — the vault crate is `cdylib`-only, with no `rlib` output, so the Factory's own test suite cannot import it directly.

{% hint style="warning" %}
`create_vault` calls back into Globals (`lock_collateral`, `increment_outstanding`, `register_vault`). These require the Factory to be registered in Globals via `set_factory` first — otherwise the calls fail with `Globals::UnauthorizedCaller`. See [Deployment](/technical-documentation/deployment.md).
{% endhint %}

## Functions

### Constructor

```
__constructor(governor, globals, vault_wasm_hash)
```

Initialise the factory with admin (`governor`), Globals reference, and the vault template hash.

### Admin Actions (`#[only_admin]`)

| Function                           | Description                                                                                                                                      |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `set_vault_wasm_hash(new_hash)`    | Update the vault WASM template                                                                                                                   |
| `upgrade(new_wasm_hash, operator)` | Upgrade the Factory (auth via stored admin; hardened once `require_timelock_admin` is set — see [Governance](/protocol-mechanics/governance.md)) |

### Vault Creation

| Function                                  | Description                                                          |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `create_vault(params: VaultParams, salt)` | Deploy a new vault with full validation; returns the vault `Address` |

**VaultParams** (canonical definition in `common-types`, shared with the Vault constructor so the two sides can never disagree on its shape):

```rust
VaultParams {
    manager: Address,
    borrower: Address,
    lent_token: Address,
    principal: i128,
    interest_rate: u32,
    loan_term: u32,
    leverage_ratio: u32,        // x100, e.g. 400 = 4.00x. Replaces split_ratio.
    reporting_interval: u64,    // Seconds; 0 = no reporting covenant
    permissioned: bool,
    grace_period: u64,
    funding_deadline: u64,
    approval_deadline: u64,
    collateral_escrow: Option<Address>,   // None = manager-collateral-only loan
    collateral_token: Option<Address>,    // asset the borrower must pledge
    collateral_recovery: Option<Address>, // receives the pledge on default
    collateral_min: i128,                 // on-chain floor the pledge must meet
    related_party: bool,                  // manager-declared disclosure flag
}
```

{% hint style="info" %}
The four borrower-collateral fields are optional in effect: leave `collateral_escrow` as `None` (and the others empty / `0`) for a manager-collateral-only loan. If `collateral_escrow` is `Some`, the vault constructor asserts `collateral_token`, `collateral_recovery`, and a positive `collateral_min` are all present.
{% endhint %}

### Queries

| Function                                           | Returns                                             |
| -------------------------------------------------- | --------------------------------------------------- |
| `get_globals()`                                    | Globals contract address                            |
| `get_vault_wasm_hash()`                            | Current vault template hash                         |
| `get_vault_count()`                                | Total deployed vaults (O(1))                        |
| `get_all_vaults()`                                 | First 100 vault addresses (use pagination for more) |
| `get_vaults_page(start, limit)`                    | A page of vault addresses                           |
| `get_manager_vaults(manager)`                      | First 100 vaults for a manager                      |
| `get_manager_vaults_page(manager, start, limit)`   | A page of a manager's vaults                        |
| `get_manager_vault_count(manager)`                 | Number of vaults for a manager                      |
| `get_borrower_vaults(borrower)`                    | First 100 vaults for a borrower                     |
| `get_borrower_vaults_page(borrower, start, limit)` | A page of a borrower's vaults                       |
| `get_borrower_vault_count(borrower)`               | Number of vaults for a borrower                     |

{% hint style="info" %}
The registry is stored as indexed persistent entries with an O(1) count, avoiding Soroban's object-size limit. `get_all_vaults` / `get_manager_vaults` / `get_borrower_vaults` cap at **100**; use the `_page` variants for larger sets.
{% endhint %}

## Error Codes

| Code | Name                       | Description                                                       |
| ---- | -------------------------- | ----------------------------------------------------------------- |
| 1    | `ManagerNotApproved`       | Manager not approved or inactive in Globals                       |
| 2    | `AssetNotSupported`        | Token not whitelisted in Globals                                  |
| 3    | `InvalidRate`              | Interest rate below global minimum                                |
| 4    | `InvalidTerm`              | Loan term outside global bounds                                   |
| 5    | `InvalidPrincipal`         | Principal outside global bounds                                   |
| 6    | `InvalidLeverageRatio`     | `leverage_ratio` must be positive                                 |
| 7    | `WasmHashNotSet`           | No vault template hash configured                                 |
| 8    | `Unauthorized`             | Caller not authorised                                             |
| 9    | `InsufficientCollateral`   | Manager doesn't have enough collateral                            |
| 10   | `ManagerDelinquent`        | Manager has a delinquent vault (previous default)                 |
| 11   | `CreditLimitExceeded`      | New vault would exceed manager's credit limit                     |
| 12   | `LeverageExceedsCap`       | `leverage_ratio` exceeds the Globals cap for this collateral type |
| 13   | `BorrowerExposureExceeded` | Borrower's absolute cap or share of `total_outstanding` exceeded  |
| 14   | `ManagerExposureExceeded`  | Manager's share of `total_outstanding` exceeded                   |
| 15   | `NotCompliant`             | Compliance gate rejected the manager (action `Manage`)            |
| 16   | `BorrowerIsManager`        | The borrower and manager are the same address                     |

## Events

| Event             | Key Fields                                                                | When             |
| ----------------- | ------------------------------------------------------------------------- | ---------------- |
| `VaultDeployed`   | `vault`, `manager`, `borrower`, `principal`, `loan_term`, `interest_rate` | Vault created    |
| `WasmHashUpdated` | `old_hash`, `new_hash`                                                    | Template updated |

*As of September 2026.*
