Stake and unstake flow
A stake moves yb-LP from a wallet into the gauge (LiquidityGauge) of the market. LT, the market's vault contract, treats the gauge as its staker. LT splits the value of the market into buckets: staked, unstaked, and admin fees. Every transfer into or out of the gauge moves value between the unstaked and staked buckets of LT. Staking does not touch the Curve pool, so a pool policy has no effect on these flows. For how LT allocates a value change between the buckets, read _calculate_values() in LT.
Prerequisites
Check these approvals before you call the gauge:
- Before you stake, approve the gauge on
LTforassets. The gauge pulls the yb-LP withLT.transferFrom. - To unstake the gauge shares of another
owner, the caller needs an allowance on the gauge shares ofowner.
Stake
The following figure shows the calls of a stake through LiquidityGauge.deposit:
The stake runs these steps in order:
- The user calls
LiquidityGauge.deposit(assets, receiver).assetsis an amount of yb-LP in LT share units, with 18 decimals. - The gauge calls
LT.checkpoint_staker_rebase(). The caller is the gauge,LT.staker(). While the LEVAMM is not killed,LTtherefore runs_calculate_valuesat_price_oracle_w().LTthen writes the updated buckets, the total supply, and the LT balance of the gauge. - The gauge checks
assetsagainstmaxDeposit(receiver)and convertsassetsto gauge shares. - The gauge runs
_checkpoint_user(receiver), which updates the reward integrals ofreceiverfor each reward token. For YB, it readsGaugeController.preview_emissions. - The gauge pulls the yb-LP with
LT.transferFrom(user, gauge, assets). InLT._transfer, the transfer goes to the gauge, soLTuses the values from step 2 and does not recompute them.LTthen updates the staked bucket:LTraisesstakedbytotal × assets / supply_tokens.totalis the total position value, andsupply_tokensis the share supply after the staker rebase, both from step 2.LTraisesideal_stakedin proportion to the LT balance of the gauge. When the gauge holds10**10share units or fewer,ideal_stakedrises by the same value asstaked.
- The gauge mints gauge shares to
receiverand emits the ERC-4626Depositevent. - The gauge runs
_check_min_shares(). The total gauge share supply must be 0 or at leastMIN_SHARES. - The gauge calls
GaugeController.emit(), which sends the gauge its accrued YB emissions.
LiquidityGauge.mint(shares, receiver) takes a gauge share amount instead and follows the same path. Its cap string is "erc4626: mint more than maximum".
Unstake
The following figure shows the calls of an unstake through LiquidityGauge.redeem:
The unstake runs these steps in order:
- The user calls
LiquidityGauge.redeem(shares, receiver, owner). - The gauge calls
LT.checkpoint_staker_rebase(), as in step 2 of the stake. - The gauge checks
sharesagainstmaxRedeem(owner), the gauge share balance ofowner. - The gauge converts
sharesto yb-LP and runs_checkpoint_user(owner). - If the caller is not
owner, the gauge spends the caller's allowance on the gauge shares ofowner. The gauge then burns the gauge shares ofowner. - The gauge sends the yb-LP with
LT.transfer(receiver, assets). InLT._transfer, the transfer leaves the gauge.LTlowersstakedbytotal × assets / supply_tokensand lowersideal_stakedin the same ratio as the LT balance of the gauge. The gauge emits the ERC-4626Withdrawevent. - The gauge runs
_check_min_shares(), as in step 7 of the stake. - The gauge calls
GaugeController.emit().
If LT.is_killed() is True, redeem lets the emergency admin withdraw for another owner. The call must use receiver == owner, or it reverts with "receiver".
withdraw(assets, receiver, owner) takes an amount of yb-LP instead of gauge shares and follows the same path. It has no killed-market branch.
While the LEVAMM is not killed, step 2 runs _calculate_values, which reads AMM.value_oracle(). If value_oracle() reverts, the unstake reverts too, and the staked shares stay in the gauge. They can leave when the oracle works again or after the market is killed. After a kill, checkpoint_staker_rebase() does nothing, so the unstake does not read the oracle.
To leave LT after the unstake, follow the Withdraw flow. LT.withdraw reverts with "Withdraw to/from staker" while the shares are still in the gauge.
Stake or unstake in one call
StakeZap combines a deposit with a stake, or an unstake with a withdrawal, in one transaction. Before you call it, give the zap the approvals in Approvals on the StakeZap page. The zap has two functions:
deposit_and_stake(gauge, assets, debt, min_shares, receiver)runs the Deposit flow with the zap as the LT receiver. It then stakes the minted yb-LP forreceiver.withdraw_and_unstake(gauge, shares, min_assets, receiver)redeems the gauge shares of the caller to the zap. It then withdraws from LT toreceiver.
deposit_and_stake passes debt to LT.deposit, so compute debt as in Compute debt before the call on the Deposit flow page. On a pool that runs the oracle-report policy, a debt from the asset price can revert with "LT deviation". Each call reverts with the strings of the steps it runs.
Events emitted
A stake or an unstake can emit these events:
- Gauge: ERC-4626
DepositorWithdraw, and ERC-20Transferof gauge shares. LT:Transferbetween the user and the gauge (LT.staker()).LT:Transferbetween the gauge and the zero address, when the rebase in step 2 changes the LT balance of the gauge.- YB:
Transferfrom theGaugeControllerto the gauge, whenemit()sends emissions.
Reverts by step
The tables in this section list only the reverts of these flows. For the full revert list of each gauge function, read the LiquidityGauge reference.
The stake can revert at these steps:
| Step | Revert | Condition |
|---|---|---|
| 2 | The revert of the oracle | The LEVAMM is not killed, and AMM.value_oracle() reverts (in LT.checkpoint_staker_rebase) |
| 3 | "erc4626: deposit more than maximum" | assets is above maxDeposit(receiver). The gauge sets this maximum to max_value(uint256) |
| 5 | No message | The gauge allowance on LT, or the yb-LP balance of the user, is below assets |
| 7 | "erc4626: leave MIN_SHARES" | The gauge share supply after the call is above 0 and below MIN_SHARES. MIN_SHARES is 10**12 raw units, which is 0.000001 gauge shares at 18 decimals |
The unstake can revert at these steps:
| Step | Revert | Condition |
|---|---|---|
| 2 | The revert of the oracle | The LEVAMM is not killed, and AMM.value_oracle() reverts (in LT.checkpoint_staker_rebase) |
| 3 | "erc4626: redeem more than maximum" | shares is above the gauge share balance of owner. For withdraw, the string is "erc4626: withdraw more than maximum", and the limit is maxWithdraw(owner) |
| 3 | "receiver" | LT.is_killed() is True, the caller is Factory.emergency_admin(), and receiver ≠ owner |
| 7 | "erc4626: leave MIN_SHARES" | The gauge share supply after the call is above 0 and below MIN_SHARES |
Related
- LT:
checkpoint_staker_rebaseand theliquiditybuckets. - LiquidityGauge: the full gauge reference, including rewards.
- StakeZap: the one-call deposit-and-stake path and its approvals.
- Watermark and recovery: what
ideal_stakedmeans for a staker. - Stake yb-LP: the user steps in the app.