Skip to Content
LearnProtocol ArchitectureStrategiesDeposits and Redemptions

Strategy: Deposits and Redemptions


Multi-asset deposits

The strategy accepts multiple ERC-20 tokens for deposits. Each listed token has its own pricing converter (IAssetConverter) and optional deposit fee. There is no ERC-4626 deposit(assets, receiver); the token address is always an explicit parameter.

Deposit asset management

FunctionRoleDescription
addDepositAsset(token, converter, feeBps)DEFAULT_ADMIN_ROLELists a new deposit token with a converter and initial fee (feeBps < 10_000)
removeDepositAsset(token)DEFAULT_ADMIN_ROLERemoves token from the allowlist; reverts StrategyDeposit_AssetNotListed if not listed
setAssetConverter(token, converter)DEFAULT_ADMIN_ROLEReplaces the pricing converter for a listed token
setDepositFeeBps(token, bps)MANAGER_ROLEAdjusts the deposit fee for a listed token (bps < 10_000)
isDepositAsset(token)-Returns whether a token is currently listed
depositFee(token)-Returns configured deposit fee in bps
converterOf(token)-Returns the IAssetConverter for a token

Deposit and mint entrypoints

All deposit/mint paths require deposit not paused and are nonReentrant. Blocklist checks run on both sender and receiver.

// Deposit exact token amount, receive shares uint256 shares = strategy.deposit(token, assets, receiver); // Deposit exact token amount with slippage guard (reverts if shares < minSharesOut) uint256 shares = strategy.deposit(token, assets, receiver, minSharesOut); // Mint exact share amount, pay required tokens uint256 assets = strategy.mint(token, shares, receiver); // Mint exact share amount with cost cap (reverts if assets > maxAssetsIn) uint256 assets = strategy.mint(token, shares, receiver, maxAssetsIn);

Deposit flow (step by step)

  1. Blocklist check on msg.sender and receiver.
  2. Management fee auto-accrual: mints any pending management fee shares to treasury before supply changes. See Fee System.
  3. safeTransferFrom(sender, strategy, assets): pulls the full gross amount from the sender.
  4. Fee split: feeAssets = assets × depositFeeBps / 10_000 (ceiling). netAssets = assets - feeAssets.
    • If feeAssets > 0, transfers fee tokens to feeTreasury. Reverts StrategyDeposit_FeeTreasuryNotSet if treasury is not configured.
  5. Convert: IAssetConverter(converter).toBaseUnits(netAssets, Floor) translates net deposit tokens into strategy base units.
  6. Share mint: _convertToShares(baseUnits, Floor) → mints shares to receiver. Emits Deposit(sender, receiver, token, netAssets, shares, feeAssets).
  7. Default shard routing: if defaultShard != address(0), transfers netAssets of the deposit token to the default shard. Emits DepositRoutedToDefaultShard. This is the primary mechanism for deploying freshly deposited capital.

Preview and conversion functions

FunctionReturnsNotes
previewDeposit(token, assets)Shares you receiveAfter fee deduction and converter; returns 0 if token unlisted
previewMint(token, shares)Gross assets requiredIncludes fee gross-up; ceiling rounding
convertToShares(token, assets)Shares for given assetsNo fee applied; for informational use
convertToAssets(token, shares)Assets for given sharesNo fee applied; ceiling rounding
maxDeposit(token, receiver)type(uint256).max or 00 when paused or token not listed
maxMint(token, receiver)type(uint256).max or 00 when paused or token not listed

Asset converters

Every listed deposit token requires an IAssetConverter to translate between token units and strategy base units (the underlying asset’s units).

interface IAssetConverter { function toBaseUnits(uint256 amount, Math.Rounding rounding) external view returns (uint256); function fromBaseUnits(uint256 amount, Math.Rounding rounding) external view returns (uint256); }

Four concrete implementations are provided:

ConverterDescription
DirectConverter1:1 identity. toBaseUnits and fromBaseUnits return the input unchanged. Use when the deposit token is the same denomination as the strategy’s base asset.
ChainlinkConverterPrices via a single Chainlink AggregatorV3 feed with configurable staleness tolerance. Supports an inverted mode for quote-currency feeds.
ChainlinkCompositeConverterDerives a ratio from two Chainlink feeds (e.g. BTC/ETH and ETH/USD to get BTC/USD). Handles decimal scaling between feeds.
StakedLBTCConverterUses IOracle.ratio() (1e18-scaled) from the LBTC staking oracle to price staked LBTC against LBTC.

Accounting

Deposit shares and redemption assets are calculated from the strategy’s externally posted pricePerShare. See Accounting for the full NAV, conversion, and rate-limiter model.


Async redemptions

Redemptions are not immediate. Users burn shares upfront via requestRedeem; an operator with PAY_REDEMPTIONS_ROLE fulfils pending requests in a later transaction once the strategy has sufficient balance for the redemption asset available.

Lifecycle

requestRedeem

uint256 requestId = strategy.requestRedeem(shares, owner);
  • Requires redeem not paused; nonReentrant.
  • If msg.sender != owner, spends ERC-20 allowance from owner to msg.sender.
  • Blocklist check on owner.
  • Management fee auto-accrual before share burn.
  • Redeem fee (if configured and feeTreasury is set): feeShares = shares × redeemFeeBps / 10_000 (ceiling). Fee shares are transferred from owner to feeTreasury. If feeShares >= shares, reverts AsyncRedemption_FeeSharesGreaterThanShares.
  • netShares = shares - feeShares are burned from owner.
  • pendingAssets = netShares × pricePerShare / 10^decimals() (floor) is recorded.
  • A new requestId (monotonic from 1) is assigned and the request is stored.
  • Emits RedeemRequested(requestId, owner, caller, pendingAssets, netShares, feeShares).

Pending request queries

FunctionReturns
pendingRedeemRequest(requestId)RedeemRequest{pendingShares, pendingAssets, owner} for an open request
pendingAssetsOf(owner)Total asset() owed across all open requests for an owner
totalPending()Aggregate (totalPendingShares, totalPendingAssets) across all open requests
redeemFee()Current redeemFeeBps

fulfillRedeemRequests

strategy.fulfillRedeemRequests([requestId1, requestId2, ...]);
  • Requires PAY_REDEMPTIONS_ROLE; nonReentrant.
  • Not blocked by deposit pause, redeem pause, or ERC-20 transfer pause: payout always proceeds once queued.
  • For each requestId:
    • Reverts AsyncRedemption_NothingToFulfill(requestId) if pendingAssets == 0.
    • Subtracts from global totalPendingShares and totalPendingAssets.
    • Deletes the request record.
    • safeTransfer(asset(), owner, pendingAssets) transfers directly to the request owner. No separate user claim step required.
    • Emits Redeem(operator, owner, assets, shares).
  • Emits RedeemRequestsFulfilled(requestIds) after the batch.

Important: The strategy’s asset() balance must be sufficient at the time safeTransfer is called. If shards hold the assets off-chain, they must call pushToStrategy(asset, amount) before the operator calls fulfillRedeemRequests. The ERC-20 transfer will revert if the balance is insufficient.

Setting the redeem fee

strategy.setRedeemFeeBps(bps); // DEFAULT_ADMIN_ROLE; bps < 10_000

A fee of 0 bps (or an unset treasury) means no shares are deducted.

Last updated on