Reserve Index DTFs/Rebalancing

4.0.0

Rebalancing in Release 4.0.0

Release 4.0.0 is currently in development.

In order to rebalance a DTF, the REBALANCE_MANAGER must first start a rebalance. This will set the new basket, per-token limits, and price ranges. The REBALANCE_MANAGER will also set the restricted window during which only the AUCTION_LAUNCHER can open auctions, and the total time-to-live for the rebalance.

Once a rebalance auction is opened by the AUCTION_LAUNCHER, every surplus token in the auction will be sold down to its target basket ratio, and every deficit token will be bought up to its target basket ratio. An auction can include every surplus and deficit token in the basket, or a subset of them. Bidders can bid on any surplus:deficit pair in the auction until they reach their respective target ratios.

Rebalance Lifecycle

  1. Rebalance Initiation: The REBALANCE_MANAGER starts a new rebalance, specifying new basket tokens, target ratios (limits), and price ranges for each asset.
  2. Auction Launcher Window: For a restricted period, only the AUCTION_LAUNCHER can open auctions, with the ability to fine-tune parameters within governance-set bounds. The AUCTION_LAUNCHER can:
    • select a sell limit within the approved sell limit range
    • select a buy limit within the approved buy limit range
    • raise starting price by up to 100x
    • raise ending price arbitrarily (can cause auction not to clear, same end result as closing auction)
  3. Permissionless Auctions: After the restricted window, anyone can open auctions using the pre-approved parameters.
  4. Bidding: Anyone can bid in open auctions, exchanging tokens at a price determined by an exponential decay curve. Every successful bid will move the DTF closer to the target basket ratio.
  5. Auction Close: Auctions end when their time elapses or their limits are reached. The rebalance technically ends when the rebalance period expires.

Key Concepts and Structs

  • BasketRange: { spot, low, high } — Target, minimum, and maximum ratios of a token to Folio shares (D27 precision).
  • Prices: { low, high } — Price range for each asset (D27 precision).
  • RebalanceDetails: { inRebalance, limits, prices } — Per-token rebalance state.
  • Auction: { rebalanceNonce, sellToken, buyToken, sellLimit, buyLimit, startPrice, endPrice, startTime, endTime } — State of a single auction.

Auction Pricing

For each token supplied to the rebalance, the REBALANCE_MANAGER provides a low and high price estimate. These should be set such that in the vast majority (99.9%+) of scenarios, the asset's price on secondary markets lies within the provided range. The maximum allowable price range for a token is 1e2: high / low must be <= 1e2.

If the price of an asset rises above its high price, this can result in a loss of value for Folio holders due to the auction price curve on a token pair starting at too-low-a-price.

When an auction is started, the low and high prices for both assets are used to calculate a startPrice and endPrice for the auction.

There are 2 ways to price assets, depending on the risk tolerance of the REBALANCE_MANAGER:

  1. Priced
  • The REBALANCE_MANAGER provides a list of NONZERO prices for each token. No token price can be 0.
  • The AUCTION_LAUNCHER can always choose to raise the starting price up to 100x from the natural starting price, or raise the ending price arbitrarily (up to the starting price). They cannot lower either price.
  1. Unpriced
  • The REBALANCE_MANAGER provides ALL 0 prices for each token. This allows the AUCTION_LAUNCHER to set prices freely and prevents auctions from being opened permissionlessly.

Overall the price range for any dutch auction (startPrice / endPrice) must be less than 1e6 to prevent precision issues.

Price Curve

Note: The first block may not have a price of exactly startPrice if it does not occur on the start timestamp. Similarly, the endPrice may not be exactly endPrice in the final block if it does not occur on the end timestamp.

All auctions use an exponential decay price curve from startPrice to endPrice over the auction duration. The price at any time t is:

P(t) = startPrice * exp(-k * t)

Where k is calculated so that P(auction.endTime) = endPrice.

Lot Sizing

Auctions are sized by the difference between current balances and the basket ratios provided by the limits.

The auction sellAmount represents the single largest quantity of sell token that can be transacted without violating the limits of either tokens in the pair.

In general it is possible for the sellAmount to either increase or decrease over time, depending on whether the surplus of the sell token or deficit of the buy token is the limiting factor.

  1. If the surplus of the sell token is the limiting factor, the sellAmount will increase over time.
  2. If the deficit of the buy token is the limiting factor, the sellAmount will decrease over time.

Auction Participation

Anyone can bid in any auction up to and including the sellAmount size, as long as the price exchange rate is met.

/// @return sellAmount {sellTok} The amount of sell token on sale in the auction at a given timestamp
/// @return bidAmount {buyTok} The amount of buy tokens required to bid for the full sell amount
/// @return price D27{buyTok/sellTok} The price at the given timestamp as an 27-decimal fixed point
function getBid(
   uint256 auctionId,
   uint256 timestamp,
   uint256 maxSellAmount
) external view returns (uint256 sellAmount, uint256 bidAmount, uint256 price);

Security and Permissions

  • Only the REBALANCE_MANAGER can start a rebalance.
  • Only the AUCTION_LAUNCHER can open auctions during the restricted window.
  • After the window, anyone can open auctions (if priced mode).
  • Anyone can bid in open auctions.
  • Any of the REBALANCE_MANAGER, AUCTION_LAUNCHER, or ADMIN roles can close an auction.
  • Any of the REBALANCE_MANAGER, AUCTION_LAUNCHER, or ADMIN roles can end a rebalance.

Function Reference

1. getRebalance()

Signature:

function getRebalance() external view returns (
    address[] memory tokens,
    BasketRange[] memory limits,
    Prices[] memory prices,
    bool[] memory inRebalance
)

Description: Returns the current rebalance state for all tokens in the basket, including their target limits, price ranges, and whether they are actively being rebalanced.


2. startRebalance()

Signature:

function startRebalance(
    address[] calldata newTokens,
    BasketRange[] calldata newLimits,
    Prices[] calldata newPrices,
    uint256 auctionLauncherWindow,
    uint256 ttl
) external onlyRole(REBALANCE_MANAGER)

Description: Begins a new rebalance, setting the target basket, per-token limits, and price ranges. Also sets the restricted window (auctionLauncherWindow) during which only the AUCTION_LAUNCHER can open auctions, and the total time-to-live (ttl) for the rebalance.

  • newTokens: List of tokens in the new basket.
  • newLimits: Per-token target, min, and max ratios.
  • newPrices: Per-token price ranges (all zero to allow unpriced auctions).
  • auctionLauncherWindow: Duration (in seconds) of the restricted period.
  • ttl: Total duration (in seconds) of the rebalance.

Notes:

  • All arrays must be the same length and non-empty.
  • If any price is zero, all must be zero (unpriced mode).
  • Emits RebalanceStarted event.

3. openAuction()

Signature:

function openAuction(
    IERC20 sellToken,
    IERC20 buyToken,
    uint256 sellLimit,
    uint256 buyLimit,
    uint256 startPrice,
    uint256 endPrice
) external onlyRole(AUCTION_LAUNCHER)

Description: Opens a new auction between two tokens during the restricted window. The AUCTION_LAUNCHER can select limits and prices within the bounds set by the current rebalance.

  • sellToken: Token to sell.
  • buyToken: Token to buy.
  • sellLimit: Minimum allowed ratio of sell token to shares (must be within approved range).
  • buyLimit: Maximum allowed ratio of buy token to shares (must be within approved range).
  • startPrice: Initial auction price (can be raised up to 100x from governance-set price).
  • endPrice: Final auction price (can be raised arbitrarily, but not lowered).

Notes:

  • Only one live auction per token pair.
  • Checks that sell token is in surplus and buy token is in deficit.
  • Emits AuctionOpened event.

4. openAuctionUnrestricted()

Signature:

function openAuctionUnrestricted(
    IERC20 sellToken,
    IERC20 buyToken
) external

Description: Opens an auction permissionlessly after the restricted window has passed. Uses the spot limits and full price range from the rebalance configuration.

  • sellToken: Token to sell.
  • buyToken: Token to buy.

Notes:

  • Can only be called after rebalance.restrictedUntil.
  • All token prices must be nonzero (priced mode only).
  • Emits AuctionOpened event.

5. getBid()

Signature:

function getBid(
    uint256 auctionId,
    uint256 timestamp,
    uint256 maxSellAmount
) external view returns (
    uint256 sellAmount,
    uint256 bidAmount,
    uint256 price
)

Description: Returns the current auction parameters for a given auction at a specific timestamp (or now if timestamp == 0).

  • auctionId: The auction to query.
  • timestamp: The time to evaluate the auction (0 for current block time).
  • maxSellAmount: The maximum amount of sell token the bidder is willing to offer.

Returns:

  • sellAmount: Amount of sell token available at this time.
  • bidAmount: Amount of buy token required for the full sellAmount.
  • price: Current auction price (D27 precision).

6. bid()

Signature:

function bid(
    uint256 auctionId,
    uint256 sellAmount,
    uint256 maxBuyAmount,
    bool withCallback,
    bytes calldata data
) external returns (uint256 boughtAmt)

Description: Places a bid in an ongoing auction. The bidder receives sellAmount of the sell token in exchange for up to maxBuyAmount of the buy token, at the current auction price. Optionally supports a callback for advanced use cases.

  • auctionId: The auction to bid in.
  • sellAmount: Amount of sell token to buy.
  • maxBuyAmount: Maximum buy token the bidder is willing to pay.
  • withCallback: If true, calls IBidderCallee interface on the bidder for custom logic.
  • data: Arbitrary data for the callback.

Returns:

  • boughtAmt: Amount of buy token actually paid.

Notes:

  • Checks that the auction is ongoing and that the bid does not exceed available amounts.
  • Emits AuctionBid event.

See Also