“We found that for the 17 pools we analyzed, covering 43% of TVL and chosen by size, composite tokens and data availability, total fees earned since inception until the cut-off date was $199.3m. We also found that the total IL suffered by LPs during this period was USD 260.1m, meaning that in aggregate those LPs would have been better off by USD 60.8m had they simply HODLd.” Stefan Loesch, Nate Hindman, Mark B Richardson, Nicholas Welch: “Impermanent Loss in Uniswap v3”, 2021; arXiv:2111.09192.
“Thus, providing liquidity has become a game reserved for sophisticated players with the introduction of Uniswap V3, where retail traders do not stand a chance.” Lioba Heimbach, Eric Schertenleib, Roger Wattenhofer: “Risks and Returns of Uniswap V3 Liquidity Providers”, 2022; arXiv:2205.08904. DOI: 10.1145/3558535.3559772.We propose an alternative solution that eliminates LVR and guarantees general intent level execution as a lower bound (e.g. CoW Swap). This document introduces the BasketManager contract, which handles protocol deposits/withdrawals/LP tokens, custodying user assets, and managing the rebalance lifecycle. Storing user assets in one contract provides two major advantages from economies of scale: optimized/cheaper rebalancing (at the protocol level vs. for each basket) and increased value capture from matching trades internally.
Details (Click to expand)
WeightStrategy.setTargetWeights(uint256 bitFlag, uint256[] weights)
BasketManager.proposeRebalance()
.
Details (Click to expand)
BasketManager.proposeRebalance()
BasketManager.proposeTokenSwap(Trade[] internalTrades, Trade[] externalTrades)
to submit all necessary swap amounts and the associated basket and target weight information. BasketManager performs minimal on-chain validation to ensure the inputs align with each basket’s target weights. If more advanced validation is needed, the role can be assigned to a separate contract using Axiom, transforming the process from a variable to a fixed cost.Details (Click to expand)
proposeTokenSwap(Trade[] internalTrades, Trade[] externalTrades)
.BasketManager.executeTokenSwap(externalTrades, bytes data)
where the data is optional information required for the current token swap adapter.Details (Click to expand)
executeTokenSwap(externalTrades, bytes data)
where the data is optional information used by the current token swap adapter.
externalTrades.minAmount
is checked against the oracle value.data
is not used.externalTrades.minAmount
is used to create a cowswap order.Details (Click to expand)
BasketManager.completeRebalance
, executing any checks and validations needed to complete the rebalance and advance the epoch.
Details (Click to expand)
REBALANCE_PROPOSED
and the token swap proposal process is restarted, allowing for additional iterations to converge closer to the target weights.completeRebalance()
.completeRebalance
to exit the rebalance process.
proposeRebalance
proposeTokenSwap
.completeRebalance
.proposeTokenSwap
completeRebalance
.executeTokenSwap
completeRebalance
.completeRebalance
proposeRebalance
was just called.keccak256(uint256 selectionBitFlag, address weightStrategy)
as the salt, preventing duplicate deployments of the same config. Creation of new basket is permissioned.
requestDeposit
deposit
or mint
functions.proRataRedeem
requestRedeem
, which transfers their basket token shares and requests these shares to be burned in exchange for the base asset.claimFallbackShares
to retrieve their previously requested basket token shares.requestRedeem
can then call redeem
or withdraw
on the respective basket token contract to claim the underlying assets.BasketManager
contract allows the bitFlag of an existing basket to be updated through the updateBitFlag
function. This function is restricted to only be callable by the _TIMELOCK_ROLE
.
To update a basket’s bitFlag:
updateBitFlag(address basket, uint256 bitFlag)
with the following parameters:
basket
: The address of the basket token to update.bitFlag
: The new bitFlag value. It must be a superset of the current bitFlag.basket
must exist and be registered in basketTokenToIndexPlusOne
.bitFlag
must be different from the current one.bitFlag
must be a superset of the current bitFlag, meaning it includes all the bits set in the current bitFlag.bitFlag
.basketId
(hash of bitFlag
and strategy
) must not already exist to prevent duplicate baskets.basketId
mapping is removed and the new one is added to basketIdToAddress
.basketAssets
for the basket are updated based on the new bitFlag
.BasketBitFlagUpdated
event is emitted with the old and new bitFlag values and IDs.bitFlag
is updated in the BasketToken
contract.proRataRedeem
method.totalSupply
is the current total supply of the basket token, excluding shares held by the fee collector or pending redemption.managementFeeBps
is the management fee in BPS for the specific basket.timeSinceLastHarvest
is the time elapsed since the last fee harvest.FeeCollector
contract then divides the fee between the protocol treasury and the strategy sponsor according to the configured split percentage for each basket.
Details (Click to expand)
FeeCollector
contract will only use the proRataRedeem
function for synchronous claiming of fees. This ensures that any tokens sent to the FeeCollector
by other users will be ignored in the fee calculation, as the proRataRedeem
function burns the shares immediately and transfers the underlying assets to the fee recipients. The FeeCollector
will not use the asynchronous redemption process, which could potentially lead to claimable fallback shares being included in the total supply used for fee calculation.sellAmount
is the amount of the sell token being traded.buyAmount
is the amount of the buy token being received (before fee deduction).swapFeeBps
is the global swap fee in BPS.BasketManager
contract for each token and can be claimed by the protocol admin, which transfers the collected fees to the protocol treasury. The swap fee is not further divided and goes entirely to the treasury.
Fee Type | Maximum Value (BPS) |
---|---|
Management Fee | 3,000 (30%) |
Swap Fee | 500 (5%) |
setManagementFee
function in the BasketManager
contract. The global swap fee can be set by the admin using the setSwapFee
function in the same contract. Both functions enforce the respective maximum fee limits and can only be called by the protocol admin role.
convertToAssets
and convertToShares
. This adapter is intended for use when no direct oracle exists for the vault token.AnchoredOracle
implementation includes the following behaviors:
1e18
) to preserve precision.ScalingOverflow
revert on extreme input values.maxDivergence
parameter is bounded between 0.1% and 50%; the constructor will revert if values outside this range are provided.IPriceOracle
) interface.OracleRouter
contract, which is referenced by the BasketManager
. Deployment scripts are responsible for the on-chain registration of each adapter. If an AnchoredOracle
instance is used, its corresponding (primary, anchor) oracle pair must also be registered.AnchoredOracle
. This smart contract implements the IPriceOracle
interface by retrieving quotes from a primary oracle and validating them against an anchor oracle. The primary quote must be within a specified divergence range from the anchor quote before the price is returned. This mechanism provides an additional layer of security against stale or manipulated oracle prices.
For assets that cannot be priced directly in USD, a CrossAdapter
is utilized with the base asset.
requestDeposit
or requestRedeem
, the system ensures that the controller does not have any pending or claimable deposits or redeems from the controller’s lastDepositRequestId
.
This behavior allows for a potential griefing attack: an attacker can call requestDeposit
or requestRedeem
with a minimal dust amount and specify the target controller address. As a result, the target controller would then be unable to make legitimate requestDeposit
or requestRedeem
requests until they first claim the pending request.
Recommendation for Integrators: When integrating the BasketToken
into other contracts, always check for any pending or claimable tokens before requesting a deposit or redeem. This ensures that any pending deposits or redeems are resolved, preventing such griefing attacks.
BasketManager
contract.