์ ๋์ค์์ 2020๋ ๋ถํฐ 2021๋ ์ด๊น์ง, DeFi Summer ๋์ ์ ๋์ฑ์ ๋์ด์ฌ๋ฆฐ ์ค์ถ์ ์ธ ์ญํ ์ ๋ด๋นํ์ต๋๋ค. Automative Market Maker(์ดํ AMM)๋ผ๋ ๋ ๋ณด์ ์ธ ๋๊ตฌ๋ก์จ, ์ฆ์ ์๋นํธ๋ผ์ง ๊ธฐํ๋ฅผ ์ ๊ณตํ๋ ๋์์, DeFi ํ๋กํ ์ฝ์ ์ฝ๊ฒ ํตํฉ๋์ด ๋ ๋ง์ ๊ฑฐ๋ ๋ณผ๋ฅจ๊ณผ ์ ๋์ฑ์ ์ฐฝ์ถํ์์ต๋๋ค.
์ดํ์ ์ ๋์ค์์ ํฌํฌํ ํ๋กํ ์ฝ๊ณผ ์์ ๋ง์ ๋ ํนํ ์์์ ๊ฐ์ง Balancer์ Curve, ๊ทธ๋ฆฌ๊ณ ์ด ํ๋กํ ์ฝ๋ค์ ํฌํฌ๊น์ง ๋ง์ AMM๋ค์ด ๋ฑ์ฅํ๊ฒ ๋์๊ณ , ๊ฐ์ ๊ฑฐ๋ฒ๋์ค ํ ํฐ์ ๋ฟ๋ฆฌ๋ฉด์ ์ ๋ก์๋ ์ ๋์ฑ ์์น๊ฐ ํผ์ณ์ก๋ค๊ณ ํ์ฌ๋ ๊ณผ์ธ์ด ์๋ ๊ฒ๋๋ค.
์ดํ์, ๋ค์๊ณผ ๊ฐ์ ๋ ํนํ ๊ธฐ๋ฅ๋ค์ ๊ฐ์ง ์๋ก์ด ์ธ๋์ ์ ๋์ค์ V3๊ฐ ๋ฐํ๋์์ต๋๋ค.
์ด ์ ๋์ฑ์ ์ง์ ํ ๊ฐ๊ฒฉ๋์ ์ง์ค์ํฌ ์ ์๋ ์ด ๊ธฐ๋ฅ์ด ๋ํ์ ์ธ๋ฐ, ๊ฐ์ ๊ฐ์น๋ฅผ ๊ฐ์ง ํ ํฐ๋ค ์ฌ์ด์ ๊ตํ์ผ๋ก ์ ๋ช ํ Stable Swap์ Curve์ ์ ๋์ค์์ ๋น๊ตํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
๋จ์ ์์๋ฃ์ ์ฐจ์ด์ด๊ธฐ ์ด์ ์ ๊ฒฐ๊ณผ๋ก ๋ณด์ด๋ฏ, ๊ฐ์ ๊ฐ์น์ ๋ํ ๊ตํ์ ์์ด์ ๊ทน๋จ์ ์ธ ์ฐจ์ด๊ฐ ๋ฐ์ํ๊ธฐ์, ๊ฐ์ ๊ฐ์น์ ์์ฐ์ ๊ตํํ๋ ๋ฐ ์์ด ์ต์ ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋ค๋ ์ ์ ๋๋ค. ํนํ๋, ์ด๋ฌํ ์ ๋์ฑ๋ค์ด ์ ๋์ค์์ ํตํด ๋ชจ์ด๊ฒ ๋๋ค๋ ์ ์ ์ฃผ๋ชฉํ ๋งํฉ๋๋ค.
์ด๋ ๊ฒ ์๋ณธ ํจ์จ์ฑ(Capital Efficiency)์ด ๊ทน๋ํ๋๋ ํ๋กํ ์ฝ์ ํตํฉํ๋ ๊ฒ์ ๋ง์ ์ ์ฌ์ฑ์ ๊ฐ์ง๊ฒ ํฉ๋๋ค. ์ด๋ฏธ ์ ๋์ค์์ ํตํฉํ์ฌ ๋ค์ํ ๊ธฐํ๋ค์ ์ ๋ชฉํ๋ ๊ฒ์ ๋ณด๋ฉด, ์์ง ์จ๊ฒจ์ง ์ ์ฌ์ฑ์ด ๋ง์ง ์์๊ฐ ํ๋ ์๊ฐ์ด ๋ค๊ฒ๋ ํฉ๋๋ค. ์ ๋์ค์์ ํตํฉํ ์์๋ ์ด๋ค ๊ฒ์ด ์์๊น์?
์ ๋์ค์์ด ์ ๋์ฑ์ ํน์ ๊ฐ๊ฒฉ๋์ ์ง์คํ ์ ์๊ฒ ๋๋ฉด์, ๊ฑฐ๋๊ฐ ์์ฃผ ์ผ์ด๋๋ ๊ฐ๊ฒฉ๋์ ๋ง์ถฐ ์ ๋์ฑ์ ์ง์์ ์ผ๋ก ์ฎ๊ฒจ์ฃผ๋ ํ๋กํ ์ฝ๋ค์ด ๋์ค๊ฒ ๋์์ต๋๋ค. ์ด ํ๋กํ ์ฝ๋ค์ ๊ตํ์ผ๋ก ๋ฐ์๋๋ ์์๋ฃ๋ฅผ ์ฃผ๋ ๋ชฉํ๋ก ํ์ฌ ๊ฐ๊ฒฉ๋๊ฐ ๋ณํ ๋๋ง๋ค, ๊ธฐ์กด ์ ๋์ฑ์ด ์์นํ ๊ฐ๊ฒฉ๋๋ฅผ ๋์ ํ์ฌ ์ฎ๊ฒจ์ค๋๋๋ค.
์๋ฅผ ๋ค์ด ETH:USDC ์ ๋์ฑ ํ์ 100๋ช ์ด ๊ฐ๊ฐ ์ ๋์ฑ ๊ณต๊ธ์ ํ๊ณ ์๋ค๊ณ ํ ๋, ๊ฑฐ๋๋๋ ๊ฐ๊ฒฉ๋๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ด๋ฅผ 100๋ช ๋ชจ๋ ๊ฐ๊ฐ ์ ๋์ฑ์ด ์์นํ ๊ฐ๊ฒฉ๋๋ฅผ ์ฎ๊ฒจ์ผ ํ์ง๋ง, ์ด๋ฌํ ํ๋กํ ์ฝ์ ์ด์ฉํ๊ณ ์๋ค๋ฉด ๋ช ๋ช ๋ถ์ ์ ๋์ฑ์ด๋ , ํ ๋ฒ์ ๋์ ์ฎ๊ฒจ์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฐ์ค๋น๋ฅผ ์๋ชจํ๋ ๋ถ๋ถ์ ์์ด์๋ ๋ ์ ๊ฒ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ ๊ฒ์ ํ์ฑํ๋ ์ ๋์ฑ ๊ณต๊ธ(Active LP)์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค. ๋ํ ์ด๋ฐ ์๋ํ๋ ํ๋กํ ์ฝ์ ์์ธ ์์๋ฃ๋ฅผ ์ฒญ๊ตฌํ์ฌ ๋ค์ ์ ๋์ฑ์ผ๋ก ๋ํด์ฃผ๋ ๊ธฐ๋ฅ๋ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์, ๋ณต๋ฆฌ์ ํจ๊ณผ ๋ํ ๋๋ฆด ์ ์๋ค๋ ์ ์ด ์์ต๋๋ค.
์ผ๋ฐ ์ด์ฉ์๊ฐ ์ ๋์ค์์ ์ ๋์ฑ ๊ณต๊ธํ๋ฉด, ๊ณต๊ธ์ ์ฆํ๋ก NFT๋ฅผ ๋ฐ๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ ์ฆํ๊ฐ NFT๋ก ์ ๊ณต๋์์ ๋ ๋จ์ ์ ๊ธฐ์กด ํ๋กํ ์ฝ๊ณผ์ ํตํฉ์ ์ด๋ ค์์ ๊ฒช๋๋ค๋ ์ ์ ๋๋ค. ์ผ๋ฐ์ ์ธ ํ ํฐ์ ๋ด๋ณด๋ก ๋์ถ์ ํ๋ ํ๋กํ ์ฝ์ NFT๋ฅผ ์์น๋ฐ๊ธฐ ์ํด์ ์ธํฐํ์ด์ค๋ฅผ ์ถ๊ฐ ๊ฐ๋ฐํด์ผํ๊ณ , NFT๊ฐ ๊ฐ์ง๊ณ ์๋ ์ ๋ณด๋ฅผ ๊ฒ์ฆํ ์ ์์ด์ผ ํ๊ธฐ์ ํ๋กํ ์ฝ์ ํ์ฅ์ ํ์์ ์ ๋๋ค.
์ด๋ฌํ ์ ์ ๊ฐ์ ์ํจ ๊ฒ์ด Gelato Network๋ผ๋ ์๋ํ ํ๋กํ ์ฝ์ด ์ ๊ณตํ๋ G-UNI ํ ํฐ์ด ์์ต๋๋ค. Gelato๋ฅผ ํตํด ์ ๋์ค์์ ์ ๋์ฑ์ ๊ณต๊ธํ๊ฒ ๋๋ฉด ๊ฐ๊ฒฉ๋๋ฅผ ์๋์ผ๋ก ์ฎ๊ฒจ์ค ๋ฟ๋ง ์๋๋ผ, ์ ๊ธฐ์ ์ผ๋ก ์์ธ ์์๋ฃ ๋ํ ๋ค์ ์ ๋์ฑ์ผ๋ก ๊ณต๊ธํ์ฌ ์ค๋๋ค. ๋ํ ์ ๋์ฑ์ ๋ํ ์ฆํ๊ฐ ์ผ๋ฐ์ ์ธ ERC20์ผ๋ก ์ ๊ณต๋๊ธฐ ๋๋ฌธ์ MakerDAO์ ๊ฐ์ ๊ธฐ์กด ํ๋กํ ์ฝ์ ํตํฉ๋์ด ์์ต๋๋ค.
์ ๊ฐ์ธ์ ์ธ Social DAO๋ฅผ ์์ํ๋ฉด์ ๋ช๋ช ์ฅ์น๋ค์ ๋์ ํ ํ์๊ฐ ์์์ต๋๋ค. ํ์ค ์ธ๊ณ์ ์ฃผ์์ ์ค๋๋ถ์ ์ด์ฉํ ๊ตํ์ด ์์ฃผ ์ผ์ด๋๋๋ฐ ๋ฐํด, ๋ธ๋ก์ฒด์ธ์์๋ ๋ง์ ํ ํฐ๋ค์ด AMM์ ์ด์ฉํ์ฌ ๊ตํ๋๋ค๋ ์ ์ ์ฃผ๋ชฉํ์์ต๋๋ค. ํนํ๋ ์ ๋์ฑ์ด ์ ์ ํ ํฐ์ ์ํด์ ์ ๋์ฑ์ ํ ๊ณณ์ ๋ชจ์ ํ์๊ฐ ์์๊ณ , ๋ฐ์ด๋ฐฑ์ด๋ ํ ํฐ ๋ฐํ์ ํตํ ํฌ์์ ํ๋ ๋ฐ ์์ด์๋ ์ข์ ๋ฐฉ๋ฒ์ด ํ์ํ์ต๋๋ค.
์ด๋ฌํ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํ๋ ๋์, DAO์ ํฌํ๊ถ์ด ์ ๋์ค์ bean:ETH์ ๋ํ ์ ๋์ฑ์ด ๋๋๋ก ๊ตฌ์ฑํ์ต๋๋ค. ์ด๋ฅผ ํตํด์ DAO์ ๊ฐ์น๋ฅผ ์ง์์ ์ผ๋ก ์ถ์ ํ ์ ์๊ฒ ๋์๊ณ , ์ฌ์ฉ์๋ ๊ฑฐ๋ฒ๋์ค ํ ํฐ์ ์คํ ์ดํน ํ์์ ๋ ์ต๋ ๊ฐ์น๋ฅผ ๋๋ ์ ์๋๋ก ํ์ฌ, ๊ฑฐ๋ฒ๋์ค ์ฐธ์ฌ๋ฅผ ๋์ฑ ๋ ๋ คํ ์ ์๊ฒ ๋์์ต๋๋ค.
์ ๋์ฑ์ด ํ๊ณณ์ ๋ชจ์ด๊ฒ ๋์์ผ๋ฏ๋ก, TWAP์ ํตํด DAO์ ๊ฐ์น ์ฐ์ ์ด ๋์ฑ ์ฝ๊ฒ ์ด๋ค์ง ์ ์๋ค๊ณ ๋ณด์ฌ์ง๋๋ค. ๊ฑฐ๋ฒ๋์ค๊ฐ ๊ฐ์ง ํ ํฐ๊ณผ ์ ๋์ฑ์ ์ฝ๊ฒ ๊ณ์ฐ๋์ด ์๋ํ๋ ๊ณต์๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ค๋ ์ , ๋ํ ํฌํ๊ถ์ด ์ ๋์ฑ์ผ๋ก ์ด๋ค์ง๊ธฐ ๋๋ฌธ์ ๋ ๊ฐ์ ํ ํฐ์ด ์๋๋ผ, ํ๋์ ํ ํฐ. ์๋ฅผ ๋ค์ด ์์ํ๊ฒ ETH๋ง ์คํ ์ดํนํ๋ ๊ฒ์ผ๋ก๋ ๊ฑฐ๋ฒ๋์ค ์ฐธ์ฌ๊ฐ ๊ฐ๋ฅํ๊ฒ ๋๋ฏ๋ก DAO์ ์์ด์ ์ง์์ ์ธ ๋์ ์ด ๋๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค. ํด๋น ์ฝ๋๋ UniswapModule ์์ ํ์ธํ ์ ์์ต๋๋ค.
โbean the DAO ์ํโ๋ฅผ ๊ฐ๋ฐํ๋ ๋์ค ์ ๋์ค์์ ํตํฉํ ๋ ๊ต์ฅํ ๋ง์ ๋ฌธ์ ์ ๋ด์ฐฉํ์ต๋๋ค. ์ ๋์ค์ V3๋ถํฐ ์ธํฐํ์ด์ค๋ค์ด ๊ธฐ๋ฅ์ ๋ฐ๋ผ ๋ถํ ๋์ด ๋ค์ํด์ก๊ณ , ๋ฐฑ์์ ๊ธฐ๋ก๋ ๋ด์ฉ์ด ์ค์ ๊ตฌํ๋ ์ฝ๋๋ฅผ ์์ ํ ์ค๋ช ํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ ๊ฐ๋ฐ์ ๋ฌธ์๋ฅผ ํ์ธํ๊ณ , ๋๋ก๋ ๋ฌธ์๋ก๋ ํด๊ฒฐ๋์ง ์์ ์ค์ ๋ก ์ฌ์ฉ๋๋ ์์๋ฅผ ์ฐพ๊ธฐ ์ํด ์ธํฐํ์ด์ค๊ฐ ์ด๋ป๊ฒ ์ปจํธ๋ํธ๋ฅผ ํธ์ถํ๋์ง ํ์ธํ ํ์๊ฐ ์์์ต๋๋ค.
๊ทธ๋์, ์ด ๊ธ์ ํตํด ์ ๋์ค์์ ํตํฉํ ๋ ๋์ผํ ๋ฌธ์ ๋ฅผ ๊ฒช์ง ์๋๋ก, ์ ๋์ค์์ ์ฝ๋ ๋ ๋ฒจ์์ ์ดํดํ ์ ์๋๋ก ์๋ดํ๊ณ , ์ ๋์ค์ ์ปจํธ๋ํธ๊ฐ ์ ๊ณตํ๋ ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉ ์์๋ก ์๋ ค๋๋ฆฌ๋ ค๊ณ ํฉ๋๋ค.
๊ฐ์ฅ ๋จผ์ , ์ํ๋ ํ ํฐ ์์ผ๋ก ์ ๋์ฑ ํ์ ๋ง๋ค ์ ์์ด์ผ ํฉ๋๋ค. ์ ๋์ฑ ํ์ ์ ๋์ค์ ํฉํ ๋ฆฌ ์ปจํธ๋ํธ๋ฅผ ํตํด์ ์์ฑ๋๋ฉฐ, ์ ๋์ค์์ ๋ชจ๋ ๋ฐฐํฌ๋ ์ปจํธ๋ํธ ์ฃผ์๋ Uniswap Contract Deployments์์ ํ์ธํ ์ ์์ต๋๋ค. ์์ฃผ ๋คํ์ธ ์ ์, ๋๋ถ๋ถ์ ์ปจํธ๋ํธ๊ฐ ์ฒด์ธ์ ๊ด๊ณ์์ด ๋์ผํ ์ฃผ์๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ ์ ์ ๋๋ค.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "./StandardToken.sol";
contract UniswapV3Integration {
address public constant UNIV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
IUniswapV3Factory v3Factory = IUniswapV3Factory(UNIV3_FACTORY);
IUniswapV3Pool pool;
StandardToken token0;
StandardToken token1;
uint24 constant fee = 3000;
constructor() {
token0 = new StandardToken("bean the token", "bean", 18, 100000e18);
token1 = new StandardToken("Wrapped Ether", "WETH", 18, 0);
pool = IUniswapV3Pool(v3Factory.createPool(address(token0), address(token1), fee));
}
}
์ด ์ฝ๋๋ ์ ๋์ค์์ ์ง์ ์ ์ผ๋ก ํตํฉํ๋ ์ปจํธ๋ํธ์ด๋ฉฐ, ์ ๋์ค์์ ์ธํฐํ์ด์ค๋ npm ์ ํตํด์ ์ฝ๊ฒ ์ถ๊ฐํด์ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.
์ปจํธ๋ํธ๊ฐ ๋ฐฐํฌ๋ ๋, ๋ ๊ฐ์ ํ์ค ERC20์ ์์ฑํ๋๋ก ํ์ต๋๋ค. ์ด๋ ๊ฐ๊ฐ bean๊ณผ WETH๋ผ๋ ํ ํฐ์ผ๋ก ๋ฐฐํฌ๋๋ฉฐ, ์ ๋์ฑ ํ์ ๋ง๋ค ๋ ํ์ํ ๋ ๊ฐ์ ํ ํฐ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ์ด ๋ ํ ํฐ์ ์ด์ฉํ์ฌ ์ ๋์ค์ ํฉํ ๋ฆฌ ์ปจํธ๋ํธ๋ฅผ ํธ์ถํ์ฌ ์ ๋์ฑ ํ์ ์์ฑํ๋๋ก ํฉ๋๋ค.
ํฉํ ๋ฆฌ ์ปจํธ๋ํธ์ createPool ํจ์ ๋ด๋ถ๋ฅผ ๋ณด๋ฉด ๋ ํ ํฐ์ ์ฃผ์์ ์์๋ฃ ๊ฐ์ ์
๋ ฅ๋ฐ๊ฒ ๋๋๋ฐ, ์ด ์ ๋ณด๋ค์ ์ถฉ๋ํ์ง ์๋ ์ปจํธ๋ํธ ์ฃผ์๋ฅผ ๊ตฌ์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
...
new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}();
...
์๋ฆฌ๋ํฐ์์๋ ์ปจํธ๋ํธ๋ฅผ ๋ฐฐํฌํ ๋ salt๋ฅผ ์ง์ ํ๋ฉด create2๋ฅผ ์ด์ฉํ์ฌ ๋ฐฐํฌ๋๋ฉฐ, salt ๊ฐ์ ๋ฐ๋ผ ์ปจํธ๋ํธ ์ฃผ์๋ฅผ ํ์ ํ ์ ์์ต๋๋ค. ์ ๋์ฑ ํ์ด create2๋ฅผ ์ด์ฉํ์ฌ ๋ฐฐํฌ๋๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ์ฌ๊ธฐ์์ ์ฌ์ฉ๋๋ salt๋ โtoken0์ ์ฃผ์, token1์ ์ฃผ์, ๊ทธ๋ฆฌ๊ณ ์์๋ฃ ๊ฐโ์ ์์๋๋ก ๋ฐฐ์นํ์ฌ keccak256๋ก ํด์ฑ ํ ๊ฐ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด, token0๊ณผ token1์ ์์๋ฅผ ๋ฐ๊พธ๋ฉด ์ ํ ๋ค๋ฅธ ํด์๊ฐ์ด salt๋ก ์ฌ์ฉ๋๋๋ฐ, ๊ฐ์ ํ ํฐ ๊ตฌ์ฑ๊ณผ ์์๋ฃ ์ฒด๊ณ๋ฅผ ๊ฐ์ง๊ณ ์์ง๋ง ์๋ก์ด ์ ๋์ฑ ํ์ ๋ง๋ค๊ฒ ๋์ง ์์๊น์?
// ๊ฒฐ๊ณผ: 0xd240917b4f29206a2f1fa9ca8c04135d5fb29b9de9297735e850d15b6343ea43
function sampleHashOne() external pure returns(bytes32) {
return keccak256(abi.encode(address(1), address(2), 3000));
}
// ๊ฒฐ๊ณผ: 0x55b5bb173ff5ab0df513148295f13e7d273d1e487d43c8e8221c3cf0816df006
function sampleHashTwo() external pure returns(bytes32) {
return keccak256(abi.encode(address(2), address(1), 3000));
}
์ฌ๊ธฐ์์ ์ ๋์ค์์ ํน์ง ์ค ํ๋๊ฐ ๋์ค๋๋ฐ, โ์ ๋์ค์์ ํ ํฐ์ ์์๋ฅผ ๊ต์ฅํ ์ค์ํ๊ฒ ์๊ฐํ๊ณ ์๋ค.โ ๋ ์ ์ ๋๋ค. ์ ๋์ค์ ์ ๋ฐ์ ์ผ๋ก ๋ ๊ฐ์ ํ ํฐ ์ฃผ์๊ฐ ๋ค์ด์์ ๋, token0์๋ ์๋์ ์ผ๋ก ์์ ์ฃผ์๋ฅผ ๊ฐ์ง ํ ํฐ์ด, token0์๋ ์๋์ ์ผ๋ก ํฐ ์ฃผ์๋ฅผ ๊ฐ์ง ํ ํฐ์ด ์ค๋๋ก ์กฐ์ ๋ฉ๋๋ค.
์ด๊ฒ์ด ๊ฐ๋ฅํ ์ด์ ๋, ์ปจํธ๋ํธ ์ฃผ์ ๋ํ 16์ง์๋ก ๊ตฌ์ฑ๋ ์ซ์์ด๊ธฐ ๋๋ฌธ์ ๋น๊ต๊ฐ ๊ฐ๋ฅํ๊ณ , ์ฃผ์ ์์ฒด๊ฐ ํด์๋ฅผ ํตํด ๊ฒฐ์ ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ถฉ๋์ด ์์ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ฌํ ํน์ฑ์ผ๋ก ํฌ๊ณ ์์์ ๋ํ ๋น๊ต๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ํ ํฐ ์์์ ์ค์์ฑ์ ์ ๋์ฑ ํ์ด ์์ฑ๋ ๋๋ฟ๋ง ์๋๋ผ, ์์ผ๋ก ๋์ฌ ๋ค๋ฅธ ๊ธฐ๋ฅ๋ค์๋ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
์๋ก์ด ์ ๋์ค์์ ์ด์ฉํ์ฌ ์ํ๋ ๊ฐ๊ฒฉ๋์ ์ ๋์ฑ์ ์ง์ค์ํฌ ์ ์๊ฒ ๋๋ฉด์ ์ ๋์ค์์์์ ๊ตํ๋น์จ์ด ์ข์ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ๋ฐ๊ฒฌ๋๊ณค ํฉ๋๋ค. ํนํ๋, ๊ฐ์น๊ฐ ๊ฐ์ ํ ํฐ๋ค ์ฌ์ด์ ์ ๋์ฑ ํ์์ ์ด์ ์ด ๊ทน๋ํ๋์๋๋ฐ DAI/USDC์ ๊ฒฝ์ฐ๋ฅผ ์์๋ก ๋ณผ ์ ์๊ฒ ์ต๋๋ค.
๋ ํ ํฐ์ ๊ฐ์น๊ฐ ๊ฑฐ์ ๋์ผํ๊ฒ ํ๊ฐ๋๋ค ๋ณด๋, 0.9998~1.0001 ๊ฐ๊ฒฉ ์ฌ์ด์ ์ ๋์ฑ์ด ๊ณต๊ธ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ํ ์ ๋์ฑ์ด ๋์ ๋ฒ์๋ฅผ ์ปค๋ฒํ์ง ์์ ํ ํฐ์ ๊ตํํ ๋ ๊ฐ๊ฒฉ ๋ณ๋์ฑ ๋ํ ๊ทน๋๋ก ๋ฎ๋ค๋ ํน์ง์ด ์์ต๋๋ค. ์ ๋์ฑ ๊ณต๊ธ์๋ค๋, ๋ ๊ฐ์ ํ ํฐ์ด ๋์ผํ ๊ฐ์น์ ์๋ ดํ๋ค๋ ์ปจ์ผ์์ค๊ฐ ์๊ธฐ ๋๋ฌธ์ ์ ๋์ค์์ ๋ฐํ๋๋ก ์๋ณธ ํจ์จ์ฑ(Capital Efficiency)์ด ์์ฒญ๋๋ค๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ด์ฒ๋ผ ์ ํฌ์ ์์ ์์๋ ์ ๋์ฑ์ ํน์ ๊ฐ๊ฒฉ ๋ฒ์์ ๋ฑ๋กํ๋ ๊ฒ์ ๋ชฉํ๋ก ํฉ๋๋ค. ์์ ๋ง๋ ์ ๋์ฑ ํ์ ํ ํฐ์ ๊ณต๊ธํ๊ธฐ ์ํด์๋ ์ด๊ธฐ ๊ฐ๊ฒฉ์ ์ง์ ํ ๋ค์, ๋ฑ๋กํ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ง์ ํด์ผ ํฉ๋๋ค.
์ ๋์ค์ V2์์๋ ์ ๋์ฑ ํ์ด ์์ฑ๋๋ฉด ๊ฐ๊ฐ์ ํ ํฐ ์๋์ ์์นํ๋ ๊ฒ์ผ๋ก ์ด๊ธฐ ๊ฐ๊ฒฉ๊ณผ ๊ตํ ๋น์จ์ ์ค์ ํ์๋ค๋ฉด, V3์์๋ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ง์ ํ์ฌ ์ ๋์ฑ์ ๋ฑ๋กํ ์๋ฐ์ ์๊ธฐ ๋๋ฌธ์ ๋จ์ํ ํ ํฐ์ ๋น์จ๋ก ์ด๊ธฐ ๊ฐ๊ฒฉ๊ณผ ๊ตํ๋น์จ์ ์ ์ ์๋ค๋ ์ ์ ๋๋ค. ๊ทธ๋ ๊ธฐ์, โ์ด๊ธฐ ๊ฐ๊ฒฉโ๊ณผ โํ ํฐ์ ๋ฑ๋กํ ๊ฐ๊ฒฉ ๋ฒ์โ๋ฅผ ์ ๋์ฑ ํ์ ๊ธฐ๋กํ์ฌ์ผ ํฉ๋๋ค.
์ ๋์ค์์์ ์ฌ์ฉ๋๋ ๊ฐ๊ฒฉ์ ์ ๊ณฑ๊ทผ(Squared Root)์ ์ด์ฉํฉ๋๋ค. โ์ด๊ธฐ ๊ฐ๊ฒฉโ๊ณผ โํ ํฐ์ ๋ฑ๋กํ ๊ฐ๊ฒฉ ๋ฒ์โ์ ์ ๊ณฑ๊ทผ ํํ๋ก ๊ณ์ฐํ์ฌ ์ ๋์ฑ ํ์ ๋ฑ๋กํ๊ฒ ๋ฉ๋๋ค. ๋ฐฑ์์ ์ ํ ์์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ด๊ฒ์ ์ ๋์ค์์ด ๋ฐ๋ฅด๊ณ ์๋ ๊ธฐ๋ณธ ์์์ธ ์ ํด๋นํ๋ ๊ฒ์ด๋ผ ๋ณผ ์ ์๊ณ , ์ด ์์์ ํตํด์ 1 ETH๊ฐ 1,800 DAI๋ผ๋ ๊ฐ๊ฒฉ์ ๊ณ์ฐํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๊ทธ๋ฌ๋ ์์์๋ ๋งํ๋ฏ, ์ ๋์ค์์์๋ ํ ํฐ์ ์์๊ฐ ๋ฌด์ฒ ์ค์ํฉ๋๋ค. ์ด๋ค ํ ํฐ์ด ๋ถ์์ ๋ถ๋ชจ์ ์์นํด์ผ ํ๋์ง ์์์ผ ํ๊ธฐ ๋๋ฌธ์
๋๋ค. WETH์ ํ ํฐ ์ฃผ์๋ 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2์ด๊ณ , DAI์ ํ ํฐ ์ฃผ์๋ 0x6b175474e89094c44da98b954eedeac495271d0f์
๋๋ค. ์ปจํธ๋ํธ ์ฃผ์์ ํฌ๊ธฐ ๋น๊ต๋ฅผ ํ๋ฉด, ์ ๋์ฑ ํ์ token0์ DAI์ด๋ฉฐ, token1์ WETH๊ฐ ๋ ๊ฒ์
๋๋ค. ์ด๋ฅผ ์ด์ฉํ์ฌ sqrtPrice๋ฅผ ๊ณ์ฐํ๋ ์๋ฆฌ๋ํฐ ์ฝ๋๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
uint160 sqrtPriceX96;
uint256 PRECISION = 2**96;
// 1 weth๊ฐ 1800 DAI์ธ ๊ฒฝ์ฐ. weth๋ ์์์ 18์๋ฆฌ ์ง์, DAI ๋ํ ์์์ 18์๋ฆฌ ์ง์.
sqrtPriceX96 = uint160(sqrt((1e18 * PRECISION * PRECISION) / 1800e18));
// ๊ฒฐ๊ณผ: 1867425699159537997291064607
sqrtPriceX96 = uint160(sqrt((1800e18 * PRECISION * PRECISION) / 1e18));
// ๊ฒฐ๊ณผ: 3361366258487168395123916293647
์๋์ ๊ฒฐ๊ณผ๊ฐ ํ ํฐ์ ์์๋ฅผ ๋ฐ๋๋ก ๋ง๋ ๊ฒฐ๊ณผ์ธ๋ฐ, ์ ํ ๋ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ ๋์ค๊ฒ ๋ฉ๋๋ค. ๋น์ฐํ ๊ฒฐ๊ณผ์ด๊ฒ ์ฃ ! ์ด๋ ๊ฒ ์ ๋์ค์์์ ์ฌ์ฉ๋๋ sqrtPrice์ ์ต๋ ์ต์๊ฐ์ ๊ฐ๊ฐ 1461446703485210103287273052203988822378723970342๊ณผ 4295128739๋ก ์ ํด์ ธ ์๊ณ , ๊ฐ๊ฒฉ ๊ณต๊ฐ๊ณผ ๋งค์นญ์ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๊ฐ๊ฒฉ ๊ณต๊ฐ๊ณผ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ ๋ํด ์ด๋ป๊ฒ ์ผ์น๋๋์ง ์์๊ฒ ์ง์? ๋ง์ฝ ํ ํฐ ์์๋ฅผ ๋ฐ๋๋ก ๊ตฌ์ฑํ์ฌ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ ์ ๋ ฅํ๊ฑฐ๋, ํ ํฐ ๊ตํ, ์ ๋์ฑ ๋ฑ๋ก์ ํ๋ฉด ํธ๋์ญ์ ์ด ๋๋ถ๋ถ ์คํจํ๊ฒ ๋๋ค๋ ์ ์ ์ ์ํ ํ์๊ฐ ์์ต๋๋ค.
ํ์ง๋ง, ์ฌ์ฉ์์ ๊ด์ ์์๋ โํ ํฐ A๋ ํ ํฐ B๋ก ํ์ฐํ์ ๋ ์ผ๋ง๊ฐ ๋์ด์ผ ํ๋โ ๋๋, โ๊ฐ๊ฐ ์ผ๋ง์์ ํ ํฐ ๊ณต๊ธ์ ํ ๊ฒ์ธ์งโ๋ฅผ ๊ณ ๋ คํ๊ธฐ ๋๋ฌธ์, ํ ํฐ์ ์์๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ๋ค์์ ํจ์๋ฅผ ์์ฑํ์ฌ ์ค๋๋ค.
/**
* @notice ํ ํฐ ์ฃผ์์ ์์์ ๋ฐ๋ผ ์ ๊ณฑ๊ทผ๋ ๊ฐ๊ฒฉ์ ์๋์ผ๋ก ๊ณ์ฐํฉ๋๋ค.
* @param addr0 ํ ํฐ ์ฃผ์0
* @param addr1 ํ ํฐ ์ฃผ์1
* @param reserve0 ํ ํฐ ์ฃผ์0์ ํด๋นํ๋ ํ ํฐ ์๋
* @param reserve1 ํ ํฐ ์ฃผ์1์ ํด๋นํ๋ ํ ํฐ ์๋
* @return sqrtPriceX96 ๊ฐ ๋ฆฌ์ ๋ธ์ ๋ฐ๋ฅธ ํ์ฌ ํ ํฐ์ ๊ฐ์น
*/
function encodeSqrtPrice(
address addr0,
address addr1,
uint256 reserve0,
uint256 reserve1
) public pure returns (uint160 sqrtPriceX96) {
if (addr0 > addr1) {
sqrtPriceX96 = uint160(sqrt((reserve0 * PRECISION * PRECISION) / reserve1));
} else {
sqrtPriceX96 = uint160(sqrt((reserve1 * PRECISION * PRECISION) / reserve0));
}
}
์ ๋์ค์์ ์ ๊ณฑ๊ทผ ๊ฐ์ผ๋ก ํ์๋๋ ๊ฐ๊ฒฉ์ ์ฌ์ฉํ๋ ์ด์ ๋ก, โ๊ธฐ๋ณธ AMM ์๊ณ ๋ฆฌ์ฆ์ธ ์ ๊ธฐํํ์ ํน์ฑ์ผ๋ก ์ ๋์ค์์์ ์ฌ์ฉ๋๋ ์ํ์ ์ ๋ค์ด ๋ง์ต๋๋ค.โ๋ผ๊ณ ์ด์ผ๊ธฐํ๊ณ ์์ผ๋ฉฐ, ์์์ ๊ณ์ ๋์๋ sqrtPrice์ ์ ๋ฏธ์ด๋ก ๋ถ๋ X96์ ๋ฌด์์ผ๊น์?
EVM์ ๋ถ๋์์์ ์ ์ํฅ์ ๋ฐ์ง ์๊ธฐ ์ํด ๋ชจ๋ ์ ๋ณด๋ฅผ 256๋นํธ ๋จ์๋ก ์ ์ฅํ๊ณ ์์ต๋๋ค. ๊ฐ์ฅ ์ฌ์ด ์ค์ ์์๋ก, ERC20 ์๊ณ ๋ ์ ์๋ก๋ง ์ ์ฅ๋ฉ๋๋ค. ๊ทธ๋ ์ง๋ง, ์์์ ์ ๋ณด์ ๋ฐ๋ผ 18์๋ฆฌ ์์์ ๋๋ 6์๋ฆฌ ์์์ ๋ฑ์ผ๋ก ํํ๋ ์ ์์ต๋๋ค. ์๊ณ ๊ฐ 1๋ก ์ ์ฅ๋์ด ์๋ค๋ฉด, 18์๋ฆฌ ์์์ ์ ์ง์ํ๋ ํ ํฐ์์๋ 0.000000000000000001๋ก ํํ๋๊ณ , 6์๋ฆฌ ์์์ ์ ์ง์ํ๋ ํ ํฐ์์๋ 0.000001๋ก ํํ๋ฉ๋๋ค.
์ ๋์ค์์์๋ ์ ๋ฏธ์ด X๋ค์ ๋ฐ๋ผ์ค๋ ์ซ์๋ฅผ ์์์ ์ ํํํ๋๋ฐ ์์ฝ๋ ๋นํธ(bit)๋ผ๊ณ ์ค๋ช
ํ๊ณ ์์ต๋๋ค. sqrtPriceX96 ์์ฒด๋ uint160์ ์ ์ฅํ๊ณ ์์ผ๋ฏ๋ก, 160 - 96 = 64๋นํธ๋ ์ ์๋ฅผ ํํํ๋ ๋ฐ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ข
์ข
์ ๋์ค์ ์ฝ๋ ๋๋ ๋ฌธ์๋ฅผ ๋ณด๋ฉด sqrtRatioX96 ์ด ๋ํ๋๋๋ฐ, sqrtPriceX96๊ณผ ๋์ผํ ์๋ฏธ๋ก ์ฌ์ฉ๋๋ ๊ฒ์
๋๋ค.
๋ค์ ์ ํฌ ์ฝ๋๋ก ๋์์ค๋ฉด, ์ด์ sqrtPriceX96์ ๊ณ์ฐํ ์ ์๊ฒ ๋์๊ธฐ ๋๋ฌธ์ ์ ๋์ฑ์ ๋ฑ๋กํ๊ธฐ ์ํ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ๊ณ์ฐํ ๊ฒ์
๋๋ค. ์ ๋์ฑ์ ๋ฑ๋กํ ๋ ๋ ๊ฐ์ ํ ํฐ ๋ชจ๋๋ฅผ ์ค๋นํ๋ ๋ฐฉ๋ฒ๋ณด๋ค ๋ ๊ฐ์ ํ ํฐ ์ค ํ๋๋ง ๋ฑ๋กํ ์ ์๋๋ก ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ค์ ํ ๊ฒ์
๋๋ค.
์ฐ์ ์ ํฌ๋ 1 bean์ 0.01 ~ 10 ETH์ ๊ฐ๊ฒฉ ๋ฒ์๋ก ๋ฑ๋กํ๋ ค๊ณ ํฉ๋๋ค. ๊ทธ๋ ๊ธฐ์ ์ ๋์ฑ ํ์ด ๊ฐ์ง๊ณ ์์ด์ผ ํ๋ ์ด๊ธฐ ๊ฐ๊ฒฉ์ 0.01 bean/ETH ๊ฐ ์ ํฉํ๊ณ , ์ด๊ฒ์ ๊ณ์ฐํ์ฌ ๊ฐ๊ฒฉ ์ ๋ณด๋ฅผ ๋ฑ๋กํฉ๋๋ค.
uint160 initialPriceX96 = encodeSqrtPrice(address(token0), address(token1), 1e18, 0.01 ether);
// ์์ ๋ง๋ Pool์ ๊ฐ๊ฒฉ ์ ๋ณด๋ฅผ ์ด๊ธฐํ ํฉ๋๋ค.
pool.initialize(initialPriceX96);
์ด๊ธฐ ๊ฐ๊ฒฉ ์ ๋ณด๋ ์ ๋์ฑ์ด ๋ฑ๋ก๋๊ธฐ ์ด์ ์๋ ๋ช ๋ฒ์ด๊ณ ํธ์ถ์ด ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์, ๋ฐ๋ก ์ดํ์ ์ ๋์ฑ์ด ๊ณต๊ธ๋๋ ๊ฒ์ด ํฉ๋นํฉ๋๋ค. ๋ค๋ง ์ฌ๊ธฐ์์๋ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ด ์ง์ ์ ์ผ๋ก ์ฌ์ฉ๋์ง ์๊ณ , ์๋ฃํ์ด int24์ธ Tick์ ์ด์ฉํ๊ฒ ๋ฉ๋๋ค.
Tick์ด๋ ์ ๋์ฑ ํ์ ํ์ฌ ๊ฐ๊ฒฉ์ด 0.01% ๋ณ๋๋ ๋๋ง๋ค ํ ์นธ์ฉ ์์ง์ด๋ ๋๊ธ์ด๋ผ๊ณ ์๊ฐํด์ผ ํฉ๋๋ค. ์ด ๋๊ธ์ ๋จ์๋ ์ ๋์ฑ ํ์ด ๊ฐ์ง๋ ์์๋ฃ ์ฒด๊ณ์๋ ์ง์ ์ ์ธ ์๊ด๊ด๊ณ๊ฐ ์์ต๋๋ค.
constructor() {
...
feeAmountTickSpacing[500] = 10;
emit FeeAmountEnabled(500, 10);
feeAmountTickSpacing[3000] = 60;
emit FeeAmountEnabled(3000, 60);
feeAmountTickSpacing[10000] = 200;
emit FeeAmountEnabled(10000, 200);
}
์ ํฌ๊ฐ ์์ ์ง์ ํ๋ 0.3%์ ํด๋นํ๋ ์์๋ฃ์ 1 Tick์ 60์ด๋ผ๋ ๋จ์๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. 1% ์์๋ฃ์์๋ 200์ด๊ณ , 0.05% ์์๋ฃ์์๋ 10์ ๋๋ค. ์ดํ์ ์ถ๊ฐ๋ 0.01% ์์๋ฃ์์๋ 1์ ๋๋ค.
๋คํ์ค๋ฝ๊ฒ๋ ์ด๋ฌํ Tick์ ๋ณํํ๊ณ ๊ณ์ฐํ๊ธฐ ์ํด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณต๋๋ฉฐ, ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ Tick์ผ๋ก ๋ณํํ๊ฑฐ๋ ๊ทธ ๋ฐ๋์ ๋ณํ๋ ๊ฐ๋ฅํฉ๋๋ค. Tick ์์ญ ๋ํ ์ต๋ ์ต์ ๊ฐ์ด ๊ฒฐ์ ๋์ด ์๋๋ฐ, integer๋ฅผ ์ฌ์ฉํ๊ณ ์์ด 887272 ์์ -887272๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด๋ ๊ฒ, ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ๊ณผ Tick์ด ์๋ก ๋ฐ๋ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์์ผ๋, ์ํ๋ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ๋จผ์ ๊ณ์ฐํ ๋ค์์ ๊ทธ๊ฒ์ Tick์ผ๋ก ๋ณํํ๋ฉด ์ ํฌ๊ฐ ์ํ๋ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ป์ ์ ์๊ฒ ๋ฉ๋๋ค. ๊ฐ๊ฒฉ์ ๋ฒ์๋ฅผ ์ค์ ํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ์์ ๊ฐ๊ฒฉ๊ณผ ๋ ๊ฐ๊ฒฉ ์ด๋ ๊ฒ ๋ ๊ฐ์ง๋ฅผ ํ์๋ก ํฉ๋๋ค.
๋ ๊ฐ๊ฒฉ์ 10 bean/ETH์ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ ๊ตฌํ ๋ค์, Tick์ผ๋ก ๋ณํ ์ํค๋ฉด ๋์ง๋ง, 0.01 bean/ETH ์ ๋ํ ์ด๊ธฐ ๊ฐ๊ฒฉ์์ 1 Tick ์ฆ๊ฐ์ํจ ๊ฐ์ ์์ ๊ฐ๊ฒฉ์ผ๋ก ์ฌ์ฉํ๊ณ ์ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ ์ด์ ๋ ์ ๋์ฑ ๋ฒ์์์ ํ๋์ ํ ํฐ๋ง ๋ฑ๋กํ ์ ์๋๋ก ํ๋ ค๋ ๋ชฉ์ ์ธ๋ฐ, ์ข ๋ ์์ธํ ์ด์ ๋ ๋์ค์ ์ค๋ช ๋ฉ๋๋ค.
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
int24 tickSpacing = 60; // 0.3% ์์๋ฃ ํ์ TickSpacing
uint160 upperPriceX96 = encodeSqrtPrice(address(token0), address(token1), 1e18, 10 ether);
int24 lowerTick = TickMath.getTickAtSqrtRatio(initialPriceX96) + tickSpacing;
int24 upperTick = TickMath.getTickAtSqrtRatio(upperPriceX96);
TickMath์ getTickAtSqrtRatio ํจ์๋ฅผ ์ด์ฉํ์ฌ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ Tick์ผ๋ก ๋ณํ์์ผฐ์ต๋๋ค. ํ์ง๋ง! ๋จ์ํ ์ด๋ ๊ฒ๋ง ์ฌ์ฉํ๋ฉด ์ ๋์ฑ์ ๋ฑ๋ก๋ ์๋ ๋์ง ์์ ์๋ ์์ต๋๋ค. ์์ Tick์ ๋จ์์ ๋ํด ์๋ ค๋๋ ธ๋๋ฐ, ์ด๋ฅผ ๋ค๋ฅด๊ฒ์ด์ผ๊ธฐ ํ์๋ฉด ๋ชจ๋ Tick ๊ฐ์ Tick์ ๋จ์๋ก ๋๋์์ ๋ ๋๋จธ์ง ๊ฐ์ด ์์ด์ผ ํ๋ค ๋ ์ ์ ๋๋ค.
๊ตํ๋ ํ ํฐ์ 0.3%๊ฐ ์์๋ฃ๋ก ์ ๋ฆฝ๋๊ธฐ ๋๋ฌธ์ ์ ๋์ฑ ๋ฒ์ ๋ํ ์์๋ฃ์ ๋ฐ๋ฅธ ๋จ์, 1 Tick ๋จ์๋ก ์์ง์ฌ์ผ ํ๋ค๋ ์ ์ ๋๋ค. ๊ณ์ฐํ Tick ๊ฐ์ Tick Spacing์ผ๋ก ๋๋จธ์ง๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๊ณ์ฐํ์ฌ, ๋๋จธ์ง๊ฐ ์๋ค๋ฉด ๋นผ๊ณ ๋ณด์ ํ ํ์๊ฐ ์์ต๋๋ค
if ((lowerTick % tickSpacing) != 0)
lowerTick = lowerTick - (lowerTick % tickSpacing) + (lowerTick < 0 ? -tickSpacing : tickSpacing);
if ((upperTick % tickSpacing) != 0)
upperTick = upperTick - (upperTick % tickSpacing) + (upperTick < 0 ? -tickSpacing : tickSpacing);
require(upperTick > lowerTick);
์์ ๋ณธ Tick Space๋ ์์์์ ์์๋ก ์ด๋ค์ ธ ์๊ณ , ๊ฐ๊ฒฉ ๊ณต๊ฐ๊ณผ 1:1 ์ผ์นํ๊ธฐ ๋๋ฌธ์ ์ด๋์๋ ํ ํฐ์ ์์๊ฐ ๊ทธ๋๋ก ์ ์ฉ๋๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ๋ฎ์ Tick์ ๋์ Tick๋ณด๋ค ๋ฌด์กฐ๊ฑด ์์์ผ ํฉ๋๋ค. ์ด์ ์ ๋์ฑ ๋ฒ์๋ฅผ ์ํ Tick์ ๊ตฌํ์ผ๋ ์ค์ง์ ์ผ๋ก ์ ๋์ฑ์ ๋ฑ๋กํ ์ฐจ๋ก์ ๋๋ค.
์ ๋์ฑ์ด ๋ฑ๋ก๋๊ธฐ ์ํด์๋ ํ์ฌ ์ ๋์ฑ ํ์ ๊ฐ๊ฒฉ๊ณผ, ๊ฐ๊ฒฉ ๋ฒ์, ๊ทธ๋ฆฌ๊ณ ๋ฑ๋กํ๊ณ ์ ํ๋ ํ ํฐ ์๋์ ๊ธฐ๋ฐํ์ฌ ๋จ์ผ ์ซ์๋ก ๋ํ๋ธ liquidity๋ฅผ ๊ณ์ฐํด ๋ด์ด์ผ ํฉ๋๋ค. ์ด๋ LiquidityAmounts ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ getLiquidityForAmounts ํจ์๋ฅผ ์ด์ฉํ์ฌ ๊ณ์ฐํ ์ ์์ต๋๋ค. ์ด ํจ์์์๋ ํ์ฌ ์ ๋์ฑ ํ์ ์ ๊ณฑ๊ทผ ๊ฐ๊ฒฉ๊ณผ, ๊ฒ์ฆ๋ Tick ๊ฐ์ ๋ค์ ์ ๊ณฑ๊ทผ ๋ ๊ฐ๊ฒฉ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ์ฃผ๊ณ , ๊ฐ๊ฐ์ ์ ๋์ฑ ๊ณต๊ธํ ํ ํฐ ์๋์ ์ ์ด์ค๋๋ค.
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
(uint256 token0Amount, uint256 token1Amount) = (0, 100000e18);
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
initialPriceX96,
TickMath.getSqrtRatioAtTick(lowerTick),
TickMath.getSqrtRatioAtTick(upperTick),
token0Amount,
token1Amount
);
์ ํฌ๊ฐ ๋ง๋ค์ด๋ธ ํ ํฐ bean๊ณผ WETH๋ ์ฃผ์๊ฐ ๊ฐ๊ฐ, 0x1866โฆ ๊ณผ 0x0c7bโฆ์
๋๋ค. ์ด๋ ๊ฒ ๋๋ฉด ์ ๋์ฑ ํ์ token0์ WETH์ด๊ณ , token1์ bean์ด ๋ฉ๋๋ค. ์ ํฌ๋ bean ํ ํฐ ํ๋๋ง ๋ฑ๋กํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, token1Amount์๋ง ๋ฑ๋กํ ์ ๋์ฑ ์๋์ ์ ์ด ์ฃผ์์ต๋๋ค. ์ด์ ์ ๋ง ๊ณ์ฐํด๋ธ liquidity๋ฅผ ์ด์ฉํ์ฌ ์ ๋์ฑ ํ์ ์ ๋์ฑ์ ๋ฑ๋กํ ์ฐจ๋ก์
๋๋ค.
(uint256 amount0, uint256 amount1) = pool.mint(
address(this),
lowerTick,
upperTick,
liquidity,
bytes("")
);
์ ํฌ๊ฐ ๋ง๋ค๊ณ ์๋ ์ปจํธ๋ํธ๊ฐ ์ ๋์ฑ ํ์ mintํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์์๋๋ก ์ ๋์ฑ์ ์์ ์๋ก ๊ธฐ๋ก๋ ์ฃผ์, ๋ฑ๋กํ ๊ฐ๊ฒฉ ์์ญ์ ๋ํ ๋ฎ์ Tick๊ณผ ๋์ Tick ๊ทธ๋ฆฌ๊ณ ์์ ๊ณ์ฐํด๋ธ liquidity ๊ฐ์ ๋ฃ๊ณ , ๋ง์ง๋ง์ผ๋ก๋ ํธ์ถ์์๊ฒ ์ฝ๋ฐฑ ํ ๋ฐ์ดํฐ๋ฅผ ์ง์ด๋ฃ๋ ๋ถ๋ถ์
๋๋ค.
๋ง์ง๋ง ์ธ์๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ ์ ๋์ค์ V3๊ฐ ๊ฐ์ง๊ณ ์๋ ์ปจํธ๋ํธ ๊ตฌ์กฐ๋ฅผ ์ดํดํ ํ์๊ฐ ์์ต๋๋ค. ์ ๋์ฑ ํ์ ์ปจํธ๋ํธ๊ฐ ์๋, ์ฝ๋๊ฐ ์๋ ์ง๊ฐ๊ณผ ๋ ๋จ์ ์ผ๋ก ์ํธ์์ฉํ ์ ์๋๋ก ์ค๊ณ๊ฐ ๋์์ต๋๋ค.
...
uint256 balance0Before;
uint256 balance1Before;
if (amount0 > 0) balance0Before = balance0();
if (amount1 > 0) balance1Before = balance1();
IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data);
if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');
...
์ ์ฝ๋๋ ์ ๋์ฑ ํ์ mint ํจ์ ๋ด๋ถ์
๋๋ค. ์ค๊ฐ์ ์ฝ๋ฐฑ์ ํธ์ถํ๋ ๊ฒ์ ์ ์ ์๋๋ฐ, ์ฌ๊ธฐ์์ msg.sender๋ ์ ๋์ฑ ํ์ ์ด์ฉํ๋ ์ฃผ์๊ฐ ์กํ๊ฒ ๋ฉ๋๋ค.
์ผ๋ฐ์ ์ธ EOA๋ ์ฝ๋๋ฅผ ๊ฐ์ง๊ณ ์์ง ์์ผ๋ ์ ๋ถ๋ถ์์ ํธ๋์ญ์
์ด ์คํจํ๊ฒ ๋ฉ๋๋ค. ์ ๋์ค์ ์ด์ฉ์๋ค์ด ์ ๋์ฑ์ ๊ณต๊ธํ ๋์๋ uniswapV3MintCallback ํจ์๊ฐ ๊ตฌํ๋์ด ์๋ NonfungiblePositionManager์ ์ด์ฉํ๊ณ , NFT๋ ํด๋น ์ปจํธ๋ํธ์์ ๋ฐํ๋๊ฒ ๋ฉ๋๋ค. ์ด๋ฌํ ์ฝ๋ฐฑ ํจ์๋ค์, ์ ๋์ฑ ํ์ ํธ์ถํ ์ปจํธ๋ํธ์์ ์คํ๋๋ฉฐ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ํ ํฐ์ ์ ๋์ฑ ํ๋ก ์ฎ๊ธฐ๋ ์ญํ ์ ํฉ๋๋ค.
import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol";
// Pool์์ fallback์ผ๋ก ํธ์ถ๋๋ ํจ์
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata
) external {
if (amount0Owed != 0) token0.transfer(address(pool), amount0Owed);
if (amount1Owed != 0) token1.transfer(address(pool), amount1Owed);
}
ํด๋น ํจ์๊ฐ ์คํ๋๋ ์ปจํธ๋ํธ๊ฐ bean ํ ํฐ์ ์์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์, ์ผ๋ฐ์ ์ธ transfer ํจ์๋ฅผ ์ด์ฉํ์ฌ ํ ํฐ์ด ์ ๋์ฑ ํ๋ก ์ฎ๊ฒจ์ง๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค. ํ ํฐ์ ์ปจํธ๋ํธ๊ฐ ๊ฐ์ง๊ณ ์์ง ์๊ณ , ์ฌ์ฉ์์ ์ง๊ฐ์์ ํ ํฐ์ ์ ๋์ฑ ํ๋ก ์ฎ๊ฒจ์ผ ํ๋ค๋ฉด transferFrom ํจ์๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ์์ ์ง๊ฐ์์ ์ ๋์ฑ ํ๋ก ํ ํฐ์ด ์ ์ก๋๋๋ก ํ์ฌ์ผ ํฉ๋๋ค.
์ฝ๋ฐฑ์ด ๋๋์ผ๋ง ์ ๋์ฑ์ด ๋ฑ๋ก๋๋๋ฐ, ํ์ฌ ์ ๋์ฑ์ ์ํ์ ๋ฐ๋ผ ๋๋จธ์ง ํ ํฐ์ด ์๊ฒจ ์ค์ ๋ก ๋ฑ๋ก๋๋ ํ ํฐ์ ์๋์ด 100000 ๊ฐ๊ฐ ์๋, 99999.999999999999999994์ธ ๊ฒ์ฒ๋ผ ์ฝ๊ฐ์ ์์ก์ด ๋จ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ๊ธฐ๋ ํฉ๋๋ค. ๋์ฒด๋ก ์ด๋ฐ ๊ฒฝ์ฐ ์ ๋์ฑ์ ๋ฑ๋กํ๊ธฐ ์ํ ๊ฐ๊ฒฉ ๋ฒ์๊ฐ ํ ํฐ์ ๋ฑ ๋จ์ด์ง๊ฒ ๋ด๊ธฐ์ ์ถฉ๋ถํ์ง ์๊ฑฐ๋, ๊ณ์ฐ์์ผ๋ก ๋๋จธ์ง ์ซ์๊ฐ ๋ฐ์ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ์ด๋ฌํ ์ ์ด ๋ฐ์ํ๋ค๋ ๊ฒ์ ์๊ณ ์์ด์ผ ํฉ๋๋ค. uniswapV3MintCallback ํจ์๊น์ง ์ ํธ์ถ๋์๋ค๋ฉด, ์ ๋์ฑ์ ์ ๋ฑ๋ก๋์์ ๊ฒ์
๋๋ค.
์ ๋์ฑ์ด ํ๋ถํ AMM์์ ํน์ ๊ฐ๊ฒฉ๋์ ๊ตฌ๋งค๊ฐ ๊ฐ๋ฅํ ์ฃผ๋ฌธ์ ๋ฃ๊ธฐ ์ํด์๋ AMM ์ธ๋ถ์ ์์๋ฅผ ์ฌ์ฉํ์ฌ์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ต๋๋ค. ํธ๋์ญ์ ์ด ๋ฏธ๋ ์์ ์ ์คํ๋๊ธฐ ์ํด์ ํน์ ์ฑ๊ตด์๋ฅผ ๋ฏฟ์ด์ผ ํ์ผ๋ฉฐ, ์ด๊ฒ์ ์คํํ๋ ์ฃผ์ฒด๊ฐ ์๋ค๋ ์ ์ด ๋ถ๋ช ํ ๋จ์ ์ผ๋ก ์กด์ฌํ์ต๋๋ค. V3๋ถํฐ๋ ์ง์คํ๋ ์ ๋์ฑ์ ๊ณต๊ธํ ์ ์๊ฒ ๋๋ฉด์, ์๋ฒฝํ์ง๋ ์์ง๋ง ์ค๋๋ถ์ฒ๋ผ ์ด์ฉํ ์ ์๊ฒ ๋์์ต๋๋ค.
์ง์ ์ ์ธ ์์๋ก์จ, DAI/ETH ์ ๋์ฑ ํ์์ 1 ETH๊ฐ 1,500 DAI์ธ ๊ฒฝ์ฐ์ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ์์ ํ์ฌ ์ฃผ๋ฌธ์ ๋ฑ๋กํ ์ ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋ก ETH์ ๊ฐ๊ฒฉ ์์น์ด ์์๋์ด ETH์ 1,600Dai์ ํ๋งคํ๋ ์ฃผ๋ฌธ์ ๋ฑ๋กํ ์ ์์ต๋๋ค. ๋ ๋ฒ์งธ๋ ETH๊ฐ ๋ฎ์์ง ๋ ๊ตฌ๋งคํ๊ธฐ ์ํด์ DAI๋ฅผ ์ด์ฉํ์ฌ ETH๋ฅผ ๊ตฌ๋งคํ๋ ์ฃผ๋ฌธ์ ๋ฑ๋กํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด, ์ด๋ฐ ํํ์ ์ฃผ๋ฌธ์ ์ด๋จ๊น์? ETH์ ๊ฐ๊ฒฉ์ด 1,750 DAI์ ๋๋ฌํ๋ ๊ฒฝ์ฐ ์ดํ์ ์ถ๊ฐ์ ์ธ ์์น์ ์์ํ๋ค๊ณ ๊ฐ์ ํ์ฌ, 1 ETH๋ฅผ 1,750 DAI์ ๊ตฌ๋งคํ๋ ์ฃผ๋ฌธ์ ๋ฑ๋กํ๊ฑฐ๋, ETH์ ๊ฐ๊ฒฉ์ ์ถ๊ฐ์ ์ธ ํ๋ฝ์ด ์์๋๋ ๊ฒฝ์ฐ ํ๋ฝ์ ๊ฒช์ง ์๊ธฐ ์ํด ETH๋ฅผ 1,400 DAI์ ํ๋งคํ๋ ์ฃผ๋ฌธ์ ๋ฑ๋กํ๋ ๊ฒ๋๋ค. ํ์ง๋ง ์์ฝ๊ฒ๋ ์ด๋ฌํ Stop ๊ณ์ด์ ์ฃผ๋ฌธ์ ์ ๋์ค์์ ๋ฑ๋ก๋ ์ ์์ต๋๋ค. ์ด๋ฌํ Stop ๊ณ์ด ์ฃผ๋ฌธ๋ค์ AMM์ ์ ๋์ฑ ๊ทธ๋ํ๋ก ๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ด๋ฐ ์์๋ค์ ๋ณด์์ ์์๊ฒ ์ง๋ง, ์ ๋์ฑ ๊ณต๊ธ๊ณผ ์ค๋๋ถ ํํ์ ์ฌ์ฉ๋ฐฉ๋ฒ์ด ๋ค๋ฅด์ง ์๋ค๋ ๊ฒ๊ณผ ๊ทผ๋ณธ์ ์ผ๋ก๋ ์ ๋์ค์์ AMM ์์คํ ์ ๋ฐ๋ฅธ๋ค๋ ์ ์ ๋๋ค. ํ์ฌ ๊ฐ๊ฒฉ์ ๋ฒ์ด๋ ๊ฐ๊ฒฉ ์์ญ์ ๋ํด์๋ ํ๋์ ํ ํฐ๋ง ๋ฑ๋ก ๊ฐ๋ฅํ๋ค๋ ์ , ํ์ฌ์ ๊ฐ๊ฒฉ์ด ์ง์ ํ ๊ฐ๊ฒฉ ์์ญ์ ์ง๋๊ฐ๋ฉด ์์ ํด๋นํ๋ ํ ํฐ๋ง ์ ๋์ฑ์ ๋จ๋๋ค๋ ์ ์ ๋๋ค. ๊ทธ๋ ๋ค๋ฉด ๊ฐ์ฅ ์ข์ ์ฌ์ฉ๋ฐฉ๋ฒ์ ์ด๋ค ๊ฒ์ผ๊น์? ์ ํฌ๊ฐ ์ค๋๋ถ์ ์ฃผ๋ฌธ์ ๋ฑ๋กํ ๋์๋ ๋ฑ ๋จ์ด์ง๋ ๊ฐ์ ์ ๋ ฅํ์ง๋ง, ์ ๋์ค์์์๋ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ง์ ํด์ ๋ฃ์ด์ผ ํ๋ค๋ ์ ์ ๋๋ค. ์ ํฌ๋ ๊ฐ๊ฒฉ ๋ฒ์๋ฅผ ์ค์ ํ๋ ๊ฐ์ฅ ์ต์ ๋จ์์ธ Tick์ ์๊ณ ์์ต๋๋ค.
// ์์๋ฃ 0.3%์ ๋ํ Tick ๋จ์
int24 tickSpacing = 60;
// 1 ETH : 900 DAI์ ๋ํ ๊ฐ๊ฒฉ์ ์ฐ์ถํฉ๋๋ค.
uint160 lowerPriceX96 = encodeSqrtPrice(address(weth), address(dai), 1 ether, 900e18);
// ๋ฎ์ Tick๊ณผ ๋์ Tick์ ์ค๋นํ ๋, ๋์ Tick์ ๋ฎ์ Tick์์ 1Tick ๋งํผ ์์ง์ธ ์์ญ์ผ๋ก ์ง์ ํฉ๋๋ค.
int24 lowerTick = getTickAtSqrtRatio(lowerPriceX96);
int24 upperTick = getTickAtSqrtRatio(upperPriceX96) + tickSpacing;
// Tick ๊ฒ์ฆ
if ((lowerTick % tickSpacing) != 0)
lowerTick = lowerTick - (lowerTick % tickSpacing) + (lowerTick < 0 ? -tickSpacing : tickSpacing);
if ((upperTick % tickSpacing) != 0)
upperTick = upperTick - (upperTick % tickSpacing) + (upperTick < 0 ? -tickSpacing : tickSpacing);
require(lowerTick < upperTick);
// token0 ์ด DAI์ด๊ธฐ ๋๋ฌธ์, 9,000 Dai๋ฅผ ์ด์ฉํ์ฌ 10 ETH๋ฅผ ๊ตฌ๋งคํ๋๋ก ํฉ๋๋ค.
(uint256 token0Amount, uint256 token1Amount) = (9000e18, 0);
// ๋ฑ๋ก๋ ์ฃผ๋ฌธ์ ๋ํ ์ ๋์ฑ์ ๊ณ์ฐํฉ๋๋ค.
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(lowerTick),
TickMath.getSqrtRatioAtTick(upperTick),
token0Amount,
token1Amount
);
// ์ฃผ๋ฌธ์ ๋ฑ๋กํ๊ณ , ์ค์ ๋ฑ๋ก๋ ์ ๋์ฑ ์๋์ด ๋ฐํ๋ฉ๋๋ค.
(uint256 amount0, uint256 amount1) = pool.mint(
address(this),
lowerTick,
upperTick,
liquidity,
bytes("")
);
DAI/ETH ์ ๋์ฑ ํ์ด ํ์ฌ ๊ฐ๊ฒฉ์ผ๋ก 1500 DAI/ETH์ ์๋ค๊ณ ๊ฐ์ ํ๊ณ , 900 DAI/ETH๊ฐ ๋๋ฉด ์๋์ผ๋ก ๊ตฌ๋งค๊ฐ ๋๋๋ก ์ ๋์ฑ์ ๊ณต๊ธํฉ๋๋ค. ๊ฐ๊ฒฉ ๋ฒ์๋ 900 DAI/ETH + 1 Tick ์ผ๋ก ์ค์ ํฉ๋๋ค. ์ดํ์ ์ ๋์ฑ ํ์ ํ์ฌ ๊ฐ๊ฒฉ์ด 900 DAI/ETH ์ดํ๋ผ๋ฉด, ํด๋น ์ ๋์ฑ์ ETH๋ก๋ง ๋จ์์์ ๊ฒ์ ๋๋ค.
์ด ๋ํ ์ง์คํ๋ ์ ๋์ฑ ๊ณต๊ธ ๊ธฐ๋ฅ์ ์ด์ฉํ์ฌ AMM์ ์ ๋์ฑ์ ๋ฑ๋กํ๋ ๊ฒ๊ณผ ๊ฐ๊ธฐ ๋๋ฌธ์, Pool์ ๊ธฐ์ค ๊ฐ๊ฒฉ์ด 900 DAI/ETH ๊ฐ ๋๊ธฐ ์ ์ ์ ๋์ฑ์ด ์ ๊ฑฐ๋์ด์ผ ์ํ๋ ํ ํฐ์ผ๋ก ์ ๋์ฑ์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค. ๊ทธ๋ ค๋ฉด ์ ๋์ฑ ํ์ ์๋ ํ ํฐ์ ์ด๋ป๊ฒ ๊ตํํ ์ ์์๊น์?
์ ๋์ฑ ํ์ ๋ ์์ ์ผ๋ก ์๋ํ ์ ์๋ค ๋ณด๋, ์ ๋์ค์์์ ์ ๊ณตํ๋ SwapRouter๋ฅผ ์ด์ฉํ์ฌ ํ ํฐ์ ๊ตํํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค. ๋ํ ์ฌ์ฉ์์ ์ ์ฅ์์ ํ ํฐ์ ํ์ฉ์ SwapRouter์๋ง ํ๋ฉด ๋๋, ํธ๋ฆฌํ ์ ์ด ์๊ธฐ๋ ํฉ๋๋ค. ํ ํฐ์ ๊ตํํ๊ธฐ ์ํ SwapRouter์ ๋ ๊ฐ์ง ํจ์๋ฅผ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด ํจ์๋ ์ ๋์ฑ ํ๋ก ๋ค์ด์ฌ ํ ํฐ์ ์ ํํ ์๋์ ๊ฐ์ง๊ณ ์ ๋์ฑ ํ์์ ๋๊ฐ ํ ํฐ์ ์์ ์๋์ ๋ฐ๋๋ก ํฉ๋๋ค. ์ฌ๊ธฐ์์ ๊ตํ๋ ํ ํฐ์ ๋ํด ํ์ ์ ์ธ ์๋์ ๋ฐ์ ์ ์๋ ์ด์ ๊ฐ ์์ต๋๋ค. ํ ํฐ ๊ตํ ํธ๋์ญ์ ์ ๋ณด๋ด๊ณ ๋์ ํด๋น ํธ๋์ญ์ ์ด ๋ธ๋ก์ ๋ค์ด๊ฐ๊ธฐ ์ ๊น์ง, ์์ ๋ค๋ฅธ ํธ๋์ญ์ ๋ค๋ก ์ธํด ํ ํฐ์ ๊ตํ์ด ์ง์์ ์ผ๋ก ๋ฐ์ํ๊ฒ ๋๋ค๋ ์ ์ ๋๋ค. ์ดํ์ ๋์ ํธ๋์ญ์ ์ด ๋ธ๋ก์ ๋ค์ด๊ฐ ๋์๋ ์ ๋์ฑ ํ ๋ด๋ถ์ ํ ํฐ ๋น์จ์ ๋ณ๋์ด ์๊ธฐ๊ฒ ๋ฉ๋๋ค. ์ ๋์ฑ ํ์ ํ ํฐ ์๋์ด ๋ณ๊ฒฝ๋๋ ๊ฒ์ ์ฌ๋ฆฌํผ์ง(Slippage)๋ผ๊ณ ํฉ๋๋ค. ์ด๊ฒ์ด, ์ ๋์ฑ ํ์์ ๋๊ฐ๋ ํ ํฐ์ ๋ํด ์์ ์๋์ ์ง์ ํ ์๋ฐ์ ์๋ ์ด์ ์ ๋๋ค.
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
address UNIV3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
ISwapRouter v3router = ISwapRouter(UNIV3_ROUTER);
uint256 tokenOut = v3router.exactInput(
ISwapRouter.ExactInputParams({
path: abi.encodePacked(address(token0), fee, address(token1)),
recipient: address(this),
deadline: block.timestamp,
amountIn: 1e18,
amountOutMinimum: 0
})
);
ํจ์๊ฐ ๋ฐ๋ ์ธ์๋ถํฐ ํ์ธํ๋ฉด, ExactInputParams ๋ผ๋ ๊ตฌ์กฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ์ธ์๋ค์ ๊ตฌ์ฑํ๊ณ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ์ ๋์ ์ด์ ์, ๊ตฌ์กฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํธ ๋ฐ์ดํฐ๊ฐ ์ต์ํ์ ํจ๋ฉ์ผ๋ก ์์ถ๋๋ฉฐ, ์ธ์๋ฅผ calldata๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ ์ธ์๋ฅผ ๋ฉ๋ชจ๋ฆฌ๋ก ๋ณต์ฌํ์ง ์์๋ ๋์ด ํ ๋น ๋น์ฉ์ ์๋ ์ ์๋ค๋ ํน์ง์ด ์์ต๋๋ค.
์ฒซ ๋ฒ์งธ ์ธ์๋ฅผ ๋ณด๋ฉด, path๊ฐ ์๋๋ฐ ์ ๋์ฑ ํ๋ก ๋ค์ด์ฌ ํ ํฐ์ ์ฃผ์, ์์๋ฃ, ๊ทธ๋ฆฌ๊ณ ์ ๋์ค ํ์์ ๋๊ฐ ํ ํฐ์ ์ฃผ์๋ฅผ ์ง์ ํ์ฌ abi.encodePacked ๋ก ์์ถํ๊ณ ์์ต๋๋ค.
๋ ๋ฒ์งธ๋ก recipient๋ ๊ตํํด ๋ด๋ ํ ํฐ์ ๋ฐ์ ์ฃผ์๋ฅผ ์ง์ ํ๋ ๊ณณ์
๋๋ค. ํด๋น ์ฝ๋๊ฐ ์ปจํธ๋ํธ์์ ์คํ๋๊ธฐ ๋๋ฌธ์ address(this)๋ก ์ง์ ๋์์ต๋๋ค.
์ธ ๋ฒ์งธ๋ก deadline์ ํด๋น ํธ๋์ญ์ ์ด ๋ธ๋ก์ ๋ค์ด๊ฐ ์ต๋ ์๊ฐ์ ์ง์ ํ๋ ๊ฒ์ ๋๋ค. deadline์ 30๋ถ ๋ค๋ก ์ค์ ํ๊ณ , ํธ๋์ญ์ ์ด 30๋ถ ๋ค์ ๋ธ๋ก์ ๋ค์ด๊ฐ๋ค๋ฉด ํ ํฐ ๊ตํ์ ์คํจํ๊ฒ ๋ฉ๋๋ค. ์ด๋ ๊ฒ ์ค๋ ๊ธฐ๊ฐ์ด ์ง๋ฌ์ ๋ ๊ฐ๊ฒฉ ๋ณ๋์ ์์ธกํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ ์คํจ๋ฅผ ํ๋๋ก ์ต์ํ์ ์ฑ๊ณต ์๊ฐ์ ์ ๋ ฅํ๋ผ๊ณ ๋ ๊ฒ์ ๋๋ค. ์ ํฌ์ ๊ฒฝ์ฐ ๋ณ๋๋ก ์ค์ ํ์ง ์๊ณ , ํธ๋์ญ์ ์ด ๋ธ๋ก์ ๋ค์ด๊ฐ๋ ์๊ฐ์ด deadline์ผ๋ก ๊ตฌ์ฑ๋๋๋ก ํ์ต๋๋ค.
๋ค ๋ฒ์งธ amountIn์, token0์ ํ ํฐ์ด ์ ๋์ฑ ํ๋ก ๋ค์ด์ฌ ์๋์ ๋๋ค. ๋ฐ๋ก ๋ค์ amountOutMinimum์ 0์ผ๋ก ์ค์ ํ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ, ์ด๊ฒ์ ์ฌ๋ฆฌํผ์ง๋ฅผ ์ ๊ฒฝ ์ฐ์ง ์๊ฒ ๋ค๋ ์๋ฏธ์ ๊ฐ์ต๋๋ค. ๋ง์ฝ 1๊ฐ์ ํ ํฐ์ผ๋ก ์ต์ 10๊ฐ์ ํ ํฐ์ ๋ฐ์ ์ ์๋๋ฐ, ์ฌ๋ฆฌํผ์ง๋ฅผ ์๊ฐํ์ฌ 9.998๊ฐ ์ ๋ ๋ฐ์๋ ์๊ด์ด ์๋ค๋ฉด, ํด๋น ์์ญ์ 9.998์ ์ ๋ ฅํ๋ฉด ๋ฉ๋๋ค.
์ด๋ ๊ฒ ํด์ ํ ํฐ์ ๊ตํ์ด ์๋ฃ๋๋ฉด ์ค์ ๋ก ๊ตํ๋ ํ ํฐ์ ์๋์ด tokenOut์ด๋ผ๋ ์ซ์๋ก ๋ฐํ๋๊ธฐ ๋๋ฌธ์ ์ฝ๊ฒ ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ด ํจ์๋ exactInput๊ณผ ์์ ํ ๋ฐ๋์ ๊ฒฝ์ฐ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ํจ์๊ฐ ๋ปํ๋ ๋ฐ ๊ทธ๋๋ก ์ ๋์ฑ ํ์์ ๋๊ฐ ํ ํฐ์ ์ ํํ ์๋์ ๊ณ ์ ํด๋๊ณ ๋ค์ด์ฌ ํ ํฐ์ ๋ํด ์์ ์๋์ ๋ฐ๋๋ก ํฉ๋๋ค.
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
address UNIV3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
ISwapRouter v3router = ISwapRouter(UNIV3_ROUTER);
uint256 tokenIn = v3router.exactOutput(
ISwapRouter.ExactOutputParams({
path: abi.encodePacked(address(token1), fee, address(token0)),
recipient: address(this),
deadline: block.timestamp,
amountInMaximum: 2e18,
amountOut: 1e18
})
);
recipient์ deadline๋ ๋์ผํ ์ญํ ์ ํ๊ณ ์์ผ๋ฉฐ, path์ ๊ฒฝ์ฐ ๋ค๋ฅด๊ฒ ๋์ํฉ๋๋ค. ์ฌ๊ธฐ์์๋ ์ผ์ชฝ๋ถํฐ ์ ๋์ฑ ํ์์ ๋๊ฐ ํ ํฐ ์ฃผ์, ์์๋ฃ, ์ ๋์ฑ ํ๋ก ๋ค์ด์ฌ ํ ํฐ ์ฃผ์ ์์๋ก ์ด๋ค์ ธ ์์ต๋๋ค. ๋ค์ด์ฌ ํ ํฐ์ ์ค๋ฅธ์ชฝ์ ์์นํ์ฌ์ผ ํฉ๋๋ค.
์๋ ๋ค๋ฅธ ์์ญ amountInMaximum์ ์๋ amountOut ์๋๋งํผ ๊ตํ๋ ๋ ์ต๋๋ก ์ฌ์ฉํ ์ ์๋ ์๋์ ์ ์ด๋ ๊ฒ์ ๋๋ค. ์ดํ์ ์ค์์ด ์๋ฃ๋๋ฉด, ์ค์ ๋ก ๋ค์ด๊ฐ ํ ํฐ ์๋์ด tokenIn ์ผ๋ก ๋ฐํ๋์ด ํ์ธํ ์ ์์ต๋๋ค.
ํ ํฐ์ ๊ตํ์ ํ๋ ๋ฐ ์์ด์๋ ๋ค์ํ ๊ฒฝ์ฐ๋ค์ ์์ ํ ์ ์์ด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, bean ํ ํฐ์ ๊ตฌํ๋ ค๋ ์ฌ์ฉ์๊ฐ ์๋๋ฐ, DAI๋ง ๊ฐ์ง๊ณ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๊ทธ๋ฌ๋ bean์ ETH์ ์์ผ๋ก ๊ตฌ์ฑ๋ ์ ๋์ฑ ํ๋ฐ์ ์์ด์ ์ด ์ฌ์ฉ์๋ DAI๋ฅผ ETH๋ก ๊ตํํ ๋ค์, ๊ตํ๋ ETH๋ฅผ ๋ค์ bean์ผ๋ก ๊ตํํ์ฌ์ผ ํฉ๋๋ค.
์ด๋ฅผ ๊ฐ๊ฐ ํธ๋์ญ์ ์ผ๋ก ๊ตฌ์ฑํ๋ฉด, ๊ฐ์ค๋น๋ ์๊น๊ณ ํ ํฐ์ด ๊ตํ๋๋ ๋์ ์ฌ๋ฆฌํผ์ง๊ฐ ๋ฐ์ํ ์๋ ์๊ธฐ์ ํ ๋ฒ์ ์ฒ๋ฆฌ๋๋ ๊ฒ์ด ์ข์ ๊ฒ์ ๋๋ค. ์ด๋ฌํ ์ ๋์ฑ ํ๋ค์ ์ฐ๊ฒฐํด ์ฃผ๋ ๊ธฐ๋ฅ์ด V2์๋ ์์๊ณ , V3์๋ ์๊ธฐ์, path๋ฅผ ์ค์ ํ๋ ๊ฒ์ผ๋ก ์์ฃผ ์ฝ๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค.
ExactInputParams ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ์ฉํ๋ค๋ฉด, ์
๋ ฅ ํ ํฐ์ธ DAI์ ๊ตฌ์ฑ๋์ด ์๋ ETH ์ ๋์ฑํ, ๊ทธ๋ฆฌ๊ณ ETH์ ์ฐ๊ฒฐ๋์ด ์๋ bean ์ ๋์ฑ ํ์ ์ฐพ๋๋ก ํฉ๋๋ค.
...
path: abi.encodePacked(address(DAI), fee, address(WETH), fee, address(BEAN)),
...
์ด๋ ๊ฒ ํจ์ค๋ฅผ ๊ตฌ์ฑํ๋ฉด, DAI/ETH ์ ๋์ฑ ํ์ DAI๊ฐ ๋ค์ด๊ฐ๊ณ ETH๊ฐ ETH/BEAN ์ ๋์ฑ ํ๋ก ๋ค์ด๊ฐ, ์ต์ข ์ ์ผ๋ก bean์ด ๊ตํ๋์ด ๋๊ฐ๊ฒ ๋ฉ๋๋ค.
์ ๋์ค์์ ๋ฑ๋ก๋ ์ ๋์ฑ์ ๋ฑ๋กํ recipient๋ง ์ ๊ฑฐํ ์ ์์ต๋๋ค. ํนํ ๋์ผํ ๊ฐ๊ฒฉ ๊ณต๊ฐ์ ๋ค์ํ ์ด์ฉ์๋ค์ด ์ ๋์ฑ์ ๊ณต๊ธํ๋ ์ผ์ด ๋ ์๊ธฐ์, ๊ฐ๊ฐ์ ์ ๋์ฑ์ ๊ณ ์ ํ ํค๋ฅผ ๋ถ์ฌ ๊ด๋ฆฌํ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ํฌ์ง์ ํค๋ผ๊ณ ํ๋ฉฐ, ์ด๋ฅผ ๊ณ์ฐํ ์ ์๋ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค.
import {PositionKey} from "@uniswap/v3-periphery/contracts/libraries/PositionKey.sol";
bytes32 positionKey = PositionKey.compute(address(this), lowerTick, upperTick);
ํฌ์ง์ ํค๋ฅผ ๊ณ์ฐํ๋ ์ธ์๋ก, ์ ๋์ฑ ๊ณต๊ธ์์ ์ฃผ์, ๋ฎ์ Tick, ๋์ Tick์ ์ด์ฉํ์ฌ ๊ณ์ฐํ๊ฒ ๋ฉ๋๋ค. ๋ด๋ถ์ ์ผ๋ก ํด์ ํจ์๋ฅผ ์ด์ฉํ๋ฏ๋ก ๊ณ์ฐ๋๋ ํค๋ ์ ์ผํ ๊ฐ์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค. ๊ณ์ฐ๋ ํฌ์ง์ ํค๋ฅผ ์ด์ฉํ์ฌ ์ ๋์ฑ ํ์ ๋ฑ๋ก๋ ์ ๋์ฑ์ ์ ๊ทผํ ์ ์๊ฒ ๋๋ฉฐ, ์ด๋ฅผ ์ง์ ์ ์ผ๋ก ํด์งํ ์ ์์ต๋๋ค.
// ํด๋นํ๋ ํฌ์ง์
์ ์กด์ฌํ๋ ์ ๋์ฑ ์๋ ์กฐํ
(uint128 Liquidity, , , , ) = pool.positions(positionKey);
// ํด๋น ํฌ์ง์
์์ ์ ๋์ฑ ๋งํผ ํ ํฐ์ ์ ์ธํ๋ฉฐ, ๊ฐ ํ ํฐ ๋ง๋ค ์ ๋์ฑ ์ ๊ฑฐ๋๋ ์๋์ ๋ฐํํฉ๋๋ค.
(uint256 amount0, uint256 amount1) = pool.burn(lowerTick, upperTick, Liquidity);
// ์ ์ธ๋ ํ ํฐ ์๋ ์ ๋ถ๋ฅผ ์์งํ์ฌ ์ ์กํฉ๋๋ค.
(amount0, amount1) = pool.collect(address(this), lowerTick, upperTick, uint128(amount0), uint128(amount1));
burn ์ ํ๋ ๊ฒ์ผ๋ก ์ ๋์ฑ์ด ๋๋๋ ค์ง๋ ๊ฒ์ด ์๋๋ผ, ๋ฑ๋ก๋ ์ ๋์ฑ์ด ์ ์ธ๋ ์ํ๋ก ๋จ์์๊ฒ ๋ฉ๋๋ค. ์ดํ์ collect ํจ์๋ฅผ ํธ์ถํ๋ ๊ฒ์ผ๋ก ์ ์ธ๋ ์ ๋์ฑ์ ํธ์ถ ๋น์ฌ์์๊ฒ ์ ์กํ๋๋ก ํ์์ต๋๋ค.
์ ๋์ค์ V2์ V3์ ์์ฒญ๋ ์ฐจ์ด๋ฅผ ๋ณด์ด๋ ๋ถ๋ถ์ด ์์๋ฃ์ ์ฒ๋ฆฌ ๋ถ๋ถ์ ๋๋ค. V2์ ๊ฒฝ์ฐ ์์๋ฃ๊ฐ ์ ๋์ฑ์ ์๋์ผ๋ก ๋์ ๋๋ ๋ฐ๋ฉด, V3์ ๊ฒฝ์ฐ ์์๋ฃ๋ ์ ๋์ฑ์ ๋ํด์ง์ง ์๊ณ ์ ์ธ๋ ์ํ๋ก ๋จ์์๊ฒ ๋ฉ๋๋ค. ์ ๋์ฑ์ด ์ ๊ฑฐ๋์ด ์ฌ์ฉ๋์ง ์๋ ์ ๋์ฑ์ผ๋ก ์ ์ธ๋๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
๋ฐ๋ผ์ ๊ฐ๊ฐ์ ์ ๋ ฅ๊ฐ์ ์ต๋๋ก ์ ๋ ฅํ๋ ๊ฒ์ผ๋ก, ์ ์ธ๋ ์ ๋์ฑ ์ ์ฒด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๊ฒ ๋ฉ๋๋ค.
(amount0, amount1) = pool.collect(address(this), lowerTick, upperTick, type(uint128).max, type(uint128).max);
์ ํฌ๊ฐ ์์ฑํ ์ปจํธ๋ํธ๋ ์ ๋์ค์ ์ปจํธ๋ํธ์ ์ง์ ์ ์ผ๋ก ์ํธ์์ฉํ๋ ๋ฐฉ์์ผ๋ก ์์ฑ๋์์ต๋๋ค. ์ผ๋ฐ์ ์ธ ์ฌ์ฉ์๋ค์ ๐ฆ Metamask์ ๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ํตํด ๊ณต๊ฐํค-๋น๋ฐํค ๊ธฐ๋ฐ์ ์ง๊ฐ์ ์ฌ์ฉํ๋ฉฐ, ์ ๋์ฑ ํ๊ณผ ์ง์ ์ํตํ๋ ๋์ ์ ์ ๋์ค์ ์ธํฐํ์ด์ค์ ์ฐ๊ฒฐ๋ ํ ํฐ์ ๊ตํํ๊ธฐ ์ํด ๋ง๋ค์ด์ง SwapRouter, ์ ๋์ฑ ํ์ ์ ๋์ฑ์ ๊ณต๊ธํ๋ ๋ฐ ์ฌ์ฉ๋๋ NonFungiblePositionManager ์ ์ฌ์ฉํ๊ฒ ๋๋ฉฐ, ์ด๋ฌํ ๊ฒ๋ค์ Periphery ์ปจํธ๋ํธ๋ผ๊ณ ๋ถ๋ฅด๊ณ ์์ต๋๋ค.
Periphery ์ปจํธ๋ํธ๋ค์ ์ ๋์ฑ ํ๊ณผ ๊ฐ์ Core ์ปจํธ๋ํธ์ ๋ฐ์ํ๊ธฐ ์ํด callback ํจ์๋ค์ ๊ตฌํํ๊ณ ์์ต๋๋ค. ๊ฐ๊ฐ์ ์ปจํธ๋ํธ๊ฐ ํ ํฐ์ด ๊ตํ๋ ๋ ํธ์ถ๋๋ uniswapV3SwapCallback ๊ทธ๋ฆฌ๊ณ ์ ๋์ฑ์ด ๋ฑ๋ก๋ ๋ ํธ์ถ๋๋ uniswapV3MintCallback ์ด ๊ตฌํ๋์ด ์์ต๋๋ค.
Periphery ์ปจํธ๋ํธ ๋ํ Core ์ปจํธ๋ํธ์ ์ธก๋ฉด์์ ๋ณด์๋ฉด ๋ณ๋์ ์ปจํธ๋ํธ์ ๋๋ค. ๋ฐ๋ผ์, ์ ๋์ค์์ ํตํฉํ๋ค๋ ๊ฒ์ ์ฝ์ด ์ปจํธ๋ํธ๊ฐ ํ์๋ก ํ๋ ์ฝ๋ฐฑ์ ๊ตฌํํ๋ ๊ฒ์ด๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
๊ทธ๋ ๋ค๋ฉด, ์ฝ๋ฐฑ์ ํตํด์ ์ด๋ค ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์์๊น์? ์ ๋์ค์์ ์ฝ๋ฐฑ์ด ๋ค์ํ ๋ก์ง์ ๊ฐ์ง ์ ์๋๋ก ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ๊ณ ์์ต๋๋ค. ์์ ์ ํฌ๋ callback์ ์ธ ๋ฒ์งธ ์ธ์๋ฅผ ์ฌ์ฉํ์ง ์์ ์ด๋ฆ์ ์ง์ ํ์ง ์์์์ต๋๋ค.
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata // <<--- ์ด ๋ถ๋ถ
) external
์ธ๊ธํ์ง ์์์ง๋ง, ํด๋น ๋ถ๋ถ์ mint ํจ์๊ฐ ํธ์ถ๋ ๋ ๋ง์ง๋ง ์ธ์๋ฅผ ๊ทธ๋๋ก callback์ผ๋ก ๋๊ฒจ์ฃผ๋ ์ญํ ์ ํ๊ฒ ๋ฉ๋๋ค. bytes("")๋ก ๋น ๋ฐ์ดํฐ๋ฅผ ์ ์กํ์์ง์, ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฑ์ ์ ๋์ฑ ํ๋ก ์ ์กํ ๋ค์, ์ ๋์ฑ ํ์ด Periphery ์ปจํธ๋ํธ์ callback ํจ์๋ก ๋ณด๋ด๊ฒ ๋ฉ๋๋ค.
์ด๋ฌํ ๋ก์ง์ ๋ถ๋ฆฌ๋ ์ฝ์ด ์ปจํธ๋ํธ์ ์์กด์ฑ์ด ์ต์ํ๋ ์ํ์์ ํ ์คํธ ๊ฐ๋ฅ์ฑ, ๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์๊ฐ ๊ตฌ์ฑ ๊ฐ๋ฅํ ๊ฐ๋ฐ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
์์ ์์ฑํ ์ปจํธ๋ํธ ์ฝ๋๋ฅผ ์
๊ทธ๋ ์ด๋ํ๊ฒ ์ต๋๋ค. ์ปจํธ๋ํธ๊ฐ ๊ฐ์ง๊ณ ์๋ ํ ํฐ์ ์ ๋์ฑ ๊ณต๊ธ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ, ์ฌ์ฉ์์ ์ง๊ฐ์ ์๋ ํ ํฐ์ ์ ๋์ฑ ๊ณต๊ธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋๋ก ํด๋ณด๊ฒ ์ต๋๋ค. ๋น์ฐํ ์ฌ์ฉ์์ ํ ํฐ์ ์ ํฌ๊ฐ ์์ฑํ๋ ์ปจํธ๋ํธ ์ฝ๋์ approve๊ฐ ๋์ด ์์ด์ผ ํฉ๋๋ค.
(uint256 amount0, uint256 amount1) = pool.mint(
address(this),
lowerTick,
upperTick,
liquidity,
abi.encode(msg.sender) // ํด๋น ์ฝ๋์ ์ปจํธ๋ํธ๋ฅผ ์ด์ฉํ๊ณ ์๋ ์ง๊ฐ์ฃผ์๋ฅผ bytes๋ก ์ธ์ฝ๋ฉํ์ฌ ์ ๋ฌ
);
...
// Pool์์ ์ปจํธ๋ํธ๋ก ํธ์ถ๋๋ ํจ์,
// ํด๋น ํจ์์ ํธ์ถ์๋ Pool์ด๋ฉฐ ๋ฐ์ดํฐ๋ Pool์์ ์ ์ก๋ฉ๋๋ค.
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data // mint๋ฅผ ํตํด ์ ๋ฌํ data๊ฐ ์ด๊ณณ์ผ๋ก ๋์ด์ต๋๋ค.
) external {
address from = abi.decode(data, (address)); // ๋์ด์จ ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ์
์ผ๋ก ๋์ฝ๋ฉํฉ๋๋ค.
// ์ดํ์ ๋์ฝ๋ฉ ๋ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก, ์ฌ์ฉ์์ ์ง๊ฐ์์ ์ ๋์ฑ ํ๋ก ํ ํฐ์ ์ ์กํฉ๋๋ค.
if (amount0Owed != 0) token0.transferFrom(from, address(pool), amount0Owed);
if (amount1Owed != 0) token1.transferFrom(from, address(pool), amount1Owed);
}
์ฌ๊ธฐ์์ data๋ bytes ํ์
์ผ๋ก๋ง ์ ์ก๋๋ฏ๋ก, ์
๋ ฅ๋๋ ๋ฐ์ดํฐ ํ์
๊ณผ ์ฝ๋ฐฑ์์ ํด์๋์ด์ผ ํ๋ ๋ฐ์ดํฐ ํ์
์ด ์ผ์นํ ์ ์์ด์ผ ํฉ๋๋ค. ํ์ฌ์ ์ฝ๋์์๋ ๋์จํ๊ฒ ํ์
์ ๊ณต์ ํ๊ณ ์์ง๋ง, ๊ตฌ์กฐ์ฒด๋ฅผ ์ด์ฉํด์ ํ์
์ ๋ํ ์ค๋ง๋ฆฌ๋ฅผ ๊ณต์ ํ๋ ๊ฒ์ด ๋ ๋์ ๋ฐฉ๋ฒ์ผ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ์ ๋์ค์์ V3 ์ปจํธ๋ํธ๋ค์ด ๋ ๋จ์ ์ผ๋ก ๋์ํ๋ ๋ฐฉ์๋ณด๋ค, ๋ค๋ฅธ ์ปจํธ๋ํธ์ ๋ณด๋ค ๋ช ํํ๊ฒ ์๋ํ ์ ์๋๋ก ์ค๊ณ๊ฐ ๋์๋ค๋ ์ ์ ์๊ณ ์๋ค๋ฉด, ์ ๋์ค์์ ๋ณด๋ค ํธํ๊ฒ ํตํฉํ ์ ์์ ๊ฒ๋๋ค. ์ง๊ธ๊น์ง ์์ฑํ ์ ์ฒด ์ฝ๋๋ฅผ ํ์ธํด ๋ด ์๋ค.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@uniswap/v3-core/contracts/libraries/TickMath.sol";
import "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol";
import "@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol";
import "@beandao/contracts/interfaces/IERC20.sol";
/**
* @title UniswapV3Integration
* @author yoonsung.eth
* @notice ํ ํฐ์ ๋ ๊ฐ์ ํด๋นํ๋ ์ ๋์ฑ ํ์ ์์ฑํ์ฌ ์ด๊ธฐ ๊ฐ๊ฒฉ์ ๊ฒฐ์ ํ์ฌ ์ด๊ธฐํํฉ๋๋ค.
* ์ฌ์ฉ์๋ ํ ํฐ์ ํด๋น ์ปจํธ๋ํธ๋ฅผ ํตํ์ฌ ๋ฏธ๋ฆฌ ์ง์ ๋ ๊ฐ๊ฒฉ ๊ณต๊ฐ์ ์ ๋์ฑ์ ๊ณต๊ธํ์ฌ
* ์ฌ์ฉ์๋ค์ด NFT์์ด ์ ๋์ฑ์ ์ฆ๋ช
ํ ์ ์๊ฒ ๋์์ฃผ๋ ์ปจํธ๋ํธ์
๋๋ค.
* @dev ํ ํฐ๊ณผ ์ด๊ธฐ ๊ฐ๊ฒฉ ๊ณต๊ฐ์ ๋ํ ์ ๋ณด ๋ฐ ์์๋ฃ ์ ๋ณด๊ฐ constructor์ ์ปจํธ๋ํธ์ ์ ์๋์ด ์์ผ๋ฏ๋ก,
* ํด๋น ์ ๋ณด๋ค์ ์ปจํธ๋ํธ๊ฐ ๋ฐฐํฌ๋ ๋ ์ด๊ธฐํ ๋๋๋ก ํ๋ฉด ์ข ๋ ์ ์ฐํด์ง๋๋ค.
*/
contract UniswapV3Integration is IUniswapV3MintCallback {
address public constant UNIV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
uint24 constant fee = 3000;
int24 constant tickSpacing = 60;
uint256 constant PRECISION = 2**96;
IUniswapV3Factory v3Factory = IUniswapV3Factory(UNIV3_FACTORY);
IUniswapV3Pool pool;
IERC20 token0;
IERC20 token1;
int24 lowerTick;
int24 upperTick;
mapping(address => uint128) userLiquidity;
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
pool = IUniswapV3Pool(v3Factory.createPool(address(_token0), address(_token1), fee));
uint160 initialPriceX96 = encodeSqrtPrice(address(_token0), address(_token1), 1e18, 0.01 ether);
uint160 upperPriceX96 = encodeSqrtPrice(address(_token0), address(_token1), 1e18, 10 ether);
pool.initialize(initialPriceX96);
lowerTick = TickMath.getTickAtSqrtRatio(initialPriceX96) + tickSpacing;
upperTick = TickMath.getTickAtSqrtRatio(upperPriceX96);
if ((lowerTick % tickSpacing) != 0)
lowerTick = lowerTick - (lowerTick % tickSpacing) + (lowerTick < 0 ? -tickSpacing : tickSpacing);
if ((upperTick % tickSpacing) != 0)
upperTick = upperTick - (upperTick % tickSpacing) + (upperTick < 0 ? -tickSpacing : tickSpacing);
require(upperTick > lowerTick);
}
/**
* @notice ์ ๋์ฑ ํ์ ๋ํ ํ ํฐ ๋ ๊ฐ๋ฅผ ์์นํ๋ฉฐ, ์ฌ์ฉ์๋ ํด๋น ์ปจํธ๋ํธ์ ๋ํด ํ ํฐ ์ฌ์ฉ ๊ถํ์ ํ์ฉํด ์ฃผ์ด์ผ ํฉ๋๋ค.
* @param token0Amount token0 ์ ์์น ์๋
* @param token1Amount token1 ์ ์์น ์๋
*/
function deposit(uint256 token0Amount, uint256 token1Amount) external {
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(lowerTick),
TickMath.getSqrtRatioAtTick(upperTick),
token0Amount,
token1Amount
);
(uint256 amount0, uint256 amount1) = pool.mint(
address(this),
lowerTick,
upperTick,
liquidity,
abi.encode(msg.sender)
);
userLiquidity[msg.sender] += liquidity;
}
/**
* @notice ์ด๋ฏธ ์์น๋ ์ ๋์ฑ์ ์ ๊ฑฐํฉ๋๋ค.
* @param liquidity ์ ๊ฑฐํ ์ ๋์ฑ ์๋
*/
function withdraw(uint128 liquidity) external returns (uint256 amount0, uint256 amount1) {
userLiquidity[msg.sender] -= liquidity;
(amount0, amount1) = pool.burn(lowerTick, upperTick, liquidity);
(amount0, amount1) = pool.collect(msg.sender, lowerTick, upperTick, uint128(amount0), uint128(amount1));
}
// ์ฌ๊ธฐ์ ์ฌ์ฉ์๋ง๋ค ์์๋ฃ๋ฅผ ์ฒญ๊ตฌํ๋ ํจ์๋ฅผ ๊ตฌํํด๋ณด์ธ์!
/**
* @notice ์ ๋์ค์ ํ์ ์ ๋์ฑ ๊ณต๊ธํ ๋ ํ์์ ์์น๋๋ ์ฝ๋ฐฑ ํจ์, IUniswapV3MintCallback์ ์ํด ๊ตฌํ๋ฉ๋๋ค.
*/
function uniswapV3MintCallback(
uint256 amount0Owed,
uint256 amount1Owed,
bytes calldata data
) external {
address from = abi.decode(data, (address));
if (amount0Owed != 0) token0.transferFrom(from, address(pool), amount0Owed);
if (amount1Owed != 0) token1.transferFrom(from, address(pool), amount1Owed);
}
function encodeSqrtPrice(
address addr0,
address addr1,
uint256 reserve0,
uint256 reserve1
) internal pure returns (uint160 sqrtPriceX96) {
if (addr0 > addr1) {
sqrtPriceX96 = uint160(sqrt((reserve0 * PRECISION * PRECISION) / reserve1));
} else {
sqrtPriceX96 = uint160(sqrt((reserve1 * PRECISION * PRECISION) / reserve0));
}
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
์์ ์ฝ๋๋ฅผ ํตํฉํ์ฌ ๊ฐ๋ฐํ๋ ค๊ณ ๋ณด๋ฉด ๊ฐ์ฅ ์ปค๋ค๋ ๋ฌธ์ ์ ๋ด์ฐฉํ๊ฒ ๋ฉ๋๋ค. ๋ฐ๋ก ์ ๋์ค์์ ํตํฉํ ์ฝ๋๋ฅผ ์์ฑํ๋ ค๋ฉด, ์์กด์ฑ ๋ฌธ์ ๋ก ์ธํด ์๋ฆฌ๋ํฐ ๋ฒ์ ์ 0.7.6์ ๋ง์ถฐ์ผ ํ๋ค๋ ๊ฑฐ์ฃ .
์ ๋์ค์ ์์ฒด๋ ์ด๋ฏธ ๊ฐ ์ฒด์ธ์ ๋ฐฐํฌ๋ ์ํ์ด๊ธฐ ๋๋ฌธ์, ์ธํฐํ์ด์ค๋ฅผ ํตํด์ ์ฝ๋ฐฑ์ ์ฃผ๊ณ ๋ฐ์ ์๋ง ์๋ค๋ฉด ์ปจํธ๋ํธ๊ฐ ์ด๋ค ์ปดํ์ผ๋ฌ๋ก ์ปดํ์ผ ๋๋ ์ง ์๊ด์ด ์์ด์ผ ํฉ๋๋ค.
ํนํ๋, ์ต์ ๋ฒ์ ์ ์๋ฆฌ๋ํฐ ์ปดํ์ผ๋ฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์๋, ์์ฑ๋๋ ํฌ๊ณ ์์ ๋ฐ์ดํธ ์ฝ๋์ ๋ฒ๊ทธ๊ฐ ์ ๊ณ , ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฌธ๋ฒ๋ค์ด ๋๊ฑฐ ์ถ๊ฐ๋๊ธฐ๋ ํ์์ผ๋ฉฐ, ์๋ก์ด Yul IR์ด ์ ์ฉ๋์ด ์คํ๋๋ ์ฝ๋๊ฐ ๋ณด๋ค ์ต์ ํ๋๋ค๋ ์ ์ด ํน์ง์ ๋๋ค.
์ ํฌ๊ฐ ์ ๋์ค์์ ์๋กญ๊ฒ ๋ฐฐํฌํ ๊ฒ๋ ์๋๊ณ , ์ ๊ณต๋๋ ์ธํฐํ์ด์ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ ๊ธฐ๋ฐฐํฌ๋ ์ ๋์ค์์ ํธ์ถํ ์ ์์ผ๋ฉด ๋๊ธฐ์, ์ด๋ฐ ๊ฒ๋ค์ ๊ทนํ ์ฌ์ํ ๋ถ๋ถ์ด๋ผ๋ ์ ์ ๋๋ค. ๊ทธ๋์, ์ ๋์ค์ ์ธํฐํ์ด์ค๋ฅผ 0.8.11 ๋ฒ์ ์ด์์์ ์ฌ์ฉํ ์ ์๋ ์ฝ๋ ํจํค์ง๋ฅผ ๋ฐฐํฌํฉ๋๋ค. ๋ฒ์ ์ ์ฌ๋ฆฌ๊ณ , ๊ตฌ ๋ฒ์ ์ปดํ์ผ๋ฌ์์ ์ฌ์ฉ๋๋ ๋์จํ ํ์ ์ฒดํฌ๋ฅผ ์ข ๋ ํ์คํ๊ฒ ์์ ํ์์ต๋๋ค.
ํด๋น ํจํค์ง๋ฅผ ๋ํ๊ณ , import ๊ตฌ๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ด ๋ฐ๊ฟ์ฃผ๋ฉด ๋ฉ๋๋ค.
pragma solidity ^0.8.0;
import "UniswapV3Pack/v3-core/libraries/TickMath.sol";
import "UniswapV3Pack/v3-core/interfaces/IUniswapV3Factory.sol";
import "UniswapV3Pack/v3-core/interfaces/IUniswapV3Pool.sol";
import "UniswapV3Pack/v3-core/interfaces/callback/IUniswapV3MintCallback.sol";
import "UniswapV3Pack/v3-periphery/libraries/LiquidityAmounts.sol";
์ด ๊ธ์ ์ ๋ง ํ์์ ์ํด ์ฐ์์ต๋๋ค. ์ด ๊ธ ์ ์ฒด์ ๋ํ๋ bean ํ ํฐ์ ์ ๋์ฑ ๊ณต๊ธํ๋ ๊ณผ์ ์, ์ค์ ๋ก ์ ๊ฐ ํ์๋ก ํ๋ ๊ฐ๊ฒฉ ๋ฒ์์์ ์ ๋์ฑ์ ๊ณต๊ธํ๊ธฐ ์ํ ๊ณผ์ ๊ณผ ๋์ผํฉ๋๋ค. ์ํ์ ํฌํ๊ถ์ ์ ๋ํ ์ํค๋ฉด์ ์คํ ์ดํน ๋์ง ์์ ๊ฑฐ๋ฒ๋์ค ํ ํฐ์ ๊ฐ์น๋ฅผ ์ต์ ํ ๋๊ตฌ๋ก์จ ํ์์ ์ด๋ผ ์๊ฐํ์ต๋๋ค. ๋๋ถ์ด ํ ํฐ์ ์ ๋์ฑ์ด ๊ทน๋๋ก ๋ฎ๋ค ๋ณด๋ ๊ฑฐ๋ฒ๋์ค ํ ํฐ์ ๋ํ ๊ฐ์น ํ๊ฐ๊ฐ ์ ๋๋ก ์ด๋ค์ง ์ ์๋๋ฐ, ์ ๋์ฑ์ด ํ๋ถํ ํ๋กํ ์ฝ์ ์ ๋์ฑ์ ์ง์ค์์ผ ๊ฐ์น ํ๊ฐ๊ฐ ์์ํ๊ฒ ์ด๋ค์ง ์ ์๋๋ก ๊ตฌ์ฑํ ์ ์์ด์ผ ํ์ต๋๋ค.
์ด๋ฌํ ๋๊ตฌ๋ก์จ ์ ๋์ค์ V3์ ์ต๊ณ ์ ๋๊ตฌ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํฉํ๋ ๊ณผ์ ์ ๋๋ฌด ๊ณ ํต์ค๋ฌ์ ์ต๋๋ค. ์ ๋์ค์์ด ๋ฐ์ ๋๋ฉด์, ์ดํดํ ์ ์๋ค๋ฉด ์ฌ์ฉํ ์ ์๋ ์ ํ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , ๋๋ถ๋ถ์ ์ฐ์ฐ์ off-chain ์์ญ์์ ์ฒ๋ฆฌํ๊ณ on-chain์ ํตํด ๊ฒ์ฆํ๋ ๋ฐฉ์์ผ๋ก ์๋ํ๊ณ ์๊ธฐ์, ์ฝ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ฐ๊ฒฐ์ํค๋ ๋ถ๋ถ์์๋ ์ด๋ ค์์ ๊ฒช์ ์๋ฐ์ ์์์ต๋๋ค.
๊ทธ๋ฌ๋ ์๊ฐ์ ๋ง์ ๊ฒ์ ํด๊ฒฐํ๋ ๋ฐ ๋์์ ์ค๋๋ค. ์ฝ ๋ ๋ฌ๊ฐ ์ ์ ์์ด ํ์ ํ๋ ๋์์ ํตํฉํ๋ ๊ณผ์ ์ ๊ฒช์๋๋ ๊ฝค ์ ๋์ค์์ด ํธํ๊ฒ ๋ค๊ฐ์๊ธฐ์, ์ดํ์ ์ฌ๋๋ค์ ์ ๊ฐ ๊ฒช์ ๊ณ ํต์ค๋ฌ์ด ๊ณผ์ ์ ๊ทธ๋๋ก ๋ฐ๋ฅด์ง ์๊ณ ์ ๋์ค์์ ์ข ๋ ์ ์ดํดํ๋ ๋ง์์ผ๋ก ์ด ๊ธ์ ์ ์์ต๋๋ค.
๋ถ๋ ์ด ๊ธ๊ณผ ์๋ฃ๋ค์ด ๋์์ด ๋ง์ด ๋๊ธธ ๋ฐ๋ผ๋ฉด์, ์ ๋์ค์์ ํตํฉํ ๋ค์ํ ํ๋กํ ์ฝ๋ค์ด ๋์ค๊ธฐ๋ฅผ ๊ธฐ๋ํด ๋ด ๋๋ค.
์ด ๊ธ์์ ๋ํ๋๋ ๋ชจ๋ ๋ด์ฉ์ ์ธ์ ๋ ๋ณ๊ฒฝ๋ ์ ์์ผ๋ฉฐ, ์ฝ๋๋ ์ํํธ์จ์ด์ ์ ์๋ ์๋์ ๋ณด์ฅํ์ง ์์ต๋๋ค. ๋ชจ๋ ์ธ๊ธ๋ ์ ๋ณด๋ ๊ต์ก ๋ชฉ์ ์ผ ๋ฟ์ด๋ฉฐ ํฌ์ ์กฐ์ธ์ผ๋ก ๋ฐ์๋ค์ฌ์๋ ์๋ฉ๋๋ค.