유니스왑 v3의 틱 관리

먼저 진드기의 저장 구조 살펴보기

struct Info {
        // 所有引用这个tick的position的流动性总和
        uint128 liquidityGross;
        //当tick被从左到右(从右到左)穿过时,流动性应该增加或减少的数值
        int128 liquidityNet;
     。。。
    }

다른 필드는 이 섹션과 관련이 없으며 지금은 건너뜁니다.

예를 들어 L = 500과 같이 두 포지션의 유동성이 동일하고 두 포지션이 동시에 하나의 틱을 가리키는 경우, 하나는 낮은 틱이고 다른 하나는 높은 틱이면 이에 대한 틱, 그것의 liquidityNet = + 500-500=0. 그리고 총 유동성=500+500=1000

가격 변동으로 인해 틱커런트가 포지션의 하한/상한 틱을 교차하게 되면 틱에 기록된 값에 따라 현재 가격에 해당하는 전체 유동성을 업데이트해야 합니다. 포지션의 유동성 가치를 ΔL이라고 가정하면 다음과 같은 네 가지 상황이 발생합니다.

왼쪽에서 오른쪽으로 낮은 틱을 교차하면서 가격이 올라갑니다: liquidityNet = liquidityNet + ΔL;

왼쪽에서 오른쪽으로 위쪽 틱을 교차하면서 가격이 올라갑니다: liquidityNet = liquidityNet - ΔL;

오른쪽에서 왼쪽으로 위쪽 틱을 교차하면서 가격이 하락합니다: liquidityNet = liquidityNet + ΔL;

오른쪽에서 왼쪽으로 낮은 틱을 교차하면서 가격이 하락합니다: liquidityNet = liquidityNet - ΔL;

틱 상태 저장

uniswap v3 버전은 제곱근 계산 비용을 줄이기 위해 가격을 직접 계산하고 Q64.94 정밀 고정 소수점 숫자를 사용하여 저장합니다. 먼저 이 Q64.94가 무엇을 의미하는지 설명하십시오.

Q(숫자 형식)는 이진 고정 소수점 숫자의 형식을 지정하는 방법입니다. 예를 들어, Q8.8로 표시되는 숫자 형식은 이 형식에서 고정 소수점 숫자의 정수 부분이 8비트이고 소수 부분이 8비트임을 의미합니다. Q64.94의 경우 값 범위는 0에서 .

_

따라서 해당 tickMin = -887272를 수행하기 위해 tickMax = 887272를 얻습니다.

이는 v3 버전의 스마트 컨트랙트가 887272*2 틱을 관리해야 적은 금액이 아닌 백만 수준에 도달한다는 것을 의미합니다.

사실 틱이 너무 많아서 대부분 초기화할 필요가 없습니다. 이 틱은 계약 코드의 두 번째 수준에서 관리됩니다.

mapping(int16 => uint256) public override tickBitmap;

    function position(int24 tick) private pure returns (int16 wordPos, uint8 bitPos) {
        wordPos = int16(tick >> 8);
        bitPos = uint8(tick % 256);
    }

887272 * 2 틱 계약은 int24로 표현되며, int16(틱 >> 8)은 상위 16비트를 나타내고 uint8(틱 % 256)은 하위 8비트를 나타내며 상위 16비트는 tickBitmap에서 키로 사용됩니다. uint256을 키 값으로 사용하시겠습니까? 나머지 하위 8비트는 2의 8승으로 총 256개의 숫자입니다. 즉, tickBitmap의 각 레코드는 256개의 틱 상태를 관리해야 하는데 가장 효율적인 방법은 비트맵을 사용하여 256개의 숫자를 256개의 이진수, 즉 uint256으로 변환하는 것이며 해당 비트는 현재 틱을 나타내는 1입니다. .견적.

가격으로 환산된 틱

우리는 공식이 매우 간단하다는 것을 알고 있지만 가격 P를 계산하려고 할 때 tick=887272와 같이 계산량이 매우 큽니다. 이 에너지 계약의 유니스왑 계산 코드는 다음과 같습니다.

function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
        require(absTick <= uint256(MAX_TICK), 'T');

        uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
        // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
        // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
    }

언뜻 보면 매우 혼란스럽습니다. 먼저 그 배후의 알고리즘을 이해하십시오.

우선 틱의 값 범위는 i가 [-887272,887272]에 속하는 것으로, 임의의 양의 정수는 다음과 같이 표현할 수 있다.

예를 들면 다음과 같습니다.

그러면 [1,887272] 범위의 숫자는 다음과 같이 표현됩니다.

위의 코드에서 0x1, 0x2, 0x4...까지 0x80000은 까지의 16진수 표현 입니다.

여전히 tick=25를 예로 들어 보겠습니다.

위 공식에서 도출

에서 ~ 까지 미리 계산해 놓았 다면 단계도 제한된 수의 곱셈으로 단순화할 수 있으므로 계산량을 잘 제어할 수 있으므로 다음 코드 줄은 이해하기 쉽습니다.

if (absTick & 0x2 != 0) 비율 = (비율 * 0xfff97272373d413259a46990580e213a) >> 128

ratio의 초기값은 1입니다. tick의 절대값을 분해하여 포함 시키면

실제로 코드 수준에서 위의 알고리즘을 기반으로 최적화 계층이 수행되었습니다.

i가 양수일 경우 계산 결과가 매우 커질 수 있고, 중간에 관련된 곱셈 연산으로 오버플로가 발생할 수 있으므로 실제 계산은 i가 음수일 때의 값입니다. 1 Decimal 보다 작은 값이므로 오버플로가 발생하지 않습니다. 즉, 위 코드의 매직 넘버는 , , ..... 이어야 합니다 . 각 계산은 128비트만큼 오른쪽으로 이동해야 하며 상위 128비트만 사용됩니다.

if (눈금 > 0) 비율 = type(uint256).max / 비율;

코드의 마지막 줄의 의미는 눈금이 양수이면 계산 결과를 도출해야 한다는 것입니다. 즉, Q128.128 형식으로 변환한 다음 1<<256을 type(uint256).max

sqrtPriceX96 =uint160((비율 >>32)+(비율 %(1<<32)==0?0:1));

두 부분으로 분리:

(비율 >>32)는 소수점 이하 32자리를 생략하는 것을 의미하며,

(비율 %(1<<32)==0?0:1) 마지막 소수점 이하 32자리는 반올림됩니다.

일반적으로 Q128.128을 Q128.96으로 변환하는 것입니다.

계속하려면. . .

おすすめ

転載: blog.csdn.net/gambool/article/details/129087906