Deposit flow
LT.deposit puts the depositor into a 2× leveraged liquidity provider (LP) position in one transaction. LT is the market's vault contract. The depositor provides only the volatile asset, and LT borrows the matching crvUSD from the allocation of the leveraged AMM (LEVAMM). Use this trace to compute debt, predict the events, and diagnose a revert.
Prerequisites
Before you call LT.deposit, complete these items:
- Approve
LTforassetsof the asset token. - Compute
debtwith the rule in Computedebtbefore the call. - Simulate
depositwitheth_callbefore you send it.preview_depositchecks the deposit cap, but it does not run the LT deviation check. On the first deposit into an emptyLT, it does not check the safe band either.
Compute debt before the call
debt sets the LEVAMM leverage: at leverage 2, the borrowed crvUSD is about equal to the asset value. The price rule is debt = assets * pool.price_oracle() // 10**asset_decimals.
The rule to use depends on the pool policy, a contract that the Curve pool calls to set its swap fee, its price_scale target, or both. YBOraclizedPolicy is the oracle-report policy.
The LEVAMM-book debt is the debt that matches the debt-to-collateral ratio of the LEVAMM book, AMM.get_debt() / AMM.collateral_amount(). The LT deviation check of the policy compares debt with this value, within ratio_tolerance.
Use the price rule unless the pool runs a YBOraclizedPolicy that is not killed; then use the LEVAMM-book debt, or the deposit reverts with LT deviation.
For the policy check, the policy lookup, and the debt solver, read Compute debt on a pool that runs the oracle-report policy in Deposit and withdraw. For a flowchart of the rule, including the case of an empty LEVAMM book, read Choose the debt rule on the same page.
Safe band
After the deposit, the LEVAMM requires debt / collateral_value between 1/16 (6.25%) and 8.5/16 (53.125%). A deposit outside this band reverts with Unsafe min or Unsafe max.
A first deposit with debt = 0 reverts with Unsafe min.
Steps
The following figure shows the calls of one deposit, from the depositor to the borrower-interest donation at the end:
The deposit runs these steps in order:
- The depositor calls
LT.deposit(assets, debt, min_shares, receiver).receiverdefaults to the caller.LTfirst checks thatreceiveris notLT.staker(), the gauge of the market. LTpullsdebtcrvUSD from the LEVAMM address, which holds the crvUSD allocation of the market. The Factory allocates that crvUSD throughLT.allocate_stablecoins.LTthen pullsassetsfrom the caller.LTcallsadd_liquidity([debt, assets], 0, amm, False)on the Curve pool, and the pool mints the LP tokens to the LEVAMM. On a v3.0.0 pool (markets 7 to 10) with a non-zeroPOLICY(), the pool callsupdate_pool_stateon the policy. On a pool that runs the oracle-report policy, the policy runs the LT deviation check in this callback.LTreads the price with_price_oracle_w(). If shares exist,LTruns_calculate_valuesbefore it books the deposit._calculate_valuesbrings the staked, unstaked, and admin buckets up to date. The buckets are the parts of the market value thatLTaccounts for separately.LT.liquidity()storestotal,staked,ideal_staked(the watermark), andadmin. For how_calculate_valuesallocates a value change between the buckets, read_calculate_values()in LT.LTcallsAMM._deposit(lp_tokens, debt). The LEVAMM adds the collateral and the debt, checks the safe band, and returns the position value after the deposit.LTchecks that value againstAMM.max_debt() / 2.LTcomputes the shares and checks the share remainder: the share supply after the deposit must be at leastMIN_SHARE_REMAINDER.LTthen checksmin_sharesand mints the shares toreceiver.LTcalls_checkpoint_gauge(), which checkpoints the gauge in theGaugeController. If that call reverts,LTtries once more with 200,000 gas.LTignores a second revert, and then emitsDeposit.LTruns_distribute_borrower_fees. It callsAMM.collect_fees(). IfLTthen holds crvUSD, it donates the crvUSD to the Curve pool withadd_liquidity([amount, 0], min_amount, empty(address), True).
What happens inside the Curve pool
When LT calls add_liquidity, the Curve pool can move its price_scale toward its target. This move is not the LEVAMM rebalance. The two corrections differ in this way:
- Curve pool
price_scalemove. On markets 7 to 10, only the first operation in a block that callstweak_pricecan moveprice_scale. If that operation fails the profit test (vp_boosted > lp_xcp_profit),price_scaledoes not move in that block. Without a policy, the target isprice_oracle, the internal EMA of the pool. With a policy, the target comes from the policy. The move can spend part of the rebalance budget of the pool, the pool profit thatreserved_profit_fractiondoes not protect. - LEVAMM leverage correction. Arbitrage trades through
VirtualPoolmake this correction later. This deposit does not start it. For the trade, read the Rebalance flow.
Events emitted
A deposit can emit these events:
Pool.AddLiquidity(provider indexed, receiver indexed, token_amounts, fee, token_supply, price_scale): field list of the v3.0.0 pools of markets 7 to 10AMM.AddLiquidityRaw(token_amounts, invariant, price_oracle)LT.TransferbetweenLT.staker()and the zero address: only when the bucket update from step 4 changes the LT balance of a gaugeLT.Transfer(sender=0x0, receiver, value): the mintLT.Deposit(sender indexed, owner indexed, assets, shares)AMM.CollectFees(amount, new_supply): indistribute_borrower_feesPool.Donation(donor indexed, token_amounts), a secondPool.AddLiquidity, andLT.DistributeBorrowerFees(sender indexed, amount, min_amount, discount): only whenLTholds crvUSD to donate. The pool events are those of the v3.0.0 pools of markets 7 to 10.
The policy callback emits no event.
Reverts by step
A deposit can revert at these steps. Each row names the step of the walk-through in which the revert occurs. For the full list of strings, read Error codes.
| Step | Revert | Condition |
|---|---|---|
| 1 | "Deposit to staker" | receiver == LT.staker() |
| 3 | "LT deviation" | The pool runs YBOraclizedPolicy, the policy is not killed, and debt is more than ratio_tolerance from the proportional debt (from the pool policy, inside add_liquidity) |
| 3 | "!wl" | Markets 7 to 10: the pool LP allowlist is on and LT is not listed (from the Curve pool) |
| 5 | No message | The LEVAMM is killed (from AMM._deposit) |
| 5 | "Unsafe min" | debt / collateral_value after the deposit is below 1/16 (from AMM._deposit) |
| 5 | "Unsafe max" | debt / collateral_value after the deposit is above 8.5/16 (from AMM._deposit) |
| 5 | "Debt too high" | The LEVAMM value after the deposit is more than AMM.max_debt() / 2 |
| 6 | "Remainder too small" | shares + supply < MIN_SHARE_REMAINDER. The constant is 10**6 share units, which is 10⁻¹² yb-LP |
| 6 | "Slippage" | The computed shares are less than min_shares |
| 7 | "GAS" | The first GaugeController.checkpoint call fails and less than 200,000 gas remains for the retry |
| 8 | "slippage" or "donation above cap!" | Markets 7 to 10: the borrower-interest donation fails in the Curve pool (from the Curve pool). The donation runs only when LT holds crvUSD |
Gas
Gas depends on the pool and its policy. To estimate it, call eth_estimateGas or simulate the transaction in the target block.
Related
- LT: the full reference for
depositandpreview_deposit. - LEVAMM:
_deposit, the safe band, andmax_debt. - Pool policy: the LT deviation check and
ratio_tolerance. - Deposit and withdraw: the call pattern and the
debtsolver. - Compounding leverage: how 2× compounding leverage behaves as the price moves.