Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autonomous Unitary Value Calculation #35

Open
gabririgo opened this issue Mar 3, 2024 · 0 comments
Open

Autonomous Unitary Value Calculation #35

gabririgo opened this issue Mar 3, 2024 · 0 comments

Comments

@gabririgo
Copy link
Contributor

gabririgo commented Mar 3, 2024

Summary

The process of calculating and updating a smart pool's unitary value (used for mint/burn amounts calculations) is now decentralized from the core to the pool operator. The introduction of advanced Geomean Oracles in Uniswap V4 opens the way for implementing a completely autonomous unitary value calculation.

Motivation

The process of updating a pool's unitary value is currently not automated (even though it can be at the smart pool operator's level), not trustless, and not in real-time (as the update requires storing the new value on-chain). Current existing solutions are centralized (i.e., ChainLink and similar), subject to oracle manipulation, and not in real-time (oracles are updated only periodically, i.e., once every 1 hour). The resulting unitary value would be only an estimate and would require further safety measures at the smart pool level to prevent abuse. Furthermore, centralized on-chain Oracle providers do not offer a censorship-resistant system, as they arbitrarily decide what tokens they do support and regularly discontinue support for some tokens.
Uniswap V4, on the other hand, provides a standard for on-chain settlement and a standard for on-chain Oracles. Their Geomean Oracles can be used to estimate unitary value in a precise and accurate way.

Specification

A new extension EUnitaryValue.sol is added to the protocol, which makes a query to the Uniswap V4 Oracle liquidity pool. To find the correct Oracle, the extension will store the Uniswap V4 Oracle hook address, which is the same on all networks, in its bytecode as an immutable. It will then compute the correct token to ETH price by calling function observe(PoolKey calldata key, uint32[] calldata secondsAgos) in two distinct points in time, and then dividing the difference of tickCumulatives by the difference of secondsPerLiquidityCumulativeX128s.
PoolKey is defined as follows:

/// @notice Returns the key for identifying a pool
struct PoolKey {
    /// @notice The lower currency of the pool, sorted numerically
    Currency currency0;
    /// @notice The higher currency of the pool, sorted numerically
    Currency currency1;
    /// @notice The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees.
    uint24 fee;
    /// @notice Ticks that involve positions must be a multiple of tick spacing
    int24 tickSpacing;
    /// @notice The hooks of the pool
    IHooks hooks;
}

Because in Uniswap V4 only one oracle pool is allowed, the inputs are:

PoolKey (
    currency0: address(0),
    currency1: tokenAddress,
    fee: 0,
    tickSpacing: type(int16).max,
    hooks: Oracle

owned token balances are multiplied by price and summed, then converted to the base token by querying the base token to ETH price.

Pool positions are evaluated from the positions contract, where we either retrieve the aggregate ETH value of positions or extract the individual token components of the positions and sum (subtract in case of debt position) to the pool's token balances.
Notice: when a token balance is sold, the token should be removed from the mapping of tracked tokens. However, if a position owns the same token, we must assert that the token balance can be evaluated when the position is closed. Furthermore, services like DefiLlama and Zapper use the list of owned tokens to evaluate tokens in positions. Technically, it would be ideal to decompose the positions in the underlying tokens and remove a token from the mapping when its balance is null. We also must check what happens in the unrealistic edge scenario where the balance is nill for example due to a positive balance plus an equivalent debt position. In this context, if the token is removed from the mapping, we must ensure that whenever the balance is not null anymore it is correctly tracked.
As a general rule, we should request that no more than 250 tokens and 250 positions be held at a time. While this is not a strict requirement, we want to ensure that the value calculation cost does not explode as a pool buys and sells tokens.

Alternatively, a new library is added to the protocol and inherited by the smart pool implementation. In this case, any further upgrades to the library code will require a pool implementation upgrade (i.e., governance approval + smart pool operator approval).
Ideally we'd want the feature to be implemented as a library to be part of the core instead of the extensions. This will lead to a bigger smart pool implementation contract, which is not huge anyway. While any upgrade to this will require both governance and pool operator approval, it is probably desirable that mint and burn function are not a moving component.

As a general rule, when implementing this feature, the default smart pool holding period in the implementation should be set to 1 day (currently 1 block) unless tests prove the current default is sufficient to prevent oracle manipulation attacks.

This RBIP requires adding a storage slot for owned tokens (plus one for owned positions), as in RBIP-28.

@gabririgo gabririgo changed the title feat: autonomous unitary value calculation feat(protocol): autonomous unitary value calculation Mar 6, 2024
@gabririgo gabririgo changed the title feat(protocol): autonomous unitary value calculation Autonomous Unitary Value Calculation Mar 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant