diff --git a/.gitbook/assets/fixed-rate-vault-architecture.svg b/.gitbook/assets/fixed-rate-vault-architecture.svg new file mode 100644 index 0000000..a889fd1 --- /dev/null +++ b/.gitbook/assets/fixed-rate-vault-architecture.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FIXED RATE VAULTS — CONTRACT ARCHITECTURE + + + + + + + + Liquidation + Adapter + + UPGRADEABLE + + + + + + InstitutionalVaultController + + governance surface · vault configuration · admin call proxy + UPGRADEABLE · transparent proxy + + + + + + + Institution + PositionToken + + ERC-721 + + + + + + VAULT INSTANCES + + + + + InstitutionalLoanVault + + per-loan ERC-4626 + collateral · debt · shares + + clone + + + + + + InstitutionalLoanVault + + per-loan ERC-4626 + collateral · debt · shares + + clone + + + + + + + × N + InstitutionalLoanVault + + per-loan ERC-4626 + collateral · debt · shares + + clone + + + + + + + deploys clones + + + + + mints NFT + + + + + liquidate() + + + + + ownerOf() + + diff --git a/.gitbook/assets/fixed-rate-vault-flow.svg b/.gitbook/assets/fixed-rate-vault-flow.svg new file mode 100644 index 0000000..8362c10 --- /dev/null +++ b/.gitbook/assets/fixed-rate-vault-flow.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FIXED RATE VAULT — FUND FLOW + + + + + + VAULT + ERC-4626 + + + collateral locked + debt tracked + shares issued + + + + + + + Supplier 1 + stablecoin holder + + + + + Supplier 2 + stablecoin holder + + + + + Supplier 3 + stablecoin holder + + + + + + Institution + borrower + posts collateral + + + + ① FUNDRAISING + ② LOCK + ③ SETTLEMENT + ④ MATURITY + + + + + + + + + + + deposit stablecoin + + + + ① COLLATERAL POSTED + posts collateral + + + + loan disbursed + + + + principal + interest + + + + + + + + + + + principal + yield + + diff --git a/.gitbook/assets/fixed-rate-vault-state-machine.svg b/.gitbook/assets/fixed-rate-vault-state-machine.svg new file mode 100644 index 0000000..6e10c87 --- /dev/null +++ b/.gitbook/assets/fixed-rate-vault-state-machine.svg @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + FIXED RATE VAULT — STATE MACHINE + + + + + + + WaitingForMargin + institution posts collateral margin + + + + + + MarginDeposited + governance calls openVault() + + + + + + Fundraising + suppliers deposit · institution tops up collateral + within fundraising window + + + + + + Lock + loan active · APY and totalDebt fixed + + + + + + PendingSettlement + institution repays before deadline + + + + + + SettlementDeadlineExceeded + deadline passed · debt still outstanding + + + + + + + + Failed + principal + margin returned + + + + + + Matured + suppliers redeem principal + yield + + + + + + Liquidated + settlement waterfall runs + + + + + + Closed + governance calls closeVault() + + + + + + + depositCollateral() + + + + openVault() + + + + raised ≥ minBorrowCap + + + + lockDuration elapsed + + + + totalDebt == 0 + + + + deadline + debt > 0 + + + + liquidateOverdueVault() + + + + repaid + + + + raise shortfall / + collateral underdelivery + + + + + + + + cancelVault() + (governance) + + + + + + + + + + closeVault() + + + + + LEGEND + + Normal progression + + Successful settlement + + Overdue / late path + + Failure / cancel path + + closeVault() to Closed + + + diff --git a/SUMMARY.md b/SUMMARY.md index 4a92bac..c680c98 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -8,6 +8,7 @@ ## What's New? +* [Fixed Term Vaults](whats-new/fixed-rate-vaults.md) * [Trade](whats-new/trade.md) * [Leveraged Positions](whats-new/leveraged-positions.md) * [E-Mode](whats-new/e-mode.md) @@ -59,6 +60,9 @@ * [Isolated E-mode](guides/isolated-e-mode.md) * [Boost and Repay with Collateral](guides/leveraged-positions.md) * [Trade](guides/trade.md) +* [Fixed Term Vaults](guides/fixed-rate-vaults/README.md) + * [Institution Guide](guides/fixed-rate-vaults/institution-guide.md) + * [Supplier Guide](guides/fixed-rate-vaults/supplier-guide.md) ## Technical reference @@ -70,6 +74,7 @@ * [Diamond Comptroller in the Core pool](technical-reference/reference-technical-articles/diamond-comptroller.md) * [E-Mode](technical-reference/reference-technical-articles/e-mode.md) * [Trade](technical-reference/reference-technical-articles/trade.md) + * [Fixed Term Vaults](technical-reference/reference-technical-articles/fixed-rate-vaults.md) * [Native Token Gateway](technical-reference/reference-technical-articles/native-token-gateway.md) * [Omnichain Governance](technical-reference/reference-technical-articles/omnichain-governance.md) * [Prime tokens](technical-reference/reference-technical-articles/prime.md) @@ -148,6 +153,12 @@ * [MaxLoopsLimitHelper](technical-reference/reference-isolated-pools/utility/max-loops-limit-helper.md) * [ErrorReporter](technical-reference/reference-isolated-pools/utility/error-reporter.md) * [ExponentialNoError](technical-reference/reference-isolated-pools/utility/exponential-no-error.md) +* [Fixed Term Vaults](technical-reference/reference-fixed-rate-vaults/README.md) + * [InstitutionalVaultController](technical-reference/reference-fixed-rate-vaults/institutional-vault-controller.md) + * [InstitutionalLoanVault](technical-reference/reference-fixed-rate-vaults/institutional-loan-vault.md) + * [LiquidationAdapter](technical-reference/reference-fixed-rate-vaults/liquidation-adapter.md) + * [InstitutionPositionToken](technical-reference/reference-fixed-rate-vaults/institution-position-token.md) + * [BaseVault](technical-reference/reference-fixed-rate-vaults/base-vault.md) * [Oracle](technical-reference/reference-oracle/README.md) * [ResilientOracle](technical-reference/reference-oracle/resilient-oracle.md) * [DeviationBoundedOracle](technical-reference/reference-oracle/deviation-bounded-oracle.md) @@ -209,6 +220,7 @@ * [Token Converters](deployed-contracts/token-converters.md) * [VenusERC4626](deployed-contracts/venus-erc4626.md) * [Periphery](deployed-contracts/periphery.md) +* [Fixed Term Vaults](deployed-contracts/fixed-rate-vaults.md) ## Services diff --git a/deployed-contracts/fixed-rate-vaults.md b/deployed-contracts/fixed-rate-vaults.md new file mode 100644 index 0000000..10ee93c --- /dev/null +++ b/deployed-contracts/fixed-rate-vaults.md @@ -0,0 +1,19 @@ +# Fixed Term Vaults + +Core contracts for the Fixed Term Vaults system. Each institution's vault is deployed as an EIP-1167 clone by governance via `createVault(...)` on `InstitutionalVaultController`, so individual vault instances are not enumerated here — query the controller's `allVaults` array (or the `VaultCreated` event log) to enumerate deployed vaults. + +## BNB Chain Mainnet + +### Fixed Term Vaults Contracts + +* InstitutionalVaultController (proxy): [`0x6D9e91cB766259af42619c14c994E694E57e6E85`](https://bscscan.com/address/0x6D9e91cB766259af42619c14c994E694E57e6E85) +* InstitutionPositionToken: [`0x3Ed56f6937fc8549f9325405d1e8E650739647Fa`](https://bscscan.com/address/0x3Ed56f6937fc8549f9325405d1e8E650739647Fa) +* LiquidationAdapter (proxy): [`0x17A6222fB8b4b6D852cA54f5bc376a6A2c6224Bd`](https://bscscan.com/address/0x17A6222fB8b4b6D852cA54f5bc376a6A2c6224Bd) + +## BNB Chain Testnet + +### Fixed Term Vaults Contracts + +* InstitutionalVaultController (proxy): [`0xf77dED2A00F94e33C392126238360D4642c16Ba2`](https://testnet.bscscan.com/address/0xf77dED2A00F94e33C392126238360D4642c16Ba2) +* InstitutionPositionToken: [`0x71dA473257a96e975558C8edD8491AD0880EFCe5`](https://testnet.bscscan.com/address/0x71dA473257a96e975558C8edD8491AD0880EFCe5) +* LiquidationAdapter (proxy): [`0x4b302b56315Ca16A0A4565108e62404496916491`](https://testnet.bscscan.com/address/0x4b302b56315Ca16A0A4565108e62404496916491) diff --git a/guides/fixed-rate-vaults/README.md b/guides/fixed-rate-vaults/README.md new file mode 100644 index 0000000..8578bf4 --- /dev/null +++ b/guides/fixed-rate-vaults/README.md @@ -0,0 +1,19 @@ +# Fixed Term Vaults + +Fixed Term Vaults let institutions borrow stablecoins for a fixed term at a fixed interest rate, backing the loan with on-chain collateral. Suppliers fund the loan during a fundraising window and earn the target APR at maturity. Each vault funds a single loan and closes once settlement is complete. + +The product has two sides: + +* **Institution** (borrower) — deposits collateral, claims the raised funds, repays at maturity, and recovers any remaining collateral. +* **Supplier** (lender) — supplies the loan asset during fundraising, holds through the lock period, and redeems principal plus the target yield once the institution has repaid. + +Available on BNB Chain. + +## Pick Your Guide + +| Role | Guide | +| --- | --- | +| **Institution** — you want to borrow against collateral at a fixed rate and need to get a vault from Venus and walk through the full lifecycle | [Institution Guide](institution-guide.md) | +| **Supplier** — you want to supply during fundraising and earn the target yield at maturity | [Supplier Guide](supplier-guide.md) | + +For the contract-level details, see the [Fixed Term Vaults Technical Reference](../../technical-reference/reference-technical-articles/fixed-rate-vaults.md). diff --git a/guides/fixed-rate-vaults/institution-guide.md b/guides/fixed-rate-vaults/institution-guide.md new file mode 100644 index 0000000..53790cb --- /dev/null +++ b/guides/fixed-rate-vaults/institution-guide.md @@ -0,0 +1,109 @@ +# Institution Guide + +This guide walks through the institution's lifecycle in a Fixed Term Vault. It covers getting allocated a vault by Venus, depositing the margin, topping up collateral during fundraising, claiming the raised funds, repaying the loan, and recovering any remaining collateral. + +## Getting a Vault + +To get started, reach out to the Venus team. Once the terms are agreed upon off-chain, Venus will deploy a vault for you. + +After deployment, you receive two things: + +* The **vault address**: the target of every later action. +* A **position NFT** minted to the operator address you nominated. The NFT is the credential for all institution-side actions. Whoever holds it controls the vault. + +NFT transfers are blocked unless Venus approves a specific recipient. The vault is now ready for your first action. + +> Every institution action below is called directly on **your vault** contract. Opening and cancelling the vault happen on the **InstitutionalVaultController** and are performed by Venus governance, with no action from you. + +## Step 1: Deposit the Margin + +Deposit the margin into the vault in a single transaction. Partial deposits below the required threshold are rejected. + +The margin is a fixed percentage of the ideal collateral amount set at vault deployment. +If you never deposit the margin, the vault simply sits idle. Venus can cancel and clean it up. + +* **Function:** `depositCollateral(amount)` (approve the collateral asset to the vault first) +* **Callable in:** `WaitingForMargin` (the same function is also used in `Fundraising` and `Lock`) +* **Keep in mind:** `amount` must cover the full margin (`marginRate × idealCollateralAmount`) in one transaction; anything below the threshold reverts. On success the vault advances to `MarginDeposited`. + +## Step 2: Wait for the Vault to Open + +After the margin lands, Venus opens the vault and starts the fundraising window. Suppliers can now supply the loan asset. No action is required from you in this step. + +* **Function:** `openVault(vault)`, called on the **InstitutionalVaultController** by Venus governance, not by you. (Governance can instead `cancelVault(vault)` from `MarginDeposited` if needed, which refunds your margin.) + +## Step 3: Top Up Collateral During Fundraising + +While fundraising is open, bring the collateral balance up from the margin to the full ideal collateral amount. + +The full amount must be in place **before the fundraising window closes**. If it isn't, the vault fails, and the outcome depends on the raise: + +* **The raise also fell short of the minimum:** you recover all deposited collateral, including the margin. +* **The raise succeeded but collateral was underdelivered:** the margin is confiscated and distributed to suppliers. You recover only the remaining non-margin collateral. + +* **Function:** `depositCollateral(amount)` (same call as the margin deposit; approve the collateral asset first) +* **Callable in:** `Fundraising` +* **Keep in mind:** bring `totalCollateralDeposited` up to the full `idealCollateralAmount` **before the fundraising window closes**. Falling short here is what triggers the failure outcomes above. + +{% hint style="warning" %} +If fundraising succeeds but you don't top up the collateral in time, the margin is confiscated. There is no recovery path once that happens. +{% endhint %} + +## Step 4: Claim the Raised Funds + +Once fundraising closes successfully, the vault enters the lock period and the raised funds become available. + +* **Function:** `claimRaisedFunds()` (transfers the entire raised amount to you) +* **Callable in:** `Lock` +* **Keep in mind:** one-shot, all-or-nothing, with no partial draw. + +**During the lock period**, you can add collateral at any time to defend the position's health if the collateral price drops. You can also withdraw any collateral above the minimum floor, as long as the position stays within the liquidation threshold. If either constraint is breached, the withdrawal is rejected. + +* **Add collateral:** `depositCollateral(amount)`, callable in `Lock`; the most direct way to restore health when the collateral price falls. +* **Withdraw collateral:** `withdrawCollateral(amount)`, callable in `Lock` (and later in `Matured` / `Failed`); in `Lock` it must keep collateral above the minimum floor and the position within the liquidation threshold, or it reverts. + +If you don't claim before the lock period ends, the funds stay in the vault. Interest is still owed at maturity either way, so claiming late means paying interest on capital you never used. + +## Step 5: Repay Before the Settlement Deadline + +When the lock period ends, repayment is due. You have until the settlement deadline to repay in full. + +Repayments can be partial, and any wallet can repay on your behalf. Once the debt clears, the vault matures and suppliers can begin redeeming. + +* **Function:** `repay(amount)` (approve the supply asset to the vault first) +* **Callable in:** `Lock`, `PendingSettlement`, `SettlementDeadlineExceeded` +* **Keep in mind:** the call is **permissionless**, so any wallet can call it on your behalf. Partial repayments are allowed and overpayment is clamped to the outstanding debt. Interest is fixed for the full lock duration, so repaying early does not reduce what you owe, but it frees collateral and removes late-payment risk. + +{% hint style="warning" %} +Missing the settlement deadline makes the vault liquidatable at the late-penalty rate, even if your collateral is healthy. +{% endhint %} + +## Key Risks You Must Understand + +A Fixed Term Vault is a fixed-term commitment with a few timing-sensitive obligations. Be aware of the following before participating: + +1. **Margin confiscation** + + If fundraising clears the minimum but you don't top up the collateral to the ideal amount before the fundraising window closes, the margin is permanently confiscated and distributed to suppliers. + +2. **Overdue liquidation** + + Missing the settlement deadline makes the vault liquidatable at the late-penalty rate, even if your collateral is healthy. + +3. **Health-based liquidation** + + If the collateral price drops during the lock period and pushes the position below the liquidation threshold, the vault is liquidatable at the standard incentive rate. + +4. **No early changes to terms** + + Target APR, lock duration, settlement window, and the borrow caps are all set at deployment and cannot be renegotiated on-chain. Confirm the terms with Venus off-chain before the vault is created. + +## Best Practices + +* **Monitor collateral price during the lock period.** Top up early when the price moves against you. Once the funds are claimed, that is the only way to defend the position. + +* **Repay as soon as you can.** Interest is fixed for the full lock duration regardless of when you repay, but repaying early frees up your collateral and removes the late-payment risk. + +* **Keep the position NFT in a wallet you control.** Whoever holds the NFT controls the vault. If you delegate it to an operator, treat that wallet with the same security as a treasury wallet. + +For the on-chain details, including the full state machine, function signatures, math, and liquidation paths, see the [Fixed Term Vaults Technical Reference](../../technical-reference/reference-technical-articles/fixed-rate-vaults.md). diff --git a/guides/fixed-rate-vaults/supplier-guide.md b/guides/fixed-rate-vaults/supplier-guide.md new file mode 100644 index 0000000..aeb1d37 --- /dev/null +++ b/guides/fixed-rate-vaults/supplier-guide.md @@ -0,0 +1,88 @@ +# Supplier Guide + +This guide walks through how to participate in a Fixed Term Vault as a supplier. You supply the loan asset during the fundraising window, hold through the lock period, and redeem principal plus the target yield at maturity. + +{% hint style="warning" %} +**Capital is at risk.** The target APR is set at vault deployment and is not a guaranteed return. Actual proceeds at settlement depend on counterparty performance and may be less than the amount supplied. This product is not available to retail investors or persons in restricted jurisdictions (US, UK, Canada, mainland China, or OFAC-sanctioned countries). +{% endhint %} + +## Before You Supply + +Each vault has a published set of terms set at deployment. Before committing funds, review: + +* **Target APR** — the annualized target rate this vault aims to deliver. Actual return is variable and depends on counterparty performance; capital is at risk. +* **Lock duration** — how long your funds are locked after fundraising closes. +* **Minimum and maximum raise** — the minimum needed to actually fund the loan, and the ceiling above which the vault stops accepting supplies. +* **Minimum supply** — the floor for a single supply. +* **Supply asset** — the asset you supply. +* **Fundraising window** — when supplying closes. + + +## Step 1: Supply During Fundraising + +While fundraising is open, supply your assets into the vault. You receive vault share tokens representing your claim. The shares are freely transferable, so you can hold them in any wallet. + +**Minimum supply floor** applies unless you're filling the final residual capacity, in which case the floor is waived so the cap can actually be reached. + + +## Step 2: Hold Through the Lock Period + +When the fundraising window closes with the raise above the minimum, the vault enters the lock period. At this point: + +* **Supplying and withdrawing are blocked.** There is no early exit. +* **The yield amount is fixed.** Interest is calculated upfront on the full raise for the entire lock duration. +* **Shares remain transferable.** You can move or sell them at any time, even while funds are locked. + + +## Step 3: Wait for Settlement + +When the lock duration elapses, the loan is due and the institution has until the settlement deadline to repay in full. Suppliers cannot withdraw during this window. + +Either the institution repays in full and the vault matures, or the settlement deadline passes with debt outstanding and the vault becomes eligible for overdue liquidation at the late-penalty rate. Either way, no action is required from the supplier. + + +## Step 4: Redeem at Maturity + +Once the institution has repaid the full debt, the vault matures. Burn your shares to receive principal plus your share of the interest. + +Redeem as soon as possible. If funds remain unclaimed for too long, Venus may sweep the remaining assets and close the vault. + +### Worked Example + +A vault raises 100,000 USDC at an 8% target APR for a 90-day lock. You supply 10,000 USDC and receive shares representing 10% of the vault. + +* Total interest at maturity: $$100{,}000 \times 0.08 \times 90/365 \approx 1{,}972.60 \text{ USDC}$$ +* Assume a 10% protocol fee on interest: $$1{,}972.60 \times 0.10 \approx 197.26 \text{ USDC}$$ +* Net target interest paid to suppliers: $$1{,}972.60 - 197.26 \approx 1{,}775.34 \text{ USDC}$$ +* Settlement pool: $$100{,}000 + 1{,}775.34 = 101{,}775.34 \text{ USDC}$$ +* Your share (10%): **~10,177.53 USDC**. That should be a return of ~1.78% over the 90-day term on your 10,000 USDC principal. + +## Key Risks You Must Understand + +A Fixed Term Vault is a fixed-term commitment. Be aware of the following before participating: + +* **No early exit during the lock period.** Funds are locked until maturity. +* **Fundraising shortfall.** If the raise falls short of the minimum, the vault fails and you get your principal back with no interest. +* **Collateral underdelivery.** If the institution doesn't top up collateral in time, you get your principal back plus a pro-rata share of the confiscated margin in the collateral asset. +* **Overdue liquidation.** If the institution misses the settlement deadline, the vault is liquidated at the late-penalty rate, which can reduce the amount available for suppliers. +* **Liquidation may reduce recovery.** If collateral didn't fully cover the debt, your payout may be less than the target rewards. + +## Best Practices + +* **Read the vault terms carefully.** Target APR, lock duration, and the minimum-raise threshold determine your worst-case outcome. +* **Track the settlement deadline.** Once it approaches, watch for repayment activity. If the institution misses it, you'll need to wait for either a catch-up repayment or a liquidation to settle the vault. + +## Recovering Your Funds + +When the vault reaches a terminal state, redemption is available. Burn your shares to claim your portion of the remaining balance. + +| Outcome | What you receive | +| --- | --- | +| **Loan matured** | Principal + your share of the interest | +| **Vault failed — raise short** | Full principal, no interest | +| **Vault failed — collateral underdelivered** | Full principal + pro-rata share of the confiscated margin | +| **Vault liquidated** | Pro-rata share of the remaining supply asset; may be less than full principal if collateral didn't cover the debt | + +--- + +For the on-chain details, including the full state machine, function signatures, math, and liquidation paths, see the [Fixed Term Vaults Technical Reference](../../technical-reference/reference-technical-articles/fixed-rate-vaults.md). diff --git a/technical-reference/reference-fixed-rate-vaults/README.md b/technical-reference/reference-fixed-rate-vaults/README.md new file mode 100644 index 0000000..3a48813 --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/README.md @@ -0,0 +1,13 @@ +# Fixed Term Vaults + +Solidity API reference for the Fixed Term Vault system, generated from NatSpec. + +The system has three singleton contracts, one vault implementation contract (cloned per loan by the controller), and one abstract base: + +| Contract | Description | +| --- | --- | +| [InstitutionalVaultController](institutional-vault-controller.md) | Factory and governance proxy. Deploys vault clones, maintains the registry, and proxies all ACM-gated lifecycle calls. | +| [InstitutionalLoanVault](institutional-loan-vault.md) | Core execution contract for a single loan. Holds collateral and supply assets, enforces the state machine, and exposes ERC-4626 supplier and institution-side entry points. | +| [LiquidationAdapter](liquidation-adapter.md) | The sole address permitted to call `vault.liquidate()`. Manages liquidator/settler whitelists, enforces the close factor, and splits seized collateral between the caller and the protocol. | +| [InstitutionPositionToken](institution-position-token.md) | Singleton ERC-721. One token per vault; the holder controls all institution-side operations on that vault. | +| [BaseVault](base-vault.md) | Abstract base inherited by `InstitutionalLoanVault`. Provides shared ERC-4626 mechanics, fundraising, interest computation, settlement waterfall, and the pause system. | diff --git a/technical-reference/reference-fixed-rate-vaults/base-vault.md b/technical-reference/reference-fixed-rate-vaults/base-vault.md new file mode 100644 index 0000000..2f9b4ad --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/base-vault.md @@ -0,0 +1,549 @@ +# BaseVault + +**Inherits:** ERC4626Upgradeable, ReentrancyGuardUpgradeable + +**Title:** BaseVault + +Abstract base ERC-4626 vault providing shared mechanics for all Venus fixed-term vault types: +fundraising (time-bounded deposit window), interest computation, settlement (protocol fee waterfall), +and core state machine transitions. + +Subcontracts ([InstitutionalLoanVault](institutional-loan-vault.md)) inherit this and add type-specific logic. +Deployed as EIP-1167 minimal proxy clones by the respective VaultController. + +## Solidity API + +## State Variables + +### BPS + +```solidity +uint256 public constant BPS = 10_000 +``` + +### MANTISSA_ONE + +```solidity +uint256 public constant MANTISSA_ONE = 1e18 +``` + +### YEAR + +```solidity +uint256 public constant YEAR = 365 days +``` + +### _config + +Immutable vault configuration set at initialization. + +```solidity +VaultConfig internal _config +``` + +### _runtime + +Runtime state that changes during the vault lifecycle. + +```solidity +VaultRuntime internal _runtime +``` + +### vaultController + +VaultController address — set to msg.sender during initialize. + +```solidity +address public vaultController +``` + +### pauseLevel + +Current pause level (Unpaused, Partial, Complete). + +```solidity +PauseLevel public pauseLevel +``` + +## Functions + +### onlyController + +Reverts if caller is not the VaultController. + +```solidity +modifier onlyController(); +``` + +### whenNotPaused + +Reverts on any pause level (Partial or Complete). Used for general operations. + +```solidity +modifier whenNotPaused(); +``` + +### whenNotCompletelyPaused + +Reverts only on Complete pause. Repay and liquidation remain available during Partial pause. + +```solidity +modifier whenNotCompletelyPaused(); +``` + +### closeVault + +Transitions vault to Closed state. All operations are blocked after this point. +Governance should only call this once all suppliers have withdrawn their funds. + +**Notes:** +- error: InvalidState If vault is not in a terminal state (Matured/Failed/Liquidated). +- event: StateTransition Emitted for the terminal state -> Closed transition. +- event: VaultClosed Emitted with the previous terminal state. + +```solidity +function closeVault() external onlyController; +``` + +### partialPause + +Partial pause — blocks general operations (deposits, collateral, borrowing). +Repay and liquidation remain available so positions can still be defended/resolved. + +**Note:** event: PauseLevelSet + +```solidity +function partialPause() external onlyController; +``` + +### completePause + +Complete pause — blocks all operations including repay and liquidation. + +**Note:** event: PauseLevelSet + +```solidity +function completePause() external onlyController; +``` + +### unpause + +Removes all pause restrictions. + +**Note:** event: PauseLevelSet + +```solidity +function unpause() external onlyController; +``` + +### sweep + +Recovers any tokens stuck in the vault. Full balance is transferred. + +**Notes:** +- error: NothingToSweep If the token balance is zero. +- event: TokensSwept + +```solidity +function sweep(address token) external onlyController; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `token` | `address` | Token address to sweep. | + +### updateVaultState + +Permissionless vault finalizer. Triggers state transitions and settlement. + +```solidity +function updateVaultState() external; +``` + +### outstandingDebt + +Total remaining debt. Decremented by repayments; zero when fully repaid. + +```solidity +function outstandingDebt() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Outstanding debt in supply asset units. | + +### config + +Returns the vault configuration. + +```solidity +function config() external view returns (VaultConfig memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `VaultConfig` | Immutable VaultConfig struct set at initialization. | + +### runtime + +Returns the runtime state. + +```solidity +function runtime() external view returns (VaultRuntime memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `VaultRuntime` | Mutable VaultRuntime struct tracking lifecycle progress. | + +### state + +Current vault lifecycle state. + +```solidity +function state() external view returns (VaultState); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `VaultState` | Current VaultState enum value. | + +### deposit + +Deposits supply assets during the Fundraising window. +Clamps to remaining capacity instead of reverting on excess. + +**Notes:** +- error: InvalidState If vault is not in Fundraising state. +- error: ExceedsMaxCap If clamped deposit amount is zero (vault at capacity). + +```solidity +function deposit(uint256 assets, address receiver) public override returns (uint256 shares); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assets` | `uint256` | Requested deposit amount in supply asset units. | +| `receiver` | `address` | Address to receive minted shares. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `shares` | `uint256` | Actual shares minted (may be less than requested if cap approached). | + +### mint + +Mints shares during the Fundraising window. +Clamps to remaining capacity instead of reverting on excess. + +**Notes:** +- error: InvalidState If vault is not in Fundraising state. +- error: ExceedsMaxCap If clamped share amount is zero (vault at capacity). + +```solidity +function mint(uint256 shares, address receiver) public override returns (uint256 assets); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `shares` | `uint256` | Requested shares to mint. | +| `receiver` | `address` | Address to receive minted shares. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assets` | `uint256` | Actual supply assets pulled (may be less than requested if cap approached). | + +### withdraw + +Withdraws supply assets in terminal states. +Advances state before checking max, enabling single-tx withdrawals +when the vault is ready to transition (e.g. PendingSettlement -> Matured). + +**Notes:** +- error: InvalidState If vault is not in a terminal state (Matured/Failed/Liquidated). +- error: ExceedsMaxCap If requested assets exceed the caller's withdrawable balance. + +```solidity +function withdraw( + uint256 assets, + address receiver, + address owner +) public override returns (uint256 shares); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assets` | `uint256` | Amount of supply assets to withdraw. | +| `receiver` | `address` | Address to receive the assets. | +| `owner` | `address` | Share holder address. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `shares` | `uint256` | Shares burned. | + +### redeem + +Redeems shares for supply assets in terminal states. +Advances state before checking max, enabling single-tx redemptions +when the vault is ready to transition (e.g. PendingSettlement -> Matured). + +**Notes:** +- error: InvalidState If vault is not in a terminal state (Matured/Failed/Liquidated). +- error: ExceedsMaxCap If requested shares exceed the caller's redeemable balance. + +```solidity +function redeem( + uint256 shares, + address receiver, + address owner +) public override returns (uint256 assets); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `shares` | `uint256` | Shares to redeem. | +| `receiver` | `address` | Address to receive the assets. | +| `owner` | `address` | Share holder address. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `assets` | `uint256` | Supply assets returned. | + +### totalAssets + +State-dependent total assets backing outstanding shares. + +Matured/Failed/Liquidated: settlementAmount (decremented on each withdrawal). All other states: totalRaised. + +```solidity +function totalAssets() public view override returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Total assets in supply asset units. | + +### maxDeposit + +Remaining deposit capacity in supply asset units. Zero outside Fundraising state. + +```solidity +function maxDeposit(address /* receiver */) public view override returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Maximum depositable amount. | + +### maxMint + +Share equivalent of maxDeposit. + +```solidity +function maxMint(address receiver) public view override returns (uint256); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `receiver` | `address` | Receiver address (passed through to maxDeposit). | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Maximum mintable shares. | + +### maxWithdraw + +Withdrawable supply asset amount for a supplier. Zero outside terminal states. + +```solidity +function maxWithdraw(address owner) public view override returns (uint256); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `owner` | `address` | Share holder address. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Maximum withdrawable supply asset amount. | + +### maxRedeem + +Redeemable share amount for a supplier. Zero outside terminal states. + +```solidity +function maxRedeem(address owner) public view override returns (uint256); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `owner` | `address` | Share holder address. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Maximum redeemable share amount. | + +## Events + +### StateTransition + +```solidity +event StateTransition(VaultState indexed from, VaultState indexed to, uint256 timestamp); +``` + +### VaultClosed + +```solidity +event VaultClosed(VaultState state); +``` + +### SettlementConfirmed + +```solidity +event SettlementConfirmed(uint256 settlementAmount, uint256 protocolFee, uint256 surplus); +``` + +### ShortfallDetected + +```solidity +event ShortfallDetected(uint256 totalOwed, uint256 available); +``` + +### PSRNotificationFailed + +```solidity +event PSRNotificationFailed(address indexed psr, bytes reason); +``` + +### RaisedFundsClaimed + +```solidity +event RaisedFundsClaimed(uint256 amount); +``` + +### Repaid + +```solidity +event Repaid(uint256 amount, uint256 remainingDebt); +``` + +### PauseLevelSet + +```solidity +event PauseLevelSet(PauseLevel oldLevel, PauseLevel newLevel); +``` + +### TokensSwept + +```solidity +event TokensSwept(address indexed token, address indexed recipient, uint256 amount); +``` + +## Errors + +### InvalidState + +```solidity +error InvalidState(); +``` + +### SameStateTransition + +```solidity +error SameStateTransition(); +``` + +### BelowMinimumDepositAmount + +```solidity +error BelowMinimumDepositAmount(); +``` + +### ExceedsMaxCap + +```solidity +error ExceedsMaxCap(); +``` + +### Unauthorized + +```solidity +error Unauthorized(); +``` + +### AlreadyWithdrawn + +```solidity +error AlreadyWithdrawn(); +``` + +### NoOutstandingDebt + +```solidity +error NoOutstandingDebt(); +``` + +### ZeroRepayAmount + +```solidity +error ZeroRepayAmount(); +``` + +### NothingToSweep + +```solidity +error NothingToSweep(); +``` + +### PartiallyPaused + +```solidity +error PartiallyPaused(); +``` + +### CompletelyPaused + +```solidity +error CompletelyPaused(); +``` diff --git a/technical-reference/reference-fixed-rate-vaults/institution-position-token.md b/technical-reference/reference-fixed-rate-vaults/institution-position-token.md new file mode 100644 index 0000000..12a6624 --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/institution-position-token.md @@ -0,0 +1,165 @@ +# InstitutionPositionToken + +**Inherits:** ERC721, Ownable2Step + +**Title:** InstitutionPositionToken + +Singleton ERC-721 representing institution positions in Institutional Vaults. +One token per vault. Holder = institution operator. Governance-gated transfers. + +Owner is VaultController (sole minter, transfer governance gateway). +Not upgradeable — logic is minimal and immutable. +Deployed with msg.sender as owner, then ownership transferred to VaultController. + +## Solidity API + +## State Variables + +### nextTokenId + +Auto-incrementing token ID counter (starts at 1). + +```solidity +uint256 public nextTokenId +``` + +### tokenIdToVault + +Maps token ID to its vault address. + +```solidity +mapping(uint256 => address) public tokenIdToVault +``` + +### vaultToTokenId + +Maps vault address to its token ID. + +```solidity +mapping(address => uint256) public vaultToTokenId +``` + +### approvedRecipient + +Recipient governance has approved to receive a specific token (one-shot). +Cleared back to address(0) once the matching transfer is consumed. + +```solidity +mapping(uint256 => address) public approvedRecipient +``` + +## Functions + +### constructor + +```solidity +constructor() ERC721("Venus Institution Position", "vINST"); +``` + +### renounceOwnership + +Disabled — renouncing ownership would permanently brick minting and transfer governance. + +**Note:** error: OwnershipCannotBeRenounced Always reverts. + +```solidity +function renounceOwnership() public pure override; +``` + +### mint + +Mints a new token to `to` for the given vault. + +**Note:** event: PositionTokenMinted + +```solidity +function mint(address to, address vault) external onlyOwner returns (uint256 tokenId); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `to` | `address` | The initial institution operator address. | +| `vault` | `address` | The vault address this token represents. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `tokenId` | `uint256` | The minted token ID. | + +### approveTransfer + +Approves `recipient` to receive `tokenId` on the next transfer. One-shot — cleared after use. + +**Notes:** +- error: ZeroAddress If recipient is address(0). +- event: PositionTransferApproved + +```solidity +function approveTransfer(uint256 tokenId, address recipient) external onlyOwner; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `tokenId` | `uint256` | The token ID to approve for transfer. | +| `recipient` | `address` | The address that must be the destination of the next transfer. | + +### revokeTransferApproval + +Revokes a previously granted transfer approval. + +**Note:** event: PositionTransferRevoked + +```solidity +function revokeTransferApproval(uint256 tokenId) external onlyOwner; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `tokenId` | `uint256` | The token ID to revoke approval for. | + +## Events + +### PositionTokenMinted + +```solidity +event PositionTokenMinted(address indexed vault, uint256 indexed tokenId, address indexed institution); +``` + +### PositionTransferApproved + +```solidity +event PositionTransferApproved(uint256 indexed tokenId, address indexed recipient); +``` + +### PositionTransferRevoked + +```solidity +event PositionTransferRevoked(uint256 indexed tokenId); +``` + +## Errors + +### TransferNotApproved + +```solidity +error TransferNotApproved(uint256 tokenId); +``` + +### OwnershipCannotBeRenounced + +```solidity +error OwnershipCannotBeRenounced(); +``` + +### ZeroAddress + +```solidity +error ZeroAddress(); +``` diff --git a/technical-reference/reference-fixed-rate-vaults/institutional-loan-vault.md b/technical-reference/reference-fixed-rate-vaults/institutional-loan-vault.md new file mode 100644 index 0000000..cafa4bf --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/institutional-loan-vault.md @@ -0,0 +1,639 @@ +# InstitutionalLoanVault + +**Inherits:** [BaseVault](base-vault.md) + +**Title:** InstitutionalLoanVault + +ERC-4626 vault for institutional fixed-term lending with on-chain collateral, +borrowing, and liquidation support. Deployed as EIP-1167 minimal proxy clones. + +Inherits BaseVault for shared ERC-4626 mechanics, fundraising, interest, settlement, +and core state machine. Adds: collateral deposit/withdraw, borrowing, risk checks, +liquidation entry points, and pre-fundraising states (WaitingForMargin, MarginDeposited). +No ACM — all governance calls are proxied through VaultController. +Position-holder gated functions (collateral ops, claimRaisedFunds) are restricted to the +current owner of the vault's PositionToken — not the original institution address. The +institution can transfer vault ownership by transferring the token to another address. +Fee-on-transfer tokens are NOT supported for either the underlying asset or collateral. + +## Solidity API + +## State Variables + +### _instConfig + +Institutional-specific configuration — collateral, sizing, position identity. + +```solidity +InstitutionalConfig internal _instConfig +``` + +### _instRuntime + +Institutional-specific runtime — collateral accounting, margin confiscation. + +```solidity +InstitutionalRuntime internal _instRuntime +``` + +### _riskConfig + +Risk parameters — LT/LI/latePenaltyRate mutable via controller. + +```solidity +RiskConfig internal _riskConfig +``` + +### positionToken + +InstitutionPositionToken contract — from controller storage. + +```solidity +IInstitutionPositionToken public positionToken +``` + +## Functions + +### onlyPositionHolder + +Restricts to the current owner of the vault's PositionToken. Ownership is transferable — +if the institution transfers the token, the new holder gains access to position-holder gated functions. + +```solidity +modifier onlyPositionHolder(); +``` + +### onlyLiquidationAdapter + +Restricts to the LiquidationAdapter contract stored on the controller. + +```solidity +modifier onlyLiquidationAdapter(); +``` + +### constructor + +**Note:** oz-upgrades-unsafe-allow: constructor + +```solidity +constructor(); +``` + +### initialize + +Initializes the vault clone. Called once by VaultController. + +```solidity +function initialize( + VaultConfig calldata config_, + InstitutionalConfig calldata instConfig_, + RiskConfig calldata riskConfig_, + IInstitutionPositionToken positionToken_, + string calldata name_, + string calldata symbol_ +) external initializer; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `config_` | `VaultConfig` | Shared vault configuration (asset, rates, caps, timing). | +| `instConfig_` | `InstitutionalConfig` | Institutional-specific configuration (collateral, sizing, position identity). | +| `riskConfig_` | `RiskConfig` | Risk parameters. | +| `positionToken_` | `IInstitutionPositionToken` | InstitutionPositionToken contract reference. | +| `name_` | `string` | ERC-20 share token name. | +| `symbol_` | `string` | ERC-20 share token symbol. | + +### openVault + +Transitions MarginDeposited -> Fundraising. Controller only. + +**Notes:** +- error: InvalidState If vault is not in MarginDeposited state. +- event: VaultOpened Emitted with the open end time. +- event: StateTransition Emitted for MarginDeposited -> Fundraising. + +```solidity +function openVault() external onlyController; +``` + +### cancelVault + +Cancels a vault that has not yet launched and refunds any deposited collateral +to the NFT position holder. Restricted to the two pre-launch states: +WaitingForMargin (no collateral yet) or MarginDeposited (margin in escrow). +Uses positionToken.ownerOf so any approved NFT transfer is honoured. + +**Notes:** +- error: InvalidState If vault is not in WaitingForMargin or MarginDeposited. +- event: VaultCancelled Emitted with the position-holder recipient and refunded collateral amount. +- event: StateTransition Emitted by _stateTransition for WaitingForMargin/MarginDeposited -> Failed. + +```solidity +function cancelVault() external onlyController nonReentrant; +``` + +### repayBadDebt + +Permissionless bad-debt rescue. Anyone may repay to settle a vault where collateral < debt. + +**Notes:** +- error: InvalidState If vault is not in Lock, PendingSettlement, or SettlementDeadlineExceeded. +- error: ZeroRepayAmount If repayAmount is zero. +- error: NoOutstandingDebt If there is no debt to repay. +- error: NotBadDebt If collateral value >= debt value. +- error: InsufficientRepayment If outstanding debt after repay still exceeds total interest (principal not fully returned). + +```solidity +function repayBadDebt(uint256 repayAmount) external nonReentrant whenNotCompletelyPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `repayAmount` | `uint256` | Amount of supply asset to pull from caller. | + +### setLiquidationThreshold + +Updates liquidation threshold. Controller only. + +**Note:** event: LiquidationThresholdUpdated + +```solidity +function setLiquidationThreshold(uint256 newLT) external onlyController; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `newLT` | `uint256` | New liquidation threshold (mantissa). | + +### setLiquidationIncentive + +Updates liquidation incentive. Controller only. + +**Note:** event: LiquidationIncentiveUpdated + +```solidity +function setLiquidationIncentive(uint256 newLI) external onlyController; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `newLI` | `uint256` | New liquidation incentive (mantissa). | + +### setLatePenaltyRate + +Updates late penalty rate. Controller only. + +**Note:** event: LatePenaltyRateUpdated + +```solidity +function setLatePenaltyRate(uint256 newRate) external onlyController; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `newRate` | `uint256` | New late penalty rate (mantissa). | + +### liquidate + +HF-based liquidation. LiquidationAdapter only. + +**Notes:** +- error: ZeroRepayAmount If repayAmount is zero. +- error: InvalidState If vault is not in Lock, PendingSettlement, or SettlementDeadlineExceeded. +- error: NoOutstandingDebt If there is no debt to repay. +- error: NotLiquidatable If vault has no LT shortfall. +- event: LiquidationExecuted Emitted with liquidator, repay amount, and collateral seized. + +```solidity +function liquidate( + uint256 repayAmount +) external onlyLiquidationAdapter nonReentrant whenNotCompletelyPaused returns (uint256 actualRepay); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `repayAmount` | `uint256` | Amount of supply asset to repay. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `actualRepay` | `uint256` | Actual amount repaid after clamping to outstanding debt. | + +### liquidateOverdueVault + +Deadline-based liquidation. LiquidationAdapter only. + +**Notes:** +- error: ZeroRepayAmount If repayAmount is zero. +- error: InvalidStateForOverdueLiquidation If not in SettlementDeadlineExceeded. +- error: NoOutstandingDebt If there is no debt to repay. +- error: ExceedsCloseFactor If actualRepay exceeds the close factor limit (via _executeLiquidation). +- error: InsufficientCollateralForSeize If seize amount exceeds collateral balance (via _executeLiquidation). +- event: OverdueLiquidationExecuted Emitted with settler, repay amount, and collateral seized. + +```solidity +function liquidateOverdueVault( + uint256 repayAmount +) external onlyLiquidationAdapter nonReentrant whenNotCompletelyPaused returns (uint256 actualRepay); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `repayAmount` | `uint256` | Amount of supply asset to repay. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `actualRepay` | `uint256` | Actual amount repaid after clamping to outstanding debt. | + +### depositCollateral + +Deposits collateral into the vault. Fee-on-transfer / rebasing collateral tokens are NOT supported. + +- WaitingForMargin: the full margin amount must be deposited in a single transaction to transition to MarginDeposited; partial deposits revert. +- Fundraising: institution deposits remaining collateral alongside lender fundraising. +- Lock: top-up collateral. + +**Notes:** +- error: InvalidState If vault is not in WaitingForMargin, Fundraising, or Lock. +- error: InsufficientCollateral If deposit in WaitingForMargin does not meet margin threshold. +- event: CollateralDeposited Emitted with actual deposited amount and total collateral. + +```solidity +function depositCollateral(uint256 amount) external onlyPositionHolder nonReentrant whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `amount` | `uint256` | Amount of collateral tokens to deposit. | + +### withdrawCollateral + +Withdraws collateral. + +- Lock: floor-checked (minimumCollateralRequired) + LT-checked. +- Failed (Scenario A — raised < minCap): withdraw all deposited collateral. +- Failed (Scenario B — institution default): withdraw deposited minus confiscated margin. +- Matured: capped at totalCollateralDeposited, unrestricted. +- Liquidated: blocked — collateral is recoverable by governance via sweep(). + +**Notes:** +- error: InvalidState If vault is not in Lock, Matured, or Failed. +- error: InsufficientCollateral If withdrawal would breach floor or exceed available amount. +- error: WithdrawalWouldBreachLT If withdrawal would cause LT shortfall during Lock. +- event: CollateralWithdrawn Emitted with position holder, amount, and remaining collateral. + +```solidity +function withdrawCollateral(uint256 amount) external onlyPositionHolder nonReentrant whenNotPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `amount` | `uint256` | Amount of collateral tokens to withdraw. | + +### claimRaisedFunds + +One-time fund withdrawal. Transfers all raised supply assets to institution. + +**Notes:** +- error: AlreadyWithdrawn if funds already claimed. +- error: ClaimWouldBreachLT if post-claim debt would exceed LT cap. +- event: RaisedFundsClaimed + +```solidity +function claimRaisedFunds() external onlyPositionHolder nonReentrant whenNotPaused; +``` + +### repay + +Repays outstanding debt. Anyone may call. Clamped to outstandingDebt. + +**Notes:** +- error: InvalidState If vault is not in Lock, PendingSettlement, or SettlementDeadlineExceeded. +- error: ZeroRepayAmount if amount is zero. +- error: NoOutstandingDebt if there is no debt to repay. +- event: Repaid + +```solidity +function repay(uint256 amount) external nonReentrant whenNotCompletelyPaused; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `amount` | `uint256` | Amount of supply asset to repay. | + +### getCollateralValueUSD + +Current collateral value in USD via oracle. + +```solidity +function getCollateralValueUSD() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Collateral value in 18-decimal USD. | + +### getDebtValueUSD + +Current outstanding debt value in USD via oracle. + +```solidity +function getDebtValueUSD() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Debt value in 18-decimal USD. | + +### institutionalConfig + +Returns the institutional-specific configuration. + +```solidity +function institutionalConfig() external view returns (InstitutionalConfig memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `InstitutionalConfig` | Institutional config struct. | + +### riskConfig + +Returns the risk configuration. + +```solidity +function riskConfig() external view returns (RiskConfig memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `RiskConfig` | Risk parameters struct. | + +### institutionalRuntime + +Returns the institutional-specific runtime state. + +```solidity +function institutionalRuntime() external view returns (InstitutionalRuntime memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `InstitutionalRuntime` | Institutional runtime struct. | + +### getVaultLiquidity + +Returns current liquidity and shortfall for the vault. + +```solidity +function getVaultLiquidity() external view returns (uint256 liquidity, uint256 shortfall); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `liquidity` | `uint256` | Excess liquidity (0 if shortfall). | +| `shortfall` | `uint256` | LT shortfall (0 if healthy). | + +### getHypotheticalVaultLiquidity + +Returns hypothetical liquidity/shortfall after a simulated withdrawal and/or debt increase. + +```solidity +function getHypotheticalVaultLiquidity( + uint256 withdrawAmount, + uint256 additionalDebt +) external view returns (uint256 liquidity, uint256 shortfall); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `withdrawAmount` | `uint256` | Simulated collateral withdrawal amount. | +| `additionalDebt` | `uint256` | Simulated additional debt on top of outstanding. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `liquidity` | `uint256` | Excess liquidity (0 if shortfall). | +| `shortfall` | `uint256` | LT shortfall (0 if healthy). | + +### calculateSeizeAmount + +Preview seize amount for a given repay and liquidation type. + +```solidity +function calculateSeizeAmount( + uint256 repayAmount, + LiquidationType liquidationType +) external view returns (uint256); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `repayAmount` | `uint256` | Amount being repaid. | +| `liquidationType` | `LiquidationType` | HF_BASED or DEADLINE. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Collateral seize amount. | + +## Events + +### VaultOpened + +```solidity +event VaultOpened(uint256 openEndTime); +``` + +### VaultLocked + +```solidity +event VaultLocked(uint256 totalRaised, uint256 lockEndTime); +``` + +### VaultFailed + +```solidity +event VaultFailed(uint256 totalRaised, uint256 minBorrowCap); +``` + +### VaultLiquidated + +```solidity +event VaultLiquidated(uint256 available); +``` + +### CollateralDeposited + +```solidity +event CollateralDeposited(uint256 amount, uint256 totalCollateral); +``` + +### CollateralWithdrawn + +```solidity +event CollateralWithdrawn(address indexed positionHolder, uint256 amount, uint256 remaining); +``` + +### LiquidationExecuted + +```solidity +event LiquidationExecuted(address indexed liquidator, uint256 repayAmount, uint256 collateralSeized); +``` + +### OverdueLiquidationExecuted + +```solidity +event OverdueLiquidationExecuted(address indexed settler, uint256 repayAmount, uint256 collateralSeized); +``` + +### MarginConfiscated + +```solidity +event MarginConfiscated(uint256 marginAmount); +``` + +### MarginCompensationClaimed + +```solidity +event MarginCompensationClaimed(address indexed receiver, uint256 amount); +``` + +### VaultCancelled + +```solidity +event VaultCancelled(address indexed recipient, uint256 collateralAmount); +``` + +### LiquidationThresholdUpdated + +```solidity +event LiquidationThresholdUpdated(uint256 oldLT, uint256 newLT); +``` + +### LiquidationIncentiveUpdated + +```solidity +event LiquidationIncentiveUpdated(uint256 oldLI, uint256 newLI); +``` + +### LatePenaltyRateUpdated + +```solidity +event LatePenaltyRateUpdated(uint256 oldRate, uint256 newRate); +``` + +## Errors + +### InsufficientCollateral + +```solidity +error InsufficientCollateral(); +``` + +### NotPositionHolder + +```solidity +error NotPositionHolder(); +``` + +### InvalidStateForOverdueLiquidation + +```solidity +error InvalidStateForOverdueLiquidation(); +``` + +### NotBadDebt + +```solidity +error NotBadDebt(); +``` + +### InsufficientRepayment + +```solidity +error InsufficientRepayment(); +``` + +### NotLiquidatable + +```solidity +error NotLiquidatable(); +``` + +### ExceedsCloseFactor + +```solidity +error ExceedsCloseFactor(); +``` + +### InsufficientCollateralForSeize + +```solidity +error InsufficientCollateralForSeize(uint256 seizeAmount, uint256 availableCollateral); +``` + +### WithdrawalWouldBreachLT + +```solidity +error WithdrawalWouldBreachLT(); +``` + +### WithdrawExceedsCollateral + +```solidity +error WithdrawExceedsCollateral(); +``` + +### ClaimWouldBreachLT + +```solidity +error ClaimWouldBreachLT(); +``` + +### InvalidOraclePrice + +```solidity +error InvalidOraclePrice(); +``` diff --git a/technical-reference/reference-fixed-rate-vaults/institutional-vault-controller.md b/technical-reference/reference-fixed-rate-vaults/institutional-vault-controller.md new file mode 100644 index 0000000..fd1388d --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/institutional-vault-controller.md @@ -0,0 +1,675 @@ +# InstitutionalVaultController + +**Inherits:** Initializable, AccessControlledV8 + +**Title:** InstitutionalVaultController + +Central orchestrator for the Institutional Vault system. Deploys vault clones, maintains the registry, +holds the Venus ACM reference, and proxies governance operations to vaults. + +Deployed as a transparent proxy (upgradeable via ProxyAdmin). + +## Solidity API + +## State Variables + +### MANTISSA_ONE + +```solidity +uint256 public constant MANTISSA_ONE = 1e18 +``` + +### MANTISSA_ONE_AND_HALF + +Maximum allowed multiplier for rate parameters (LI, LP). Caps bonus/penalty at 50% above mantissa. + +```solidity +uint256 public constant MANTISSA_ONE_AND_HALF = 1.5e18 +``` + +### MAX_APY_BPS + +Maximum allowed fixed APY in basis points (100% = 10 000 bps). +Interest is calculated as: totalRaised * fixedAPY * lockDuration / (BPS * YEAR). + +```solidity +uint256 public constant MAX_APY_BPS = 10_000 +``` + +### vaultImplementation + +InstitutionalLoanVault implementation address for cloning. + +```solidity +address public vaultImplementation +``` + +### liquidationAdapter + +LiquidationAdapter contract address. + +```solidity +address public liquidationAdapter +``` + +### oracle + +Venus ResilientOracle address. + +```solidity +address public oracle +``` + +### protocolShareReserve + +Venus ProtocolShareReserve (PSR) address. + +```solidity +address public protocolShareReserve +``` + +### comptroller + +Comptroller address for PSR integration. + +```solidity +address public comptroller +``` + +### treasury + +Treasury address — recipient for swept tokens. + +```solidity +address public treasury +``` + +### positionToken + +InstitutionPositionToken contract address. + +```solidity +IInstitutionPositionToken public positionToken +``` + +### allVaults + +Array of all deployed vault addresses. + +```solidity +address[] public allVaults +``` + +### isRegistered + +Whether a vault is registered. + +```solidity +mapping(address => bool) public isRegistered +``` + +### institutionNonce + +Per-institution deploy counter (for CREATE2 salt). + +```solidity +mapping(address => uint256) public institutionNonce +``` + +## Functions + +### constructor + +**Note:** oz-upgrades-unsafe-allow: constructor + +```solidity +constructor(); +``` + +### initialize + +Initializes the controller proxy. + +```solidity +function initialize( + address vaultImplementation_, + address oracle_, + address protocolShareReserve_, + address comptroller_, + address treasury_, + address positionToken_, + address acm_ +) external initializer; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vaultImplementation_` | `address` | InstitutionalLoanVault implementation for cloning. | +| `oracle_` | `address` | Venus ResilientOracle address. | +| `protocolShareReserve_` | `address` | PSR address. | +| `comptroller_` | `address` | Comptroller address for PSR. | +| `treasury_` | `address` | Treasury address — recipient for swept tokens. | +| `positionToken_` | `address` | InstitutionPositionToken address. | +| `acm_` | `address` | Venus AccessControlManager address. | + +### acceptPositionTokenOwnership + +Accepts pending ownership of the InstitutionPositionToken. +Required because PositionToken uses Ownable2Step. Call after transferOwnership. + +```solidity +function acceptPositionTokenOwnership() external; +``` + +### createVault + +Deploys a new vault clone via deterministic CREATE2. + +**Note:** event: VaultCreated + +```solidity +function createVault( + VaultConfig calldata _vaultConfig, + InstitutionalConfig calldata _instConfig, + RiskConfig calldata _riskConfig, + string calldata _name, + string calldata _symbol +) external returns (address vault); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `_vaultConfig` | `VaultConfig` | Shared vault configuration (asset, rates, caps, timing). | +| `_instConfig` | `InstitutionalConfig` | Institutional-specific configuration (collateral, sizing, position identity). | +| `_riskConfig` | `RiskConfig` | Risk parameters. | +| `_name` | `string` | ERC-20 share token name for the deployed vault. | +| `_symbol` | `string` | ERC-20 share token symbol for the deployed vault. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Deployed vault address. | + +### openVault + +Transitions MarginDeposited -> Fundraising on a vault. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function openVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### cancelVault + +Cancels a pre-launch vault and refunds any deposited collateral to the NFT position holder. +Callable only on a vault still in WaitingForMargin or MarginDeposited. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function cancelVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### partialPauseVault + +Partial pause — blocks general operations; repay and liquidation remain available. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function partialPauseVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### completePauseVault + +Complete pause — blocks all operations including repay and liquidation. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function completePauseVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### unpauseVault + +Unpause vault — removes all pause restrictions. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function unpauseVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### closeVault + +Transitions vault to Closed state. All operations are blocked after this point. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function closeVault(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### sweep + +Recovers stuck tokens from a vault to treasury. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function sweep(address vault, address token) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | +| `token` | `address` | Token address to sweep. | + +### approvePositionTransfer + +Approves transfer of the vault's position token to a specific recipient. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function approvePositionTransfer(address vault, address recipient) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | +| `recipient` | `address` | The address that must be the destination of the next transfer. | + +### revokePositionTransfer + +Revokes a previously granted approval. + +**Note:** error: VaultNotRegistered If vault is not in the registry. + +```solidity +function revokePositionTransfer(address vault) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | + +### setLiquidationThreshold + +Updates liquidation threshold on a vault. + +**Notes:** +- error: VaultNotRegistered If vault is not in the registry. +- error: InvalidLiquidationThreshold If newLT == 0 or newLT > MANTISSA_ONE. +- event: LiquidationThresholdUpdated + +```solidity +function setLiquidationThreshold(address vault, uint256 newLT) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | +| `newLT` | `uint256` | New liquidation threshold (mantissa). | + +### setLiquidationIncentive + +Updates liquidation incentive on a vault. + +**Notes:** +- error: VaultNotRegistered If vault is not in the registry. +- error: InvalidLiquidationIncentive If newLI <= MANTISSA_ONE or newLI > MANTISSA_ONE_AND_HALF. +- event: LiquidationIncentiveUpdated + +```solidity +function setLiquidationIncentive(address vault, uint256 newLI) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | +| `newLI` | `uint256` | New liquidation incentive (mantissa). Must be in range (MANTISSA_ONE, MANTISSA_ONE_AND_HALF]. | + +### setLatePenaltyRate + +Updates late penalty rate on a vault. + +**Notes:** +- error: VaultNotRegistered If vault is not in the registry. +- error: InvalidLatePenaltyRate If newRate <= MANTISSA_ONE or newRate > MANTISSA_ONE_AND_HALF. +- event: LatePenaltyRateUpdated + +```solidity +function setLatePenaltyRate(address vault, uint256 newRate) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address. | +| `newRate` | `uint256` | New late penalty rate (mantissa). Must be in range (MANTISSA_ONE, MANTISSA_ONE_AND_HALF]. | + +### setVaultImplementation + +Update clone source. Only affects future vaults. + +**Notes:** +- error: InvalidAddress if zero address. +- event: VaultImplementationUpdated + +```solidity +function setVaultImplementation(address impl) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `impl` | `address` | New implementation address. | + +### setLiquidationAdapter + +Update LiquidationAdapter address. + +**Notes:** +- error: InvalidAddress if zero address. +- event: LiquidationAdapterUpdated + +```solidity +function setLiquidationAdapter(address adapter) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `adapter` | `address` | New adapter address. | + +### setOracle + +Update ResilientOracle reference. + +**Notes:** +- error: InvalidAddress if zero address. +- event: OracleUpdated + +```solidity +function setOracle(address oracle_) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `oracle_` | `address` | New oracle address. | + +### setProtocolShareReserve + +Update ProtocolShareReserve address. + +**Notes:** +- error: InvalidAddress if zero address. +- event: ProtocolShareReserveUpdated + +```solidity +function setProtocolShareReserve(address psr) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `psr` | `address` | New PSR address. | + +### setComptroller + +Update comptroller address for PSR. + +**Notes:** +- error: InvalidAddress if zero address. +- event: ComptrollerUpdated + +```solidity +function setComptroller(address comptroller_) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `comptroller_` | `address` | New comptroller address. | + +### setTreasury + +Update treasury address for swept tokens. + +**Notes:** +- error: InvalidAddress if zero address. +- event: TreasuryUpdated + +```solidity +function setTreasury(address treasury_) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `treasury_` | `address` | New treasury address. | + +### predictVaultAddress + +Predicts the next vault address for a given institution. + +```solidity +function predictVaultAddress(address institution) external view returns (address); +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `institution` | `address` | Institution operator address. | + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `address` | Predicted vault address. | + +### getAggregatedVaultStates + +Returns state summary for all registered vaults. + +```solidity +function getAggregatedVaultStates() external view returns (VaultStateInfo[] memory); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `VaultStateInfo[]` | Array of VaultStateInfo structs. | + +### allVaultsLength + +Returns total number of deployed vaults. + +```solidity +function allVaultsLength() external view returns (uint256); +``` + +**Returns** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `` | `uint256` | Number of vaults in registry. | + +### renounceOwnership + +Disabled — renouncing ownership would permanently brick ACM-gated vault governance. + +**Note:** error: OwnershipCannotBeRenounced Always reverts. + +```solidity +function renounceOwnership() public pure override; +``` + +## Events + +### VaultCreated + +```solidity +event VaultCreated(address indexed vault, address indexed institution); +``` + +### VaultImplementationUpdated + +```solidity +event VaultImplementationUpdated(address indexed oldImpl, address indexed newImpl); +``` + +### LiquidationAdapterUpdated + +```solidity +event LiquidationAdapterUpdated(address indexed oldAdapter, address indexed newAdapter); +``` + +### OracleUpdated + +```solidity +event OracleUpdated(address indexed oldOracle, address indexed newOracle); +``` + +### ProtocolShareReserveUpdated + +```solidity +event ProtocolShareReserveUpdated(address indexed oldPSR, address indexed newPSR); +``` + +### ComptrollerUpdated + +```solidity +event ComptrollerUpdated(address indexed oldComptroller, address indexed newComptroller); +``` + +### TreasuryUpdated + +```solidity +event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury); +``` + +### LiquidationThresholdUpdated + +```solidity +event LiquidationThresholdUpdated(address indexed vault, uint256 newLT); +``` + +### LiquidationIncentiveUpdated + +```solidity +event LiquidationIncentiveUpdated(address indexed vault, uint256 newLI); +``` + +### LatePenaltyRateUpdated + +```solidity +event LatePenaltyRateUpdated(address indexed vault, uint256 newRate); +``` + +## Errors + +### VaultNotRegistered + +```solidity +error VaultNotRegistered(); +``` + +### InvalidConfig + +```solidity +error InvalidConfig(); +``` + +### InvalidLiquidationThreshold + +```solidity +error InvalidLiquidationThreshold(); +``` + +### InvalidLiquidationIncentive + +```solidity +error InvalidLiquidationIncentive(); +``` + +### InvalidLatePenaltyRate + +```solidity +error InvalidLatePenaltyRate(); +``` + +### InvalidAddress + +```solidity +error InvalidAddress(); +``` + +### OwnershipCannotBeRenounced + +```solidity +error OwnershipCannotBeRenounced(); +``` diff --git a/technical-reference/reference-fixed-rate-vaults/liquidation-adapter.md b/technical-reference/reference-fixed-rate-vaults/liquidation-adapter.md new file mode 100644 index 0000000..f1e0410 --- /dev/null +++ b/technical-reference/reference-fixed-rate-vaults/liquidation-adapter.md @@ -0,0 +1,320 @@ +# LiquidationAdapter + +**Inherits:** Initializable, AccessControlledV8, ReentrancyGuardUpgradeable + +**Title:** LiquidationAdapter + +Manages whitelisted liquidators/settlers, routes liquidation calls to Institutional Vault vaults, +receives seized collateral, and splits incentive between protocol and caller. + +Deployed as a transparent proxy (upgradeable). Holds ACM for whitelist and config management. + +## Solidity API + +## State Variables + +### MANTISSA_ONE + +```solidity +uint256 public constant MANTISSA_ONE = 1e18 +``` + +### vaultController + +InstitutionalVaultController address (vaults are validated via controller). + +```solidity +address public vaultController +``` + +### isWhitelistedLiquidator + +HF-based liquidators — can call liquidate() when LT shortfall > 0. + +```solidity +mapping(address => bool) public isWhitelistedLiquidator +``` + +### isWhitelistedSettler + +Deadline-based settlers — can call liquidateOverdueVault() in SettlementDeadlineExceeded. + +```solidity +mapping(address => bool) public isWhitelistedSettler +``` + +### protocolLiquidationShare + +Fraction of incentive portion to protocol (mantissa). + +```solidity +uint256 public protocolLiquidationShare +``` + +### closeFactor + +Max fraction of debt repayable per liquidation (mantissa). Global for all vaults. + +```solidity +uint256 public closeFactor +``` + +### protocolShareAccrued + +Accrued protocol share per collateral token. Swept to PSR via governance. + +```solidity +mapping(address => uint256) public protocolShareAccrued +``` + +## Functions + +### constructor + +**Note:** oz-upgrades-unsafe-allow: constructor + +```solidity +constructor(); +``` + +### initialize + +Initializes the adapter proxy. + +```solidity +function initialize( + address vaultController_, + uint256 protocolLiquidationShare_, + uint256 closeFactor_, + address acm_ +) external initializer; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vaultController_` | `address` | VaultController address. | +| `protocolLiquidationShare_` | `uint256` | Initial protocol share of incentive (mantissa). | +| `closeFactor_` | `uint256` | Initial close factor (mantissa). | +| `acm_` | `address` | Venus AccessControlManager address. | + +### setLiquidatorWhitelist + +Add or remove a liquidator from the whitelist. + +**Note:** event: LiquidatorWhitelistUpdated + +```solidity +function setLiquidatorWhitelist(address liquidator, bool approved) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `liquidator` | `address` | Address to update. | +| `approved` | `bool` | Whether to approve or remove. | + +### setSettlerWhitelist + +Add or remove a settler from the whitelist. + +**Note:** event: SettlerWhitelistUpdated + +```solidity +function setSettlerWhitelist(address settler, bool approved) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `settler` | `address` | Address to update. | +| `approved` | `bool` | Whether to approve or remove. | + +### setProtocolLiquidationShare + +Set the protocol share of the liquidation incentive (mantissa). + +**Notes:** +- error: InvalidShare if share > 1e18. +- event: ProtocolLiquidationShareUpdated + +```solidity +function setProtocolLiquidationShare(uint256 share) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `share` | `uint256` | New share (0 <= share <= 1e18). | + +### setCloseFactor + +Set max fraction of debt repayable per liquidation. + +**Notes:** +- error: InvalidCloseFactor if zero or > 1e18. +- event: CloseFactorUpdated + +```solidity +function setCloseFactor(uint256 newCF) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `newCF` | `uint256` | New close factor (0 < newCF <= 1e18). | + +### sweepProtocolShareToReserve + +Transfer accrued protocol share for the given collateral token to PSR. + +**Note:** event: ProtocolShareSweptToReserve + +```solidity +function sweepProtocolShareToReserve(address collateral) external; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `collateral` | `address` | Collateral token address. | + +### liquidate + +HF-based liquidation. Whitelisted liquidator only. + +```solidity +function liquidate( + address vault, + uint256 repayAmount +) external onlyWhitelistedLiquidator nonReentrant; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address to liquidate. | +| `repayAmount` | `uint256` | Amount of supply asset to repay. | + +### liquidateOverdueVault + +Deadline-based liquidation. Whitelisted settler only. + +```solidity +function liquidateOverdueVault( + address vault, + uint256 repayAmount +) external onlyWhitelistedSettler nonReentrant; +``` + +**Parameters** + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `vault` | `address` | Vault address to liquidate. | +| `repayAmount` | `uint256` | Amount of supply asset to repay. | + +### renounceOwnership + +Disabled — renouncing ownership would permanently brick ACM-gated liquidation governance. + +**Note:** error: OwnershipCannotBeRenounced Always reverts. + +```solidity +function renounceOwnership() public pure override; +``` + +## Events + +### LiquidatorWhitelistUpdated + +```solidity +event LiquidatorWhitelistUpdated(address indexed liquidator, bool approved); +``` + +### SettlerWhitelistUpdated + +```solidity +event SettlerWhitelistUpdated(address indexed settler, bool approved); +``` + +### ProtocolLiquidationShareUpdated + +```solidity +event ProtocolLiquidationShareUpdated(uint256 oldShare, uint256 newShare); +``` + +### CloseFactorUpdated + +```solidity +event CloseFactorUpdated(uint256 oldCloseFactor, uint256 newCloseFactor); +``` + +### LiquidationCollateralSplit + +```solidity +event LiquidationCollateralSplit(uint256 totalSeized, uint256 protocolAmount, uint256 callerAmount); +``` + +### ProtocolShareSweptToReserve + +```solidity +event ProtocolShareSweptToReserve(address indexed collateral, uint256 amount); +``` + +## Errors + +### NotWhitelistedLiquidator + +```solidity +error NotWhitelistedLiquidator(); +``` + +### NotWhitelistedSettler + +```solidity +error NotWhitelistedSettler(); +``` + +### VaultNotRegistered + +```solidity +error VaultNotRegistered(); +``` + +### InvalidShare + +```solidity +error InvalidShare(); +``` + +### InvalidCloseFactor + +```solidity +error InvalidCloseFactor(); +``` + +### InvalidAddress + +```solidity +error InvalidAddress(); +``` + +### ZeroRepayAmount + +```solidity +error ZeroRepayAmount(); +``` + +### OwnershipCannotBeRenounced + +```solidity +error OwnershipCannotBeRenounced(); +``` diff --git a/technical-reference/reference-technical-articles/fixed-rate-vaults.md b/technical-reference/reference-technical-articles/fixed-rate-vaults.md new file mode 100644 index 0000000..8aa2c26 --- /dev/null +++ b/technical-reference/reference-technical-articles/fixed-rate-vaults.md @@ -0,0 +1,258 @@ +# Fixed Term Vaults + +A Fixed Term Vault is a two-party, fixed-term loan between an institution and a pool of on-chain suppliers. The institution borrows a stablecoin against crypto collateral at a rate and duration agreed at vault creation. Suppliers commit capital during a short fundraising window; at maturity they redeem their shares for principal plus the fixed interest, regardless of market conditions between entry and settlement. + +Each vault is a fully isolated contract clone — its own collateral, its own debt, its own supplier shares. A default, liquidation, or governance action in one vault has zero effect on any other vault or on Venus core markets. + +## Architecture + +### Contracts + +The system is composed of four contracts. Each vault is an independent clone deployed by the controller — there is no shared state between loans. + +
Fixed Term Vault contract architecture diagram

The controller deploys a fresh vault clone and mints a position NFT per loan; all liquidation calls are routed through the LiquidationAdapter

+ +**[InstitutionalVaultController](../reference-fixed-rate-vaults/institutional-vault-controller.md)** is the sole factory and the only conduit through which any admin operation reaches a vault. It deploys each vault clone and mints the corresponding position NFT in the same transaction. It also exposes the ACM-gated lifecycle calls (`openVault`, `cancelVault`, `partialPauseVault`, `completePauseVault`, `unpauseVault`, `closeVault`) and risk-parameter updates that governance invokes separately over the life of each vault. All ACM permission checks are enforced here — individual vaults carry no ACM wiring and trust calls from the controller implicitly. Deployed behind a transparent proxy, so governance policy can be updated without touching live contracts. + +**[InstitutionalLoanVault](../reference-fixed-rate-vaults/institutional-loan-vault.md)** is the core execution contract for a single loan. It holds all assets — collateral and supply stablecoin — enforces the `VaultState` machine, and is the only place debt is created, tracked, and cleared. Suppliers interact through the standard ERC-4626 interface (`deposit`, `mint`, `withdraw`, `redeem`); institution-side calls (`depositCollateral`, `claimRaisedFunds`, `withdrawCollateral`) are gated by `onlyPositionHolder`; liquidation entry points are gated by `onlyLiquidationAdapter`. Each vault is deployed as an EIP-1167 minimal proxy clone — non-upgradeable and single-use. Its rules are immutable from the moment it goes live, and renewing a deal always means deploying a fresh clone rather than resetting an existing one. + +**[InstitutionPositionToken](../reference-fixed-rate-vaults/institution-position-token.md)** is a singleton ERC-721 shared across all vaults — one contract, one token ID per vault. Whoever holds a given token ID controls all institution-side operations on that vault, since `depositCollateral`, `claimRaisedFunds`, and `withdrawCollateral` all resolve to `positionToken.ownerOf(positionTokenId)` at call time. Keeping ownership in a transferable NFT rather than hardcoded in the vault means control can move to a new address without any state change inside the vault itself. Every transfer requires a prior single-use governance approval via `approvePositionTransfer(vault, recipient)`, consumed on the next `safeTransferFrom`. + +**[LiquidationAdapter](../reference-fixed-rate-vaults/liquidation-adapter.md)** is the only address permitted to call `vault.liquidate()`, enforced by `onlyLiquidationAdapter` on each vault. The vault itself does one thing: check whether the health factor permits liquidation. Everything else — who is allowed to liquidate, how much they can seize, at what incentive rate, and what share goes to the protocol — is owned entirely by the adapter. Whitelisted liquidators and overdue settlers are registered here via two independent ACM-gated lists. Parameters like `closeFactor` and `protocolLiquidationShare` live here too, meaning governance can tune liquidation behaviour across the entire system without touching any deployed vault. The adapter also accumulates the protocol's share of seized collateral and transfers it to PSR via `sweepProtocolShareToReserve(address collateral)`. + +### BaseVault, ERC-4626, and extensibility + +`InstitutionalLoanVault` inherits `BaseVault`, an abstract contract built on top of ERC-4626 that was designed to be reusable across different kinds of fixed-term vault. `BaseVault` captures everything that any such vault has in common — fundraising, interest computation, the settlement waterfall, state machine scaffolding, and the pause system — so a new vault type only has to implement what is specific to its loan structure. Future vault types follow the same pattern: inherit `BaseVault`, override its three virtual hooks (`_checkAndAdvanceState`, `_afterWithdrawHook`, `_beforeClaimRaisedFunds`), and add the remaining type-specific logic on top. + +ERC-4626 is used as the supplier-facing API, but several methods deviate from the specification to enforce lifecycle constraints: + +| Method | Standard behaviour | Vault behaviour | +| --- | --- | --- | +| `deposit` | Always open; reverts on cap breach | Only in `Fundraising`; excess silently clamped to remaining cap, not reverted | +| `mint` | Always open; reverts on cap breach | Only in `Fundraising`; excess silently clamped | +| `withdraw` | Available any time (subject to balance) | Only in terminal states (`Matured`, `Failed`, `Liquidated`) | +| `redeem` | Available any time (subject to balance) | Only in terminal states | +| `maxDeposit` | Returns `type(uint256).max` | Returns `maxBorrowCap − totalRaised`; zero outside `Fundraising` | +| `maxWithdraw` | Returns asset value of shares | Zero outside terminal states | +| `maxRedeem` | Returns `balanceOf(owner)` | Zero outside terminal states | +| `totalAssets` | Returns live underlying balance | Returns `totalRaised` pre-terminal; switches to `settlementAmount` once settled | + +`totalAssets()` is backed by internal accounting variables (`totalRaised` and `settlementAmount`) rather than `balanceOf(address(this))`. Tokens sent directly to the vault address have no effect on the share price, which removes the donation-based inflation attack that affects naive ERC-4626 implementations. + +`minSupplierDeposit` adds a minimum deposit floor absent from the spec. The floor is waived for the final deposit that fills the remaining capacity exactly, preventing the vault from getting permanently stuck below `maxBorrowCap` when the residual slot is smaller than the minimum. Fee-on-transfer and rebasing tokens are not supported for either the supply asset or collateral. + +### Oracle + +All USD valuations in the system route through Venus `ResilientOracle`. The vault calls `oracle.getPrice(asset)`, which returns a price scaled to `36 − asset.decimals()` decimal places — always expressed as an 18-decimal USD value per token unit regardless of the token's own decimals. A zero price reverts with `InvalidOraclePrice`. + +Both the supply asset and the collateral asset must have a non-zero oracle price at vault creation. The controller probes the oracle during `createVault` and reverts with `InvalidConfig` if either price is missing. This prevents a vault from entering price-dependent states — liquidation checks, claim validation, bad-debt detection — with an asset the oracle cannot price. + +## State machine + +The vault tracks lifecycle as a `VaultState` enum. Transitions are monotonic — no state ever goes backward. Every non-view entry point calls `_checkAndAdvanceState()` before its own logic, so state advances automatically on the first relevant call after a trigger condition is met. All transitions after `openVault` are automatic — no governance call is needed to move the vault from `Fundraising` through `Lock`, `PendingSettlement`, and into a terminal state. For cases where no interaction is pending but conditions for a transition are already met, anyone can call `updateVaultState()` to advance the state explicitly. + +
Fixed Term Vault state machine diagram

State transitions are monotonic — no state ever goes backward. Dashed red paths show cancel and failure routes; the grey arrows at the bottom show all three terminal states collapsing into Closed via closeVault()

+ +## Core mechanics + +### Margin deposit + +Before suppliers can interact, the institution must post a security deposit — the required margin — in a single `depositCollateral` call. It acts as a credible commitment: the institution either tops up collateral to `idealCollateralAmount` before fundraising closes or forfeits the margin to suppliers. The vault stays in `WaitingForMargin` until this condition is met: + +$$\text{requiredMargin} = \text{idealCollateralAmount} \times \frac{\text{marginRate}}{10^{18}}$$ + +Once `totalCollateralDeposited ≥ requiredMargin` the vault advances to `MarginDeposited`, where it waits for governance to inspect the configuration and call `openVault()` to open the fundraising window. + +### Fundraising + +`deposit` and `mint` are permissionless during `Fundraising`. Unlike standard ERC-4626, both calls silently clamp to remaining capacity (`maxBorrowCap - totalRaised`): a `deposit` that exceeds the cap fills the residual and mints fewer shares than requested, rather than reverting. `minSupplierDeposit` is enforced on every call except one that fills or exceeds the remaining capacity — because the residual may be smaller than the minimum, skipping the check on that deposit prevents the vault from being permanently stuck below `maxBorrowCap`. Share tokens are standard ERC-20s, freely transferable at all times. + +At `fundraisingEndTime` both sides are evaluated simultaneously. If `totalRaised ≥ minBorrowCap` and `totalCollateralDeposited ≥ idealCollateralAmount` the vault transitions to `Lock` and the loan begins. + +If either condition is not met, the vault transitions to `Failed`. Two distinct failure modes are possible, distinguished by `InstitutionalRuntime.institutionDefaulted`: + +**Raise shortfall** (`totalRaised < minBorrowCap` at window close). `institutionDefaulted = false`. No default has occurred; suppliers recover full principal and the institution recovers all collateral including the margin. + +**Collateral underdelivery** (`totalRaised ≥ minBorrowCap` but `totalCollateralDeposited < idealCollateralAmount` at window close). `institutionDefaulted = true`. Only the pre-determined margin is confiscated, not the institution's full collateral position. `confiscatedMargin` is set to exactly `requiredMargin`: + +$$\text{requiredMargin} = \text{idealCollateralAmount} \times \frac{\text{marginRate}}{10^{18}}$$ + +Suppliers recover principal plus a pro-rata share of `confiscatedMargin` (see [Margin confiscation](#margin-confiscation-collateral-underdelivery) for the per-redemption distribution). The institution recovers `totalCollateralDeposited - confiscatedMargin`. + +#### Margin confiscation: collateral underdelivery + +When the vault fails via collateral underdelivery, `confiscatedMargin = requiredMargin`. Each `withdraw` / `redeem` triggers `_afterWithdrawHook`, which distributes a pro-rata slice of the remaining confiscated margin: + +$$\text{compensation} = \text{confiscatedMarginRemaining} \times \frac{\text{shares}}{\text{totalSupplyBeforeBurn}}$$ + +Compensation is denominated in the **collateral asset** (e.g. BTC or ETH), not the supply stablecoin. `confiscatedMarginRemaining` decrements on every redemption, so early redeemers and late redeemers receive the same proportion. + +### Lock entry and borrowing + +When the vault transitions to `Lock`, two values are fixed for the lifetime of the loan: + +**Total interest** is stored immediately as `totalDebt` and is owed in full regardless of when the institution repays — there is no early-repayment discount: + +$$\text{totalInterest} = \frac{\text{totalRaised} \times \text{fixedAPY} \times \text{lockDuration}}{\text{BPS} \times \text{YEAR}}$$ + +`BPS = 10000`, `YEAR = 365 days`. `totalDebt = totalInterest` at lock entry; after `claimRaisedFunds` it becomes `totalInterest + totalRaised`. + +**Minimum collateral floor** is recalculated proportionally to the actual raise. When the raise underfills `maxBorrowCap`, the floor scales down, freeing excess collateral above it for withdrawal during `Lock`: + +$$\text{minimumCollateralRequired} = \text{idealCollateralAmount} \times \frac{\text{totalRaised}}{\text{maxBorrowCap}}$$ + +#### Claiming raised funds + +`claimRaisedFunds()` is a one-shot call (gated by `fundsWithdrawn`), available only in `Lock`. It transfers the entire supply asset balance to the position-NFT holder. Before releasing funds, it simulates **interest plus principal** against current collateral via `_getHypotheticalVaultLiquidity(0, totalRaised)` — not just the principal being claimed. The call reverts with `ClaimWouldBreachLT` if the combined debt would breach the liquidation threshold. + +After a successful claim, `totalDebt = totalInterest + totalRaised` — the full lifetime obligation. + +#### Repaying + +`repay(amount)` is unrestricted: any address can reduce `totalDebt` by pulling supply asset from its own balance. This is intentional — third parties can service the debt without holding the position NFT. Available in `Lock`, `PendingSettlement`, and `SettlementDeadlineExceeded`; overpayment silently clamps to `outstandingDebt()`. + +#### Collateral during Lock + +The institution may add or withdraw collateral during `Lock`. Withdrawal requires both checks to pass: + +1. **Floor check** — `totalCollateralDeposited − amount ≥ minimumCollateralRequired`. The locked floor cannot be touched. +2. **LT health check** — the post-withdrawal state must not produce an LT shortfall. + +The tighter of the two determines how much can be withdrawn. + +### Settlement window + +At `lockEndTime` the vault enters `PendingSettlement`. This state is never skipped — even if the institution cleared all debt before the lock expired, the vault holds in `Lock` until `block.timestamp ≥ lockEndTime`, then moves to `PendingSettlement`, and only transitions to `Matured` once `outstandingDebt() == 0`. + +The institution has until `settlementDeadline` to repay in full. `repay()` remains available and unrestricted throughout. If the debt is cleared before the deadline the vault moves to `Matured` and the settlement waterfall runs. If the deadline passes with debt still outstanding the vault enters `SettlementDeadlineExceeded` — the institution may still repay voluntarily, but whitelisted settlers can now trigger overdue liquidation at the late-penalty rate (see [Overdue](#overdue)). + +### Settlement waterfall + +On entry to `Matured` or `Liquidated`, `_settleProtocolShare` runs once and distributes the supply asset balance: + +| Branch | Condition | Protocol fee | `settlementAmount` | +| --- | --- | --- | --- | +| Full repayment | `available ≥ totalRaised + totalInterest` | `totalInterest × reserveFactor` | `available − protocolFee − surplus` | +| Partial interest shortfall | `totalRaised < available < totalRaised + totalInterest` | `(available − totalRaised) × reserveFactor` | `available − protocolFee` | +| Principal shortfall | `available ≤ totalRaised` | 0 | `available` | + +Surplus above `totalRaised + totalInterest` is forwarded to PSR. `ShortfallDetected(expected, available)` fires in the shortfall branches. + +`totalAssets()` returns `totalRaised` throughout `Lock` and `PendingSettlement`, then switches to `settlementAmount` once the vault settles — the conversion anchor for all ERC-4626 share-to-asset calculations on redemption. Each supplier's payout on redemption is: + +$$\text{payout} = \text{shares} \times \frac{\text{settlementAmount}}{\text{totalSupply}}$$ + +### Liquidation + +#### Health factor + +The vault's health is computed by `_getHypotheticalVaultLiquidity`, which follows the same accounting approach as Compound V2 (the protocol Venus is built on) — collateral is weighted by a liquidation threshold and compared against outstanding debt, both expressed in USD: + +$$\text{LT-cap} = \frac{\text{collateralUSD} \times \text{liquidationThreshold}}{10^{18}}$$ + +$$\begin{cases} \text{liquidity} = \text{LT-cap} - \text{debtUSD} & \text{if } \text{debtUSD} \leq \text{LT-cap} \\ \text{shortfall} = \text{debtUSD} - \text{LT-cap} & \text{otherwise} \end{cases}$$ + +`liquidationThreshold` is a mantissa (e.g. `0.85e18` = 85%). A shortfall greater than zero means the vault is liquidatable. The same function is used with non-zero `withdrawAmount` or `additionalDebt` arguments to simulate hypothetical state changes — called by `claimRaisedFunds` and `withdrawCollateral` before executing the action, so neither operation can push the vault into an underwater position. + +#### Seize calculation and Compound V2 lineage + +The collateral seize formula is taken directly from Compound V2. In Compound V2, liquidators repay debt in the borrowed asset and receive collateral at a bonus rate. The same principle applies here: the repaid value is converted to USD, multiplied by the incentive multiplier, then divided by the collateral price to arrive at collateral units: + +$$\text{seizeAmount} = \frac{\text{repayAmount} \times \text{supplyPrice} \times \text{incentive}}{10^{18} \times \text{collateralPrice}}$$ + +`incentive` is `liquidationIncentive` for health-based liquidations and `latePenaltyRate` for overdue liquidations. Both are mantissa-encoded multipliers greater than `1e18` — an incentive of `1.1e18` means the liquidator receives 10% more collateral than the repaid debt's USD value. Prices come from `ResilientOracle.getPrice()` scaled to `36 − asset.decimals()` decimal places. + +The vault transfers the full `seizeAmount` to `LiquidationAdapter`. The adapter then isolates the bonus slice and takes the protocol's share of that slice only — not of the full seizure: + +``` +repayEquivalent = totalSeized × MANTISSA_ONE / incentive +incentiveAmount = totalSeized − repayEquivalent +protocolAmount = incentiveAmount × protocolLiquidationShare / MANTISSA_ONE +callerAmount = totalSeized − protocolAmount +``` + +This mirrors Compound V2's `liquidationIncentiveMantissa` accounting: the protocol treasury participates only in the bonus, leaving the principal-equivalent collateral recovery entirely with the liquidator. + +#### Health-based + +Available in `Lock`, `PendingSettlement`, and `SettlementDeadlineExceeded` when the vault has a non-zero shortfall (see [Health factor](#health-factor) above). + +Whitelisted liquidators call `LiquidationAdapter.liquidate(vault, repayAmount)`. The adapter validates vault registration and forwards to `vault.liquidate(repayAmount)` through the `onlyLiquidationAdapter` modifier. Inside the vault, `liquidate()` checks for an LT shortfall and reverts with `NotLiquidatable` if none exists; `_executeLiquidation` then enforces the close factor: if `actualRepay > outstandingDebt × closeFactor` the call reverts with `ExceedsCloseFactor` — it is a hard revert, not a silent cap. (`actualRepay` is `min(repayAmount, outstandingDebt)` — the close-factor check fires on the clamped value, not the raw input.) Seized collateral is split between the caller and the protocol per the formula above. + +A health-based liquidation does not directly trigger a state transition. The vault advances normally — through `PendingSettlement` and into `Matured` once debt is zero. The `Liquidated` state is never reached via health-based liquidation. + +#### Overdue + +Available once the vault enters `SettlementDeadlineExceeded`. No LT shortfall is required — the time breach alone qualifies. The same `closeFactor` cap applies, but collateral is seized at `latePenaltyRate` instead of `liquidationIncentive`. A vault that breaches both the LT cap and the deadline can be liquidated through either path; the chosen path determines the bonus rate. Like health-based liquidation, an overdue liquidation that clears all debt transitions the vault to `Matured`, not `Liquidated`. The `Liquidated` state is reached exclusively via `repayBadDebt`. + +#### Bad-debt rescue + +Available in `Lock`, `PendingSettlement`, and `SettlementDeadlineExceeded` whenever the USD value of deposited collateral falls below the USD value of outstanding debt. `repayBadDebt` is permissionless — any address may call it. The repayment must be large enough to reduce `totalDebt` to at most `totalInterest` in a single call (i.e., the principal must be fully covered); the call reverts with `InsufficientRepayment` otherwise. Once that condition is met the vault transitions to `Liquidated` and the settlement waterfall runs immediately over the combined supply asset balance. Without a rescue the vault remains in whichever state it was in and suppliers have no recourse beyond ordinary liquidation. + +## Risk parameters + +All tunable parameters grouped by contract, mutability, and who sets them. + +**Set once at vault creation via `createVault` — fixed for the life of the vault:** + +| Parameter | Location | Units | Constraint | +| --- | --- | --- | --- | +| Supply asset | `VaultConfig.supplyAsset` | address | Must have non-zero oracle price; must differ from collateral | +| Collateral asset | `InstitutionalConfig.collateralAsset` | address | Must have non-zero oracle price; must differ from supply asset | +| Target APR | `VaultConfig.fixedAPY` | basis points | 1 – 10 000 | +| Reserve factor | `VaultConfig.reserveFactor` | mantissa | ≤ `1e18` | +| Minimum borrow cap | `VaultConfig.minBorrowCap` | supply asset units | > 0; ≤ `maxBorrowCap` | +| Maximum borrow cap | `VaultConfig.maxBorrowCap` | supply asset units | ≥ `minBorrowCap` | +| Minimum supplier deposit | `VaultConfig.minSupplierDeposit` | supply asset units | 0 = no floor | +| Fundraising duration | `VaultConfig.openDuration` | seconds | > 0 | +| Lock duration | `VaultConfig.lockDuration` | seconds | > 0 | +| Settlement window | `VaultConfig.settlementWindow` | seconds | > 0 | +| Ideal collateral amount | `InstitutionalConfig.idealCollateralAmount` | collateral token units | > 0 | +| Margin rate | `InstitutionalConfig.marginRate` | mantissa | 0 < rate ≤ `1e18` | + +**Set at vault creation — mutable per vault by governance via the controller:** + +| Parameter | Location | Units | Constraint | +| --- | --- | --- | --- | +| Liquidation threshold | `RiskConfig.liquidationThreshold` | mantissa | 0 < LT ≤ `1e18`; `LT × LI < 1e36`; `LT × latePenaltyRate < 1e36` | +| Liquidation incentive | `RiskConfig.liquidationIncentive` | mantissa | `1e18 < LI ≤ 1.5e18`; `LT × LI < 1e36` | +| Late penalty rate | `RiskConfig.latePenaltyRate` | mantissa | `1e18 < rate ≤ 1.5e18`; `LT × rate < 1e36` | + +**Held on `LiquidationAdapter` — global across all vaults, mutable by governance:** + +| Parameter | Field | Constraint | +| --- | --- | --- | +| Close factor | `closeFactor` | 0 < CF ≤ `1e18` | +| Protocol liquidation share | `protocolLiquidationShare` | ≤ `1e18` | + +The constraint `LT × LI < 1e36` (and the equivalent for `latePenaltyRate`) ensures that a liquidation always improves vault health rather than worsening it. The controller enforces this invariant on both creation (`_validateLiquidationInvariant`) and every subsequent per-vault update. + +## Governance and access control + +### Pause system + +A two-level pause is controlled by governance via `partialPauseVault` / `completePauseVault`: + +| Level | Blocked | Live | +| --- | --- | --- | +| **Partial** | `deposit`, `mint`, `depositCollateral`, `claimRaisedFunds` | `repay`, `liquidate`, `withdraw`, `redeem` | +| **Complete** | All vault interactions | — | + +By design, governance can freeze new supply and collateral operations without interrupting active debt service or liquidations. + +### Position NFT + +`depositCollateral`, `claimRaisedFunds`, and `withdrawCollateral` are gated by `onlyPositionHolder`, which checks `positionToken.ownerOf(positionTokenId) == msg.sender` at call time. Transferring the NFT immediately reassigns control of all three. `repay` carries no such guard — intentional, for the permissionless debt-service case. + +NFT transfers require a single-use governance approval: `approvePositionTransfer(vault, recipient)` records the approved target, consumed on the next `safeTransferFrom`. `revokePositionTransfer` cancels a pending approval before it is used. + +### ACM permissions + +Governance operations (create, open, cancel, pause, close, risk-parameter updates) route through `InstitutionalVaultController` and are gated per selector by the Venus AccessControlManager. Liquidation entry points on the vault are gated by `onlyLiquidationAdapter`; the adapter maintains its own ACM-gated whitelists for liquidators and settlers independently. + +## Further reading + +- [Supplier Guide](../../guides/fixed-rate-vaults/supplier-guide.md) +- [Institution Guide](../../guides/fixed-rate-vaults/institution-guide.md) +- [Solidity API Reference](../reference-fixed-rate-vaults/README.md) +- [Repository](https://github.com/VenusProtocol/fixed-rate-vaults) diff --git a/whats-new/fixed-rate-vaults.md b/whats-new/fixed-rate-vaults.md new file mode 100644 index 0000000..b94f278 --- /dev/null +++ b/whats-new/fixed-rate-vaults.md @@ -0,0 +1,62 @@ +# Fixed Term Vaults + +Venus Protocol is introducing a new way to earn and borrow: **Fixed Term Vaults**. + +Instead of the variable rates and shared liquidity pools of Venus core markets, Fixed Term Vaults offer something simpler and more predictable. An institution wants to borrow stablecoins for a set period at a set rate. Suppliers fund that loan, earn a target APR, and get their principal back at maturity. The rate and lock duration are set at vault creation — no mid-term rate changes. + +Each vault is **entirely self-contained**. It involves one stablecoin, one institution, and one contract. It shares no liquidity, no risk parameters, and no liquidation flow with Venus core markets or any other vault. Each vault stands or falls on its own. + +Every vault implements the **ERC-4626 tokenised vault standard**, so suppliers interact through the familiar `deposit`, `withdraw`, `redeem`, and `balanceOf` interface — no custom integration required. Share tokens are standard ERC-20s, freely transferable at any point in the vault's life. + +
Fixed Term Vault fund flow diagram

Suppliers supply stablecoins into the vault, the institution receives the loan and repays with interest, and suppliers redeem principal plus target yield at maturity

+ +## How a Vault Progresses + +
Fixed Term Vault state machine diagram

Fixed Term Vault state transitions

+ +Every vault follows the same journey from creation to close. States move in one direction only — there's no going back. + +1. **Waiting for margin** — the vault exists on-chain, but the institution must post the full required collateral margin in a single transaction before anything else can happen. +2. **Margin deposited** — the margin is locked in escrow. Governance reviews the vault and calls `openVault()` to begin the fundraising window. +3. **Fundraising** — suppliers can now supply. The vault accepts stablecoins up to its maximum borrow cap. During this same window, the institution tops up their collateral to the required level. Both sides must complete their part before the window closes. +4. **Lock** — fundraising succeeded. The fixed-term loan begins. Total interest is computed and fixed immediately as a single lump sum — the full lifetime obligation is known from this moment. +5. **Pending settlement** — the lock period has ended. The institution now has until the settlement deadline to repay principal plus interest in full. +6. **Settlement deadline exceeded** — the deadline passed with debt still outstanding. The institution can still repay voluntarily; if they don't, whitelisted settlers can trigger overdue liquidation. +7. **Terminal states** — the vault resolves into one of three outcomes, after which governance calls `closeVault()`: + - **Matured** — the institution repaid in full. Suppliers redeem their shares for principal plus yield. + - **Failed** — two distinct cases: (a) *Raise shortfall* — not enough suppliers funded the vault; suppliers recover their principal and the institution recovers all collateral including the margin. (b) *Collateral underdelivery* — the raise met its minimum but the institution didn't post full collateral by close; the margin is confiscated and distributed pro-rata to suppliers in the collateral asset. + - **Liquidated** — bad-debt rescue completed. The collateral value fell below outstanding debt and a permissionless repayer covered the principal; suppliers redeem against the settlement waterfall over the remaining assets. + +## Participants + +### Suppliers + +Fixed Term Vaults are designed to give lenders certainty: + +- **You know your target yield upfront.** The target APR and lock duration are set before fundraising opens — there's nothing to guess or monitor. +- **Collateral is posted before you can supply.** The institution's margin is on-chain and locked before the fundraising window opens. Combined with full vault isolation, a default in one vault cannot affect any other vault or Venus core markets. +- **Your position stays liquid.** Vault shares are transferable ERC-20s. You can move or sell them to another party at any time during the vault's life. + +### Institutions + +Fixed Term Vaults give borrowers control over their cost of capital: + +- **Predictable cost.** The target APR is fixed at vault creation — no variable-rate exposure over the loan term. +- **Your collateral stays safe.** It is locked in the vault contract and never lent out or rehypothecated — no third party can touch it. If it appreciates during the loan, that upside is still entirely yours. +- **Plan your repayment from day one.** The total amount owed is calculable at lock entry, so there are no surprises when the settlement window opens. + +## Liquidations + +Fixed Term Vaults run their own liquidation system, independent of Venus core. Two paths exist: + +- **Health-based liquidation** — available during the Lock and settlement phases if the vault's outstanding debt exceeds the liquidation-threshold value of its collateral. Whitelisted liquidators repay a portion of the debt (capped by the global close factor) and receive collateral at the liquidation incentive rate. A share of the bonus goes to the protocol. +- **Overdue liquidation** — available once the institution has missed the settlement deadline, regardless of collateral health. The same close-factor cap applies, but collateral is seized at the late-penalty rate. + +Both paths route through the `LiquidationAdapter`, which maintains separate ACM-gated whitelists for health-based liquidators and overdue settlers. Direct vault calls are blocked. + +## Go Deeper + +- [Supplier Guide](../guides/fixed-rate-vaults/supplier-guide.md) — step-by-step walkthrough for lenders. +- [Institution Guide](../guides/fixed-rate-vaults/institution-guide.md) — step-by-step walkthrough for borrowers. +- [Fixed Term Vaults Technical Reference](../technical-reference/reference-technical-articles/fixed-rate-vaults.md) — contract architecture, math, and liquidation paths in full detail. +- [Solidity API Reference](../technical-reference/reference-fixed-rate-vaults/README.md) — full function-level reference for all contracts.