[Uniswap-v3] 0. Constructor

·

4 min read

0. 들어가며

https://youtu.be/WW_xRGXSr7Q?si=zU-x_gGsMOO4S01c

이건 유니스왑 V3 코어 컨트랙트를 코드를 뜯어보며 설명해주는 강의이다!

이걸 하루에 하나씩 들으면서 정리하는 것이 목푠데 꼬박꼬박은 못해도 꾸준히 한번 해봐야겠다! ㅎㅎ

이 강의영상은 유니스왑 v3 코어의 컨트랙트 코드를 보면서 직접 컨트랙트를 짜는 (기존 코어 컨트랙트 구조보다 단순화시켜서) 강의인데,

그래서 내 프로젝트(Clamm)와 깃헙에서 클론으로 가져온 v3-core를 하위 디렉토리로 먼저 만들고, 내 프로젝트는 foundry 프레임워크를 이용해서 세팅을 해주게된다.

현재 내 배경지식수준은

https://youtu.be/CU7ZKnDqhUA?si=apj_SRKfecClc7wd

이 시리즈로 유니스왑의 개념을 아주조금 찍먹해본 상태이다. AMM이 어떤건지도 들어만 본 정도?? 아무튼 이정도에서 위 강의를 시작해봄,,

1. Setup

(0) 사전작업 : 파운드리 설치하기

curl -L <https://foundry.paradigm.xyz> | zsh # 나는 zsh를 터미널로 사용해서 이렇게 바꿔주었다(안해도되는듯)

# warning: libusb not found. You may need to install it manually on MacOS via Homebrew (brew install libusb).
#Detected your preferred shell is zsh and added foundryup to PATH. Run 'source /Users/hanbin/.zshenv' or start a new terminal session to use foundryup.
#Then, simply run 'foundryup' to install Foundry.
#이런 메세지가 떠서 하라는대로 해주었다.

brew install libusb
source /Users/내맥북이름/.zshenv
foundryup

# 설치가 잘 진행된다!

(1) 디렉토리 구조와 파운드리로 프로젝트 시작하기

mkdir clamm
git clone <https://github.com/Uniswap/v3-core.git>
# 두가지 하위 디렉토리를 생성해준다. 
#하나는 강의를 진행하며 직접 내가 작성할 유니스왑 프로젝트(CLAMM)
#하나는 참조용 진짜 유니스왑V3 프로젝트 클론(v3-core)

cd clamm
forge init
# 프로젝트 초기화를 해준다
# /clamm/src 디렉토리에 CLAMM.sol 파일을 생성해준다.

2. CLAMM.sol 파일 작성하기

이 파일은 강의를 진행하며 직접 생성할 나만의 유니스왑 V3 pool 컨트랙트이다.

(1) 먼저 저작권과 컴파일러버전을 명시해준다.

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;

강의에 나온대로 했더니 버전에러가 떴다. 내 현재 컴파일러 버전과 맞지않다는 것이다.

foundry.toml파일에서 다음을 추가해주었다.

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.19" # 👈 이걸 추가했다.
# See more config options <https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options>

그리고도 또 빨간 줄이 떠있어서 눌러보니 VSC 상에서 여러 컴파일러 버전 목록이 위에 나와서 타겟인 0.8.19을 선택했다. 이제 버전 세팅까지 마쳤으니 컨트랙트 코드를 작성할 수 있다.

(2) 유니스왑 코드 확인하기

우선 유니스왑의 UniswapV3Pool.sol 파일에서 constructor 코드를 확인해보자

constructor() {
    int24 _tickSpacing;
    (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
    tickSpacing = _tickSpacing;

    maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
}

생성자함수 내에서

  • factory

  • token0

  • token1

  • fee

  • _tickSpacing

을 초기화 해주는 것을 확인할 수 있다.

다시 유니스왑의 컨트랙트에서 위로 올려서 해당 변수를 선언한 코드를 확인해본다.

/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override factory;
/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override token0;
/// @inheritdoc IUniswapV3PoolImmutables
address public immutable override token1;
/// @inheritdoc IUniswapV3PoolImmutables
uint24 public immutable override fee;

/// @inheritdoc IUniswapV3PoolImmutables
int24 public immutable override tickSpacing;

모두 immutable로 선언된 것을 확인할 수 있다!

먼저 각각이 무슨 역할을 하는 변수인지를 알아보자.

<aside> 💡 constructor에서 초기화하는 불변변수의 이해

  • token0, token1 : 이 Pool에 담게 될 두가지 토큰 컨트랙트의 주소

  • fee : 토큰의 스왑이 일어날 때 수수료의 비율

  • tickSpacing : 뭔지 모르겠음.. ⇒ 다음 강에서 이것을 다룰 것! </aside>

다시 constructor로 돌아와 해당 변수를 초기화하는 라인을 보면,

(factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();

Deployer라는 컨트랙트를 통해 위의 값을 가져오고 있다(factory 인듯함). 우리는 Factory는 따로 생성하지 않을 것이기 때문에 위의 값들은 constructor의 인자값으로 바로 받아와 초기화 해주는 형태로 구성할 것이다.

(4) constructor에서 인자값 설정

나의 CLAMM 컨트랙트에도 위의 변수를 선언해준다.

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;

contract CLAMM {
    address public immutable token0;
    address public immutable token1;
    uint24 public immutable fee;
    int24 public immutable tickSpacing;

    constructor(
        address _token0,
        address _token1,
        uint24 _fee,
        int24 _tickSpacing
    ) {
        token0 = _token0;
        token1 = _token1;
        fee = _fee;
        tickSpacing = _tickSpacing;
    }
}

위에서 말했듯이 우리는 간략화한 pool 컨트랙트를 작성하는 것이 목표이기 때문에 factory 없이 직접 constructor의 인자로 값을 받아 초기화 해준다.

(5) Tick?

maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);

다시 유니스왑 컨트랙트로 돌아가보면… constructor에 이런 코드가 있다.

현재 상태에서 나는

maxLiquidityPerTick, Tick, tickSpacingToMaxLiquidityPerTick, _tickSpacing

여기 나오는 4개의 이름을 다 모른다! 하하하하

한가지 알 수 있는 것은 Tick은 어떤 라이브러리라는 것이다. 해당 파일로 가서 위 Tick라이브러리의 tickSpacingToMaxLiquidityPerTick 메소드를 확인해보자

/// @notice Derives max liquidity per tick from given tick spacing
/// @dev Executed within the pool constructor
/// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing`
///     e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ...
/// @return The max liquidity per tick
function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128) {
    int24 minTick = (TickMath.MIN_TICK / tickSpacing) * tickSpacing;
    int24 maxTick = (TickMath.MAX_TICK / tickSpacing) * tickSpacing;
    uint24 numTicks = uint24((maxTick - minTick) / tickSpacing) + 1;
    return type(uint128).max / numTicks;
}

음 뭘 위한 코드인지 잘 모르겠다. tick spacing의 개념 자체가 지금은 없다.

우선은 위와 똑같이 Tick 라이브러리를 만들고 함수 시그니처만 복사해두고 tick의 개념을 이해한 뒤 다시 코드를 보기로 하자.

/lib/Tick.sol

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;

library Tick {
    function tickSpacingToMaxLiquidityPerTick(
        int24 tickSpacing
    ) internal pure returns (uint128) {
                // @TODO : 로직 작성하기!
        }
}

/CLAMM.sol

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;

import "./lib/Tick.sol";

contract CLAMM {
    address public immutable token0;
    address public immutable token1;
    uint24 public immutable fee;
    int24 public immutable tickSpacing;
    uint public immutable maxLiquidityPerTick;

    constructor(
        address _token0,
        address _token1,
        uint24 _fee,
        int24 _tickSpacing
    ) {
        token0 = _token0;
        token1 = _token1;
        fee = _fee;
        tickSpacing = _tickSpacing;

        maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(
            _tickSpacing
        );
    }
}

maxLiquidityPerTick을 선언하고 초기화해주는 코드를 추가해주었다.

constructor의 구성은 위와같이 단순하다.

아직 tick, price, tickSpacing 같은 개념을 모르는 상태에서는 이해할 수 있는것은 이정도이다.

이 개념은 다음 강의에서 배울 예정!