Skip to content

Commit deb97f9

Browse files
committed
article: Uniswap v3 fork guide
1 parent 51505ab commit deb97f9

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
<summary>Guides</summary>
122122

123123
- [Uniswap-v2](./guides/uniswap-v2/README.md)
124+
- [Uniswap-v3](./guides/uniswap-v3/README.md)
124125
</details>
125126
- <details>
126127
<summary>EIPs</summary>

guides/uniswap-v3/README.md

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
# Uniswap-v3 smart contracts fork
2+
3+
**Автор:** [Алексей Куценко](https://github.com/bimkon144) 👨‍💻
4+
5+
В этом гайде мы покажем, как быстро и просто форкнуть Uniswap V3.
6+
7+
## Базовые знания
8+
9+
Есть два основных репозитория:
10+
11+
- [uniswap-v3-core](https://github.com/Uniswap/v3-core).
12+
- [uniswap-v3-periphery](https://github.com/Uniswap/v3-periphery).
13+
14+
### uniswap-v3-core
15+
16+
Репозиторий uniswap-v3-core содержит ключевые смарт-контракты: `UniswapV3Factory.sol`, `UniswapV3Pool.sol`, `UniswapV3PoolDeployer.sol`, которые реализуют основной функционал протокола Uniswap V3. Эти смарт-контракты реализуют механизм концентрированной ликвидности для обмена токенами.
17+
18+
Схема деплоя нового пула выглядит так:
19+
![alt text](./images/core.png)
20+
21+
`UniswapV3Factory.sol` отвечает за создание и управление пулами обмена с различными параметрами комиссий. Смарт-контракт фабрики отслеживает все существующие пулы и их адреса. Когда создается новый пул токенов, фабрика разворачивает новый смарт-контракт `UniswapV3Pool.sol` через вспомогательный контракт `UniswapV3PoolDeployer.sol`.
22+
23+
### uniswap-v3-periphery
24+
25+
Репозиторий uniswap-v3-periphery содержит вспомогательные смарт-контракты: `NonfungiblePositionManager.sol`, `NonfungibleTokenPositionDescriptor.sol`, `SwapRouter.sol`, и библиотеки: `PoolAddress.sol`, `LiquidityAmounts.sol`, и другие. Они взаимодействуют с основными смарт-контрактами из uniswap-v3-core. Эти смарт-контракты облегчают взаимодействие с протоколом для пользователей и разработчиков.
26+
27+
Необходимо задеплоить:
28+
29+
- `SwapRouter.sol`. Предоставляет интерфейс для обмена токенов через пулы Uniswap V3.
30+
- `NonfungiblePositionManager.sol`. Управляет позициями ликвидности в виде NFT токенов.
31+
- `NonfungibleTokenPositionDescriptor.sol`. Предоставляет метаданные для NFT токенов позиций.
32+
- `QuoterV2.sol`. Позволяет получать предварительную информацию о свопах.
33+
- `UniswapInterfaceMulticall.sol`. Обеспечивает агрегацию нескольких вызовов в одну транзакцию.
34+
35+
_Важно!_ `Multicall2.sol` контракт тоже нужно задеплоить. Он не относится к uniswap-v-3-periphery репозиторию. Он лежит в отдельном [репозитории](https://github.com/makerdao/multicall/blob/master/src/Multicall2.sol). Обеспечивает выполнение нескольких вызовов для чтения данных в одном запросе.
36+
37+
Для решения нашей задачи потребуется несколько шагов - подготовка кодовой базы, настройка смарт-контрактов и деплой смарт-контрактов.
38+
39+
## Подготовка кодовой базы
40+
41+
_Убедитесь что [Node.js](https://nodejs.org/en/download/) и [npm](https://www.npmjs.com/get-npm) установлены, проверить можно через `node --version` и `npm --version`._
42+
43+
Чуть позже мы также будем использовать Hardhat и его плагин для деплоя - Hardhat ignition.
44+
45+
На данном этапе мы инициализируем наш проект и настроим его для работы с Uniswap V3.
46+
47+
1) Создадим новый проект и инициализируем его:
48+
```bash
49+
mkdir UNISWAP-V3-FORK-SC && cd ./UNISWAP-V3-FORK-SC
50+
```
51+
52+
2) Инициализируем Hardhat проект:
53+
```bash
54+
# Инициализация проекта
55+
npx hardhat init
56+
```
57+
58+
Выберите "Create a TypeScript project".
59+
Соглашаемся на все вопросы.
60+
61+
3) Установим необходимые зависимости:
62+
```bash
63+
# Устанавливаем плагины Hardhat
64+
npm install --save-dev @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-verify dotenv
65+
66+
# Устанавливаем библиотеки Uniswap и зависимости
67+
npm install @uniswap/lib @uniswap/v2-core @uniswap/v3-core @uniswap/v3-periphery base64-sol
68+
```
69+
70+
4) Создадим структуру каталогов и скопируем Uniswap контракты:
71+
72+
_Копируем таким способом, потому что в папке аудита есть ошибки которые не позволяют контрактам скомпилироваться._
73+
74+
_Таким образом, скопировать код можете любым удобным способом, отличным от нашего, даже вручную._
75+
```bash
76+
# Создаем структуру директорий
77+
mkdir -p src/v3-core src/v3-periphery
78+
79+
# Клонируем репозитории Uniswap V3 во временные директории
80+
git clone https://github.com/Uniswap/v3-core.git temp-v3-core
81+
git clone https://github.com/Uniswap/v3-periphery.git temp-v3-periphery
82+
83+
# Копируем контракты из временных директорий в нашу структуру
84+
cp -r temp-v3-core/contracts/* src/v3-core/
85+
cp -r temp-v3-periphery/contracts/* src/v3-periphery/
86+
87+
# Удаляем временные директории
88+
rm -rf temp-v3-core temp-v3-periphery
89+
90+
# Создаем Multicall2 контракт
91+
curl -o src/Multicall2.sol https://raw.githubusercontent.com/makerdao/multicall/master/src/Multicall2.sol
92+
93+
# Создаем WETH контракт для тестнета. Для Мейннета вам нужно будет указать этот адрес в .env.
94+
95+
curl -o src/WETH9.sol https://raw.githubusercontent.com/gnosis/canonical-weth/master/contracts/WETH9.sol
96+
```
97+
98+
5) Настроим файл `hardhat.config.ts`:
99+
```typescript
100+
import { HardhatUserConfig } from "hardhat/config";
101+
import "@nomicfoundation/hardhat-toolbox";
102+
import "@nomicfoundation/hardhat-ignition-ethers";
103+
import "@nomicfoundation/hardhat-verify";
104+
import 'dotenv/config';
105+
106+
const PRIVATE_KEY= process.env.PRIVATE_KEY as string;
107+
108+
const AMOY_RPC_URL = process.env.AMOY_RPC_URL;
109+
110+
const POLYGON_API_KEY = process.env.POLYGON_API_KEY || '';
111+
112+
const config: HardhatUserConfig = {
113+
solidity: {
114+
compilers: [
115+
{
116+
version: "0.7.6",
117+
settings: {
118+
optimizer: {
119+
enabled: true,
120+
runs: 200,
121+
details: {
122+
yul: true
123+
}
124+
},
125+
viaIR : false,
126+
},
127+
},
128+
{
129+
version: "0.4.22"
130+
}
131+
]
132+
},
133+
paths: {
134+
sources: "./src"
135+
},
136+
networks: {
137+
amoy: {
138+
url: AMOY_RPC_URL,
139+
accounts: [PRIVATE_KEY],
140+
}
141+
},
142+
etherscan: {
143+
apiKey: {
144+
amoy: POLYGON_API_KEY
145+
},
146+
customChains: [{
147+
network: "amoy",
148+
chainId: 80002,
149+
urls: {
150+
apiURL: "https://api-amoy.polygonscan.com/api",
151+
browserURL: "https://amoy.polygonscan.com"
152+
}
153+
}]
154+
}
155+
};
156+
157+
export default config;
158+
```
159+
160+
6) Создадим файл .env - `cp .env.example .env` и заполним поля:
161+
162+
```
163+
PRIVATE_KEY="deployer_private_key"
164+
165+
AMOY_RPC_URL="RPC_URL"
166+
167+
POLYGON_API_KEY="API_KEY"
168+
WETH_ADDRESS=""
169+
```
170+
171+
`WETH_ADDRESS` - адрес WETH токена для тестнета не указываем, т.к наш следующий скрипт задеплоит его.
172+
173+
Для мейннета, вам нужно будет добавить требуемую сеть в `hardhat.config.ts` и `.env` а также указать `WETH_ADDRESS` в этой сети.
174+
175+
7) Создадим ignition modules для деплоя
176+
177+
Для этого в папке `ignition/modules/` создадим модуль который будет обеспечивать деплой контрактов в требуемой последовательности:
178+
179+
```typescript
180+
//UniswapV3.ts файл
181+
182+
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
183+
import 'dotenv/config';
184+
185+
export default buildModule('UniswapV3', (m) => {
186+
// Получаем WETH адрес из переменной окружения или деплоим новый, если не указан
187+
const wethAddress = process.env.WETH_ADDRESS;
188+
const weth = wethAddress
189+
? m.contractAt("WETH9", wethAddress)
190+
: m.contract("WETH9");
191+
192+
// Деплоим фабрику
193+
const uniswapV3Factory = m.contract("UniswapV3Factory");
194+
195+
// Деплоим свап роутер
196+
const swapRouter = m.contract("SwapRouter", [uniswapV3Factory, weth], {
197+
after: [weth, uniswapV3Factory],
198+
});
199+
200+
// Деплоим библиотеку для NFT-дескриптора
201+
const NFTDescriptor = m.library("NFTDescriptor");
202+
const nativeCurrencyLabelBytes = '0x46554e4e594d4f4e455900000000000000000000000000000000000000000000'; // cast format-bytes32-string "FUNNYMONEY"
203+
// Эти байты представляют название нативной валюты сети в формате bytes32.
204+
// "FUNNYMONEY" - тестовое название, которое нужно заменить на имя реальной валюты вашей сети (например, "ETH" для Ethereum).
205+
206+
// Деплоим дескриптор для NFT позиций
207+
const nonfungibleTokenPositionDescriptor = m.contract("NonfungibleTokenPositionDescriptor", [weth, nativeCurrencyLabelBytes], {
208+
after: [weth],
209+
libraries: {
210+
NFTDescriptor: NFTDescriptor,
211+
}
212+
});
213+
214+
// Деплоим менеджер NFT позиций
215+
const nonfungibleTokenPositionManager = m.contract("NonfungiblePositionManager", [uniswapV3Factory, weth, nonfungibleTokenPositionDescriptor], {
216+
after: [uniswapV3Factory, weth, nonfungibleTokenPositionDescriptor]
217+
});
218+
219+
// Деплоим QuoterV2 для получения расчетных цен
220+
const quoterV2 = m.contract("QuoterV2", [uniswapV3Factory, weth], {
221+
after: [uniswapV3Factory, weth],
222+
})
223+
224+
// Деплоим интерфейс для мультивызовов
225+
const uniswapInterfaceMulticall = m.contract("UniswapInterfaceMulticall");
226+
227+
// Деплоим стандартный Multicall2
228+
const multicall2 = m.contract("Multicall2");
229+
230+
return {
231+
weth,
232+
uniswapV3Factory,
233+
swapRouter,
234+
nonfungibleTokenPositionDescriptor,
235+
nonfungibleTokenPositionManager,
236+
quoterV2,
237+
uniswapInterfaceMulticall,
238+
multicall2
239+
};
240+
});
241+
```
242+
243+
## Настройка смарт-контрактов
244+
245+
Для правильной работы Uniswap V3 необходимо рассчитать хеш байткода смарт-контракта UniswapV3Pool и заменить его в библиотеке PoolAddress.sol.
246+
247+
1) Создаем скрипт для расчета хеша:
248+
249+
```javascript
250+
// computeInitCodeHash.js файл
251+
252+
const { ethers } = require('ethers');
253+
const fs = require('fs');
254+
const path = require('path');
255+
256+
const jsonFilePath = path.resolve(__dirname, './artifacts/src/v3-core/UniswapV3Pool.sol/UniswapV3Pool.json');
257+
258+
// Асинхронная функция для чтения ABI JSON файла и вычисления INIT_CODE_HASH
259+
async function computeInitCodeHash() {
260+
try {
261+
// Чтение ABI JSON файла
262+
const contractJson = JSON.parse(fs.readFileSync(jsonFilePath, 'utf8'));
263+
264+
// Проверка наличия байткода в ABI JSON файле
265+
if (!contractJson.bytecode) {
266+
throw new Error('Байткод не найден в ABI JSON файле.');
267+
}
268+
269+
// Вычисление INIT_CODE_HASH с использованием байткода контракта
270+
const computedInitCodeHash = ethers.keccak256(contractJson.bytecode);
271+
272+
// Вывод результата
273+
console.log('INIT_CODE_HASH:', computedInitCodeHash);
274+
return computedInitCodeHash;
275+
} catch (error) {
276+
console.error('Ошибка при вычислении INIT_CODE_HASH:', error);
277+
}
278+
}
279+
280+
// Вызов функции
281+
computeInitCodeHash();
282+
```
283+
284+
2) Компилируем смарт-контракты:
285+
```bash
286+
npx hardhat compile
287+
```
288+
289+
3) Создайте файл .env заполняем его своими данными:
290+
```
291+
PRIVATE_KEY="ваш_приватный_ключ"
292+
AMOY_RPC_URL="amouy_RPC_URL"
293+
POLYGON_API_KEY="ваш_api_ключ_polygonscan"
294+
WETH_ADDRESS="Адрес_Если_Мейн_нет"
295+
```
296+
297+
4) Запускаем скрипт для расчета хеша:
298+
```bash
299+
node computeInitCodeHash.js
300+
```
301+
302+
5) Полученный хеш необходимо заменить в файле src/v3-periphery/libraries/PoolAddress.sol:
303+
```solidity
304+
bytes32 internal constant POOL_INIT_CODE_HASH = ПолученныйХеш;
305+
```
306+
307+
## Деплой смарт-контрактов
308+
309+
Теперь мы готовы к деплою смарт-контрактов.
310+
311+
1) Компилируем смарт-контракты:
312+
```bash
313+
npx hardhat compile
314+
```
315+
316+
3) Деплоим основные смарт-контракты Uniswap V3:
317+
```bash
318+
npx hardhat ignition deploy ignition/modules/UniswapV3.ts --network amoy --deployment-id amoy --verify
319+
```
320+
321+
После завершения деплоя, адреса смарт-контрактов будут доступны в директории `/ignition/deployments/[network_name]/deployed_addresses.json`.
322+
323+
_Важно!_ Ignitian плагин иногда сбоит, поэтому, если у вас вылезает проблема с nonces, просто запускайте его пока он на задеплоит все контракты. Умный плагин проверит, какие контракты не задеплоины и не верифицированы и все сделает.
324+
325+
## Заключение
326+
327+
Форк и деплой Uniswap V3 смарт-контрактов может показаться сложной задачей, но с правильными инструментами и подходом это становится гораздо более доступным. В этом гайде мы использовали Hardhat Ignition - современный инструмент, который значительно упрощает процесс деплоя взаимосвязанных смарт-контрактов.
328+
329+
Hardhat Ignition предоставляет следующие преимущества:
330+
- Возможность описать всю цепочку зависимостей между контрактами в одном модуле
331+
- Автоматизация развертывания комплексной системы контрактов одной командой
332+
- Встроенная проверка состояния деплоя и возможность его возобновления после сбоев
333+
- Интеграция с системой верификации контрактов на Etherscan
334+
335+
Важным аспектом работы с Uniswap V3 является правильное вычисление `POOL_INIT_CODE_HASH`, необходимого для корректного расчета адресов создаваемых пулов без изменения оригинальных смарт-контрактов. Мы рассмотрели, как это сделать с помощью JavaScript-скрипта.
336+
337+
Если вам интересно, то мы для вас подготовили [готовый](https://github.com/fullstack-development/Uniswap-v3-Fork-SC/tree/main) репозиторий для деплоя.
338+
339+
## Ссылки:
340+
341+
- [Официальная документация Uniswap V3](https://docs.uniswap.org/contracts/v3/overview)
342+
- [Книга по Uniswap V3](https://uniswapv3book.com/)
343+
- [Github - Uniswap V3 Core](https://github.com/Uniswap/v3-core)
344+
- [Github - Uniswap V3 Periphery](https://github.com/Uniswap/v3-periphery)
345+
- [Hardhat](https://hardhat.org/)
346+
- [Hardhat Ignition](https://hardhat.org/ignition/docs/getting-started)
347+
- [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts)
348+
- [Multicall2 смарт-контракт](https://github.com/makerdao/multicall)
349+
- [API ключи для Polygon Amoy](https://amoy.polygonscan.com/)

guides/uniswap-v3/images/core.png

59.1 KB
Loading

0 commit comments

Comments
 (0)