유니스왑은 DEX(Decentralized EXchange) 의 하나로, 탈중앙화 거래소중 하나이다.
DEX 순위를 보면, 아래 사진과 같이 유니스왑이 대다수를 차지하고 있다.
점유율을 보면 사실상 1위.
유니스왑은 현재 V3 까지 출시되었으며, V4 의 코어 개발을 마쳤으며, Audit 만 남아있다고 한다.
유니스왑은 스마트 컨트랙트만으로 동작하는데, 유저들이 Pool 을 구성하고
구성된 Pool 에 유동성을 공급한 유저들에게는 지속적으로 거래 수수료를 지급.
동시에, uniswap 이라는 LP 토큰을 받아갈 수 있다.
따라서 예치한만큼의 금액이 묶이는 것이 아니고 LP 토큰을 거래하여 LP 토큰만큼의 자금을 그대로 운용할 수 있는 것이다.
유니스왑의 풀은 CA 이다. 그리고 CA 에는 두개의 토큰이 예치되어 스왑에 사용된다.
CA 에 예치된 두개 토큰의 비율이 가격을 의미한다.
예를 들어, 스테이블 코인과 이더리움으로 구성된 풀이 있다고 가정했을 때.
10000 스테이블 코인 / 50 이더리움
만큼으로 풀이 구성되어있다면,
1 이더리움은 2000 스테이블 코인만큼의 가치를 가진다.
1 스테이블 코인은 1달러의 가치를 유지하므로, 1 이더리움은 $2000 이다.
따라서 예치된 두개의 토큰량을 가져온다면, 각 토큰의 가격을 알 수 있다.
먼저, uniswap pool CA 를 가져와야한다.
이미 이더스캔과 같은 사이트에는 각 pool CA 에 대한 정보가 등록되어 있다.
하지만 우리가 사용할 네트워크는 goerli 테스트넷이므로.. CA 부터 찾아본다.
아래의 컨트랙트를 이용하면, CA 값을 받아올 수 있다.
먼저, uniswap v3-core 의 IUniswapV3Factory.sol 을 import 한다.
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
interface 를 선언하고 사용할 함수를 적는다. (getPool 함수)
interface UniswapV3Factory { function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool); }
컨트랙트를 선언하고 유니스왑v3 의 Factory contract address 를 선언해준다.
address public factoryAddress; // Uniswap V3 Factory contract address
컨스트럭터를 선언하고, 팩토리 컨트랙트 주소를 input 으로 받는다.
팩토리 컨트랙트 주소는 Uniswap Contract Deployments 여기서 확인할 수 있다.
constructor(address _factoryAddress) {
factoryAddress = _factoryAddress;
}
마지막으로, 풀의 주소를 가져오는 함수를 선언해주면 된다.
이 함수에는 a토큰 CA, b토큰 CA, pool 의 스왑 수수료
3가지가 input 으로 들어간다.
function getPoolInfo(address tokenA, address tokenB, uint24 fee) external view returns (address) {
IUniswapV3Factory factory = IUniswapV3Factory(factoryAddress);
address pool = factory.getPool(tokenA, tokenB, fee);
return pool;
}
작성된 컨트랙트는 아래와 같다.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
```import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";```
interface UniswapV3Factory {
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
}
contract getPool_Address {
address public factoryAddress; // Uniswap V3 Factory contract address
constructor(address _factoryAddress) {
factoryAddress = _factoryAddress;
}
function getPoolInfo(address tokenA, address tokenB, uint24 fee) external view returns (address) {
IUniswapV3Factory factory = IUniswapV3Factory(factoryAddress);
address pool = factory.getPool(tokenA, tokenB, fee);
return pool;
}
}
컨트랙트를 테스트넷에 배포해서 확인해보았다.
먼저 컨스트럭터에 팩토리 컨트랙트 주소를 넣어야 하므로 위의 링크에 들어가 확인한다.
배포한 컨트랙트에서, 위에서 설명한 3개의 input 을 넣고 확인한다.
A 는 WETH. B 는 DAI. fee 는 0.3% 이다.
반환받은 주소 값을 사용해서 다음 단계로...
위에서 반환받은 풀의 주소를 이용해서, 풀에 예치된 두개 토큰의 수량을 확인해보았다.
먼저, ERC20, V3Pool 컨트랙트 파일을 import 한다.
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
풀의 주소를 입력받을 상태변수를 선언하고 컨스트럭터로 배포 시에 주소 값을 받도록 한다.
contract getBalance {
address public poolAddress; // WETH/DAI 페어의 풀 컨트랙트 주소
constructor(address _poolAddress) {
poolAddress = _poolAddress;
}
입력한 풀 주소의 토큰 0, 1번의 예치량을 각각 가져와 지역변수에 담아준다.
그리고 지역변수를 return.
function getPoolBalances() public view returns (uint256 DAI_Balance, uint256 WETH_Balance) {
IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
address WETH_Token = pool.token0();
IERC20 WETH = IERC20(WETH_Token);
WETH_Balance = WETH.balanceOf(poolAddress);
// WETH 예치량 가져오기
address DAI_Token = pool.token1();
IERC20 DAI = IERC20(DAI_Token);
DAI_Balance = DAI.balanceOf(poolAddress);
// DAI 예치량 가져오기
return (WETH_Balance, DAI_Balance);
}
}
여기서 address WETH_Token = pool.token0();
코드를 보면,
풀 컨트랙트 내부에는 WETH 토큰이 0번 토큰으로 설정되있는 것을 알 수 있다.
그러므로, 위에서 풀의 주소 값을 가져올 때 처럼 WETH, DAI 토큰의 CA 를 입력할 필요가 없다.
아래는 전체 컨트랙트 코드이다.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract getBalance {
address public poolAddress; // WETH/DAI 페어의 풀 컨트랙트 주소
constructor(address _poolAddress) {
poolAddress = _poolAddress;
}
function getPoolBalances() public view returns (uint256 DAI_Balance, uint256 WETH_Balance) {
IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
// WETH 예치량 가져오기
address WETH_Token = pool.token0();
IERC20 WETH = IERC20(WETH_Token);
WETH_Balance = WETH.balanceOf(poolAddress);
// DAI 예치량 가져오기
address DAI_Token = pool.token1();
IERC20 DAI = IERC20(DAI_Token);
DAI_Balance = DAI.balanceOf(poolAddress);
return (WETH_Balance, DAI_Balance);
}
}
위에서 반환받은 풀 CA 를 컨스트럭터로 입력하고 테스트넷에 배포한 후, 함수를 실행해본다.
풀 CA 안에 예치된 토큰의 양을 반환한다.
이더스캔에서 실제 CA 에 예치된 양이 맞는지 확인해본다.
1,377,272 DAI
4.585 ETH
반환받은 값이 맞는 걸 확인할 수 있다.
그렇다면, 이 풀에서 1 ETH 의 가격은 얼마일까?
DAI 예치량 / ETH 예치량 = 1 ETH 가격
대략 $ 300,386
이다...
실제 이더리움의 가격과는 상당히 괴리감이 있지만, Goerli 테스트넷의 이더리움은 24시간마다 0.02 개만 받아갈 수 있으므로 위의 가격이 형성되는 것으로 보인다.
실제 메인넷에 배포했다면, 더 많은 토큰의 풀에 접근할 수 있었을 것이다.
아쉽게도 테스트넷으로 프로젝트를 진행하므로, 다음번에...
대체재로 체인링크의 오라클을 이용해서 데이터를 가져온다면, 더 많은 데이터를 가져올 수 있을 것 같다.
다음은 체인링크로..