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

Strange behaviour fees in range #573

Closed
Whynot63 opened this issue Aug 17, 2022 · 5 comments
Closed

Strange behaviour fees in range #573

Whynot63 opened this issue Aug 17, 2022 · 5 comments

Comments

@Whynot63
Copy link

Whynot63 commented Aug 17, 2022

Preamble

According uniswap whitepaper v3, we could calculate fees per share in the price range between two ticks with formulas 6.17 - 6.19:

$$\begin{equation} f_a(i)=\begin{cases} f_g - f_o(i), & i_c \geq i\\ f_o(i), & i_c < i \end{cases} \end{equation} $$

$$\begin{equation} f_b(i)=\begin{cases} f_o(i), & i_c \geq i \\ f_g - f_o(i), & i_c < i \end{cases} \end{equation} $$

$$f_r = f_g - f_b(i_l) - f_a(i_u)$$

$f_r$ - fees for given range
$f_g$ - fees earned in all pool (variable feeGrowthGlobal0X128 / feeGrowthGlobal1X128 in pool smart contract)
$f_b(i)$ - fees earned below tick $i$
$f_a(i)$ - fees earned above tick $I$

$i_l$ - lower tick for given range
$i_u$ - upper tick for given range
$f_o$ - feeGrowthOutside0X128 / feeGrowthOutside1X128 in tick state

Here is simplified version of original code for calculation accumulated fees in range:

function getFeeGrowthInside(
    mapping(int24 => Tick.Info) storage self,
    int24 tickLower,
    int24 tickUpper,
    int24 tickCurrent,
    uint256 feeGrowthGlobalX128,
) internal view returns (uint256 feeGrowthInsideX128) {
    Info storage lower = self[tickLower];
    Info storage upper = self[tickUpper];

    uint256 feeGrowthBelowX128;
    if (tickCurrent >= tickLower) {
        feeGrowthBelowX128 = lower.feeGrowthOutsideX128;
    } else {
        feeGrowthBelowX128 = feeGrowthGlobalX128 - lower.feeGrowthOutsideX128;
    }

    uint256 feeGrowthAboveX128;
    if (tickCurrent < tickUpper) {
        feeGrowthAboveX128 = upper.feeGrowthOutsideX128;
    } else {
        feeGrowthAboveX128 = feeGrowthGlobalX128 - upper.feeGrowthOutsideX128;
    }

    feeGrowthInsideX128 = feeGrowthGlobalX128 - feeGrowthBelowX128 - feeGrowthAboveX128;
}

Problem

Ok, lets try to calculate fees for pool USDC/USDT - 0.01% on polygon.

Block: 31995913
Tick lower: -6
Tick upper: -2

Calculations for token0:

Python implementation of fees calculation

It uses my wrapper for web3 - web3 premium, but nothing rocket science here is

import datetime

from web3_premium.chains import polygon
from web3_premium.contract import Contract
from web3_premium.utils import get_block_by_datetime


BLOCK = int(
    get_block_by_datetime(polygon.explorer, datetime.datetime(2022, 8, 17, 16, 0))
)
usdt_usdc_01_pool = Contract("0xdac8a8e6dbf8c690ec6815e0ff03491b2770255d", polygon)


def tick_growth_outside(tick):
    # get feeGrowthOutside0X128 from tick info for given tick and block
    return usdt_usdc_01_pool.ticks(tick, block=BLOCK)[2]


def get_fee_growth_inside(tick_lower, tick_upper, tick_current, fee_growth_global):
    print(f"$i_l$ = {tick_lower}")
    print(f"$i_u$ = {tick_upper}")
    print(f"$i_c$ = {tick_upper}")
    print(f"$f_g$ = {fee_growth_global}")
    print(f"$f_o(i_l)$ = {tick_growth_outside(tick_lower)}")
    print(f"$f_o(i_u)$ = {tick_growth_outside(tick_upper)}")

    if tick_current >= tick_lower:
        fee_growth_below = tick_growth_outside(tick_lower)
        print("$i_c \geq i_l$")
        print("$f_b(i_l) = f_o(i_l)$")
    else:
        fee_growth_below = fee_growth_global - tick_growth_outside(tick_lower)
        print("$i_c < i_l$")
        print("$f_b(i_l) = f_g - f_o(i_l)$")
    print(f"$f_b(i_l)$ = ${fee_growth_below}")

    if tick_current < tick_upper:
        fee_growth_above = tick_growth_outside(tick_upper)
        print("$i_c < i_u$")
        print("$f_a(i_u) = f_o(i_u)$")
    else:
        fee_growth_above = fee_growth_global - tick_growth_outside(tick_upper)
        print("$i_c \geq i_u$")
        print("$f_a(i_u) = f_g - f_o(i_u)$")
    print(f"$f_a(i_u)$ = {fee_growth_above}")

    fee_growth_inside = fee_growth_global - fee_growth_below - fee_growth_above
    print(f"$f_r$ = {fee_growth_inside}")
    return fee_growth_inside


current_tick = usdt_usdc_01_pool.slot0(block=BLOCK)[1]
fee_growth_global = usdt_usdc_01_pool.feeGrowthGlobal0X128(block=BLOCK)
get_fee_growth_inside(-6, -2, current_tick, fee_growth_global)

$i_l$ = -6
$i_u$ = -2
$i_c$ = -2
$f_g$ = 52059057655713157425077904244139302
$f_o(i_l)$ = 48464060636512038136115728978196309
$f_o(i_u)$ = 2001227920504599228768954726169803

$i_c \geq i_l$
$f_b(i_l) = f_o(i_l)$
$f_b(i_l)$ = 48464060636512038136115728978196309

$i_c \geq i_u$
$f_a(i_u) = f_g - f_o(i_u)$
$f_a(i_u)$ = 50057829735208558196308949517969499

$f_r$ = -46462832716007438907346774252026506

So, the problem is that fees could not be negative.

Question

So, I calculate fees with formula from whitepaper and get negative fees.

It is the bug or error in my calculations?

@aadams
Copy link

aadams commented Aug 17, 2022

The contract implicitly relies on int256 underflow/overflow. Python won't do this for you.

Make sure to mimic the SDK found here: https://discord.com/channels/597638925346930701/607978109089611786/1009532714862522450

@Whynot63
Copy link
Author

Ok, another details:

I implemented formula range fees calculation on solidity and get another strange results:

feeGrowthInside0X128 - 115792089237316195423570985008687907853269938202807848032018676661138877613430
feeGrowthInside1X128 - 115792089237316195423570985008687907853269831232744442064129902573409404585318

Why it is strange? Because fees inside range much greater than all fees in pool.

Here is code (copy paste of getFeeGrowthInside + wrapper function):

pragma solidity >=0.5.0;
pragma abicoder v2;

interface IUniswapV3PoolState {
    function feeGrowthGlobal0X128() external view returns (uint256);
    function feeGrowthGlobal1X128() external view returns (uint256);
    function ticks(int24 tick)
        external
        view
        returns (TestTick.Info memory);

    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );

}

contract TestTick {
    IUniswapV3PoolState pool = IUniswapV3PoolState(0xDaC8A8E6DBf8c690ec6815e0fF03491B2770255D);

    event Log(uint256, uint256);

    struct Info {
        uint128 liquidityGross;
        int128 liquidityNet;
        uint256 feeGrowthOutside0X128;
        uint256 feeGrowthOutside1X128;
        int56 tickCumulativeOutside;
        uint160 secondsPerLiquidityOutsideX128;
        uint32 secondsOutside;
        bool initialized;
    }

    function getFeeGrowthInside(
        int24 tickLower,
        int24 tickUpper,
        int24 tickCurrent,
        uint256 feeGrowthGlobal0X128,
        uint256 feeGrowthGlobal1X128
    ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) {
        Info memory lower = pool.ticks(tickLower);
        Info memory upper = pool.ticks(tickUpper);

        uint256 feeGrowthBelow0X128;
        uint256 feeGrowthBelow1X128;
        if (tickCurrent >= tickLower) {
            feeGrowthBelow0X128 = lower.feeGrowthOutside0X128;
            feeGrowthBelow1X128 = lower.feeGrowthOutside1X128;
        } else {
            feeGrowthBelow0X128 = feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128;
            feeGrowthBelow1X128 = feeGrowthGlobal1X128 - lower.feeGrowthOutside1X128;
        }

        // calculate fee growth above
        uint256 feeGrowthAbove0X128;
        uint256 feeGrowthAbove1X128;
        if (tickCurrent < tickUpper) {
            feeGrowthAbove0X128 = upper.feeGrowthOutside0X128;
            feeGrowthAbove1X128 = upper.feeGrowthOutside1X128;
        } else {
            feeGrowthAbove0X128 = feeGrowthGlobal0X128 - upper.feeGrowthOutside0X128;
            feeGrowthAbove1X128 = feeGrowthGlobal1X128 - upper.feeGrowthOutside1X128;
        }

        feeGrowthInside0X128 = feeGrowthGlobal0X128 - feeGrowthBelow0X128 - feeGrowthAbove0X128;
        feeGrowthInside1X128 = feeGrowthGlobal1X128 - feeGrowthBelow1X128 - feeGrowthAbove1X128;
    }

    function run() external {
        int24 tickLower = -6;
        int24 tickUpper = -2;
        (, int24 tickCurrent, , , , ,) = pool.slot0();

        (uint256 feeGrowthInside0X128, uint256 feeGrowthAbove1X128) = getFeeGrowthInside(
            tickLower,
            tickUpper,
            tickCurrent,
            pool.feeGrowthGlobal0X128(),
            pool.feeGrowthGlobal1X128()
        );

        emit Log(feeGrowthInside0X128, feeGrowthAbove1X128);
    }

}

@Whynot63
Copy link
Author

Also reproduced this bug with uniswap sdk:

import JSBI from 'jsbi';
import { TickLibrary } from '@uniswap/v3-sdk';

console.log(
    TickLibrary.getFeeGrowthInside(
        {
            feeGrowthOutside0X128: JSBI.BigInt("48464060636512038136115728978196309"),
            feeGrowthOutside1X128: JSBI.BigInt("155431297820226069518189999139402599")
        },
        {
            feeGrowthOutside0X128: JSBI.BigInt("2001227920504599228768954726169803"),
            feeGrowthOutside1X128: JSBI.BigInt("1998401698250741836755495414347981")
        },
        -6,
        -2,
        -2,
        JSBI.BigInt("52059057655713157425077904244139302"),
        JSBI.BigInt("158999387230287332017750457411857410")
    ).toString()
)

Output

115792089237316195423570985008687907853269938202807848032018676661138877613430,115792089237316195423570985008687907853269831232744442064129902573409404585318

exact as solidity code

@Whynot63
Copy link
Author

Ok, for history - here is a rigth formula.

@gphil
Copy link

gphil commented Jun 6, 2024

@Whynot63 I am running into the exact same issue as you where the underflowed result is way too big. In your next comment you seemed to say "here is a right formula" but no formula was included, could you possibly share this formula?

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

3 participants