Add Llamalend v2 developer documentation#122
Conversation
New docs section covering the core lending infrastructure contracts for Llamalend v2: LendFactory, Vault, LendController, LendControllerView, and AMM (LLAMMA). Includes an overview page with architecture diagram, new features, security improvements, and Vyper module system explanation. Source code blocks and example blocks are left empty as the contracts are still being finalized (audit fixes ongoing). Also enables Mermaid diagram rendering site-wide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| | `max_amount` | `uint256` | Maximum input amount (slippage protection) | | ||
| | `_for` | `address` | Address to receive the output tokens | | ||
|
|
||
| Returns: `[in_amount, out_amount_done]` (`uint256[2]`). |
There was a problem hiding this comment.
out_amount_done can be less than out_amount if there is no enough liquidity in the AMM
| | `j` | `uint256` | Output token index | | ||
| | `in_amount` | `uint256` | Input amount | | ||
|
|
||
| Returns: `[in_amount_done, out_amount]` (`uint256[2]`). |
|
|
||
| ::::description[`AMM.get_amount_for_price(_p: uint256) -> uint256: view`] | ||
|
|
||
| Returns the amount of token 0 (borrowed token) needed to move the AMM price to `_p`. |
There was a problem hiding this comment.
It's wrong. It returns (amount, is_pump)
-
If
is_pump=True, it means that we do borrowed->collateral exchange (pumping collateral). In this caseamountis the borrowed token amount needed to pump the price to the passed_pvalue. -
If
is_pump=False, it means that we do collateral->borrowed exchange (dumping collateral). In this caseamountis the collateral token amount needed to dump the price to the passed_pvalue.
|
|
||
| ::::description[`AMM.price_oracle() -> uint256: view`] | ||
|
|
||
| Returns the current price from the external price oracle, smoothed by the AMM's internal EMA. |
There was a problem hiding this comment.
It's not smoothed, it's clamped by MAX_P_O_CHG: constant(uint256) = 12500 * 10**14 # <= 2**(1/3) - max relative change to have fee < 50%
|
|
||
| ::::description[`AMM.p_oracle_up(_n: int256) -> uint256: view`] | ||
|
|
||
| Returns the upper oracle price boundary of band `_n` (based on the oracle price, not the current AMM state). |
There was a problem hiding this comment.
It just gives a price grid we use. Like uniswap ticks. For the band n there are p_oracle_up(n) and p_oracle_down(n) - upper and lower borders of the band. They are constants and are calculated like:
# p_oracle_up(n) = p_base * ((A - 1) / A) ** n
# p_oracle_down(n) = p_base * ((A - 1) / A) ** (n + 1) = p_oracle_up(n+1)
Actually p_grid_up/down naming would be much better.
Also it worths to mention that band number can be negative. And it's counterintuitive: it's positive to the left from p_base and negative to the right. The higher n the lower the price.
|
|
||
| ::::description[`AMM.p_oracle_down(_n: int256) -> uint256: view`] | ||
|
|
||
| Returns the lower oracle price boundary of band `_n`. |
|
|
||
| :::: | ||
|
|
||
| ### `dynamic_fee` |
|
|
||
| ::::description[`AMM.read_user_tick_numbers(_user: address) -> int256[2]: view`] | ||
|
|
||
| Returns the upper and lower band indices of a user's position. |
There was a problem hiding this comment.
The order is [n1, n2]. n1 < n2 while p1 > p2
|
|
||
| ::::description[`AMM.bands_x(_n: int256) -> uint256: view`] | ||
|
|
||
| Returns the total amount of borrowed token in band `_n`. |
There was a problem hiding this comment.
With 18 decimals, it's not converted to real token decimals
|
|
||
| ::::description[`AMM.bands_y(_n: int256) -> uint256: view`] | ||
|
|
||
| Returns the total amount of collateral token in band `_n`. |
|
|
||
| ::::description[`AMM.fee() -> uint256: view`] | ||
|
|
||
| Returns the current swap fee. |
There was a problem hiding this comment.
It's default fee without the dynamic part (min possible)
|
|
||
| ::::description[`AMM.A() -> uint256: view`] | ||
|
|
||
| Returns the amplification coefficient. Band width is approximately `1/A`. |
There was a problem hiding this comment.
It's not "the amplification coefficient". It's "the band width factor"
|
|
||
| ::::description[`LendControllerView.liquidate_health_preview(_user: address, _full: bool) -> int256: view`] | ||
|
|
||
| Previews health after liquidation. |
|
|
||
| ### `create_loan` | ||
|
|
||
| ::::description[`LendController.create_loan(_collateral: uint256, _debt: uint256, _N: uint256)`] |
There was a problem hiding this comment.
It's changed:
def create_loan(
_collateral: uint256,
_debt: uint256,
_N: uint256,
_for: address = msg.sender,
_callbacker: address = empty(address),
_calldata: Bytes[CALLDATA_MAX_SIZE] = b"",
):
| @@ -0,0 +1,342 @@ | |||
| # LendControllerView | |||
There was a problem hiding this comment.
Update methods' signatures. It's changed
|
|
||
| ::::description[`LendController.add_collateral(_d_collateral: uint256, _for: address)`] | ||
|
|
||
| Adds collateral to an existing loan without borrowing more. Can be called by anyone on behalf of a borrower. |
There was a problem hiding this comment.
It's permissioned now. There are no permissionless methods anymore (except liquidationg unhealthy users)
|
|
||
| ::::description[`LendController.health(_user: address, _full: bool) -> int256: view`] | ||
|
|
||
| Returns the health of a user's loan. Health > 0 means the loan is safe; health < 0 means it can be liquidated. If `_full` is true, returns health assuming the worst-case scenario where all collateral has been soft-liquidated. |
There was a problem hiding this comment.
If _full is true it takes into account the collateral price
If _full is false it calculates the health like a user in the soft-liquidation mode
|
|
||
| :::: | ||
|
|
||
| ### `borrow_more_health_preview` |
There was a problem hiding this comment.
All the health_preview signatures have been changed
|
|
||
| ::::description[`LendController.available_balance() -> uint256: view`] | ||
|
|
||
| Returns the amount of borrowed tokens currently available for new loans — i.e., the tokens deposited by lenders minus what's already lent out. |
There was a problem hiding this comment.
It doesn't count donations which is important
|
|
||
| ::::description[`LendController.borrow_cap() -> uint256: view`] | ||
|
|
||
| Returns the maximum total debt allowed for this market. If 0, there is no cap. |
There was a problem hiding this comment.
I would mention that it's 0 by default and requires admin action (DAO vote) to become > 0
|
|
||
| :::: | ||
|
|
||
| ### `amm_price` |
|
|
||
| ::::description[`LendController.admin_percentage() -> uint256: view`] | ||
|
|
||
| Returns the current admin fee percentage. |
| | `_liquidation_discount` | `uint256` | Discount at which liquidation can occur | | ||
| | `_price_oracle` | `address` | Initialized price oracle contract | | ||
| | `_monetary_policy` | `address` | Initialized monetary policy contract | | ||
| | `_name` | `String[64]` | Name for the vault token | |
| | --- | --- | --- | | ||
| | `_n` | `uint256` | Market index (0-based) | | ||
|
|
||
| Returns: market struct containing vault, controller, amm, collateral token, borrowed token, and market index (`Market`). |
There was a problem hiding this comment.
Returns: vault, controller, amm, collateral token, borrowed token, price oracle and monetary policy addresses
|
|
||
| ::::description[`LendFactory.vaults_index(_vault: IVault) -> uint256: view`] | ||
|
|
||
| Returns the index of a vault in the factory registry. Uses a `2^128` offset internally — a return value of `0` means the vault is not registered. |
There was a problem hiding this comment.
Remove the second sentence. 0 desn't mean that
|
|
||
| n = factory.market_count() | ||
| for i in range(n): | ||
| vault_addr = factory.vaults(i) |
There was a problem hiding this comment.
factory.vaults(i) has been removed from LendFactory. Use factory.markets(i) instead
|
|
||
| ## New Features | ||
|
|
||
| - **Any token as borrowed asset** — crvUSD is no longer the only allowed borrowable token. Any ERC20-compliant token can be used to create a lending market. |
There was a problem hiding this comment.
"Any token pair" would be more correct. crvUSD was required to be in each pair either as borrowed or as collateral.
Summary
markdown.mermaid: truein docusaurus config)Status: Work in Progress
This PR is not final. The contracts are still being finalized as audit findings are being addressed. Specifically:
<SourceCode>) are intentionally left empty — they will be filled once the contract code is finalized and deployed<Example>) are also empty — will be added with live<ContractCall>components once deployment addresses are availableOnce the code is finalized and deployed, a follow-up pass will fill in all source code, examples, ABIs, and deployment info.
Test plan
yarn buildpasses with no new errors🤖 Generated with Claude Code