Как работает TWAP в Uniswap V2. Часть 2
В нашем примере выше мы смотрели цены только за последние 24 часа, но что если вам важны цены на токены за последний час, неделю или другой интервал времени?
Uniswap, понятное, не может хранить все данные о ценах, которые могут однажды быть кому-то интересны. Не удобно также постоянно делать snapshot цен и сохранять их в контракте, так как кому-то все же нужно было бы оплачивать стоимость транзакции за это.
Решение Uniswap заключается в том, что он хранит только числитель значений - каждый раз, когда происходит изменение коэффициента ликвидности (вызывается mint, burn, swap или sync), он записывает новую цену и то, как долго держалась предыдущая цена.
Переменные price0Cumulativelast и price1CumulativeLast на скрине выше являются общедоступными, поэтому заинтересованные пользователи сами должны делать snapshot при необходимости.
Единственное, что нужно отметить это: price0CumulativeLast и price1CumulativeLast обновляются только на строках 79 и 80 в коде выше (оранжевый круг), и могут только увеличиваться, пока не переполнятся (overflow). Нет никакого механизма, заставляющего их "уменьшаться". Они всегда увеличиваются при каждом вызове _update, который и происходит после swap, mint или burn. Это означает, что эти переменные только увеличивают цены с момента запуска пула.
#uniswap #v2 #twap
В нашем примере выше мы смотрели цены только за последние 24 часа, но что если вам важны цены на токены за последний час, неделю или другой интервал времени?
Uniswap, понятное, не может хранить все данные о ценах, которые могут однажды быть кому-то интересны. Не удобно также постоянно делать snapshot цен и сохранять их в контракте, так как кому-то все же нужно было бы оплачивать стоимость транзакции за это.
Решение Uniswap заключается в том, что он хранит только числитель значений - каждый раз, когда происходит изменение коэффициента ликвидности (вызывается mint, burn, swap или sync), он записывает новую цену и то, как долго держалась предыдущая цена.
Переменные price0Cumulativelast и price1CumulativeLast на скрине выше являются общедоступными, поэтому заинтересованные пользователи сами должны делать snapshot при необходимости.
Единственное, что нужно отметить это: price0CumulativeLast и price1CumulativeLast обновляются только на строках 79 и 80 в коде выше (оранжевый круг), и могут только увеличиваться, пока не переполнятся (overflow). Нет никакого механизма, заставляющего их "уменьшаться". Они всегда увеличиваются при каждом вызове _update, который и происходит после swap, mint или burn. Это означает, что эти переменные только увеличивают цены с момента запуска пула.
#uniswap #v2 #twap
❤3🔥1👌1
Как работает TWAP в Uniswap V2. Часть 3
Сегодня заканчиваем говорить о TWAP и посмотрим на чуть более сложные математические примеры. И для начала поговорим о том, почему TWAP должен отслеживать два ратио. Вернемся немного к первому посту.
Цена A по отношению к B - это просто A/B и наоборот. Например, если у нас есть 2000 USDC в пуле (без учета десятичных дробей) и 1 Ether, то цена 1 Ether равна просто 2000 USDC / 1 ETH.
Таким образом, цена USDC, выраженная в ETH, - это просто это число с перевернутыми числителем и знаменателем.
Однако мы не можем просто "перевернуть" одну из цен, чтобы получить другую, когда мы накапливаем цены (не уверен в точном переводе accumulating pricing в данном случае).
Рассмотрим следующее. Если наш накопитель цен начинается с 2 и прибавляет 3, мы не можем просто сделать так:
1 / 2+3 != 1/2 + 1/3
Однако цены все еще "в некоторой степени симметричны", поэтому выбор арифметического представления с фиксированной точкой должен иметь одинаковую емкость для целых и для десятичных чисел.
Если Eth в 1 000 раз "ценнее", чем USDC, то USDC в 1 000 раз "менее ценен", чем USDC. И чтобы хранить эти значения более точно, число с фиксированной точкой должно иметь одинаковый размер по обе стороны decimals, поэтому Uniswap выбрал u112x112 (объяснение в первом посте).
Далее пара слов о значении PriceCumulativeLast, которое всегда увеличивается, пока не переполнится, а затем снова продолжает рост.
Uniswap V2 был создан до версии Solidity 0.8.0, поэтому арифметика по умолчанию могла привести к overflow/underflow. Корректные современные реализации ценового оракула должны использовать блок unchecked, чтобы все работало как положено.
В конце концов, значение priceAccumulators и временная метка блока будет переполняться в контракте. В этом случае предыдущий резерв будет выше нового. Когда оракул вычислит изменение цены, он получит отрицательное значение. Но это не имеет значения в силу правил модульной арифметики.
Чтобы упростить задачу, давайте представим целые числа, которые переполняются при 100.
Мы фиксируем priceAccumulator на 80, а через несколько транзакций/блоков priceAccumulator поднимается до 110, но переполняется до 10. Мы вычитаем 80 из 10, что дает -70. Но значение хранится как беззнаковое целое, поэтому оно дает -70 mod(100), что равно 30. Это тот же результат, который мы ожидали бы, если бы переполнения не было (110-80=30).
Это справедливо для всех границ переполнения, а не только для 100 в нашем примере.
То же самое произойдет, если мы переполним временную метку. Поскольку для ее представления мы используем uint32, отрицательных чисел не будет. Опять же, предположим, что для простоты мы переполнимся на 100. Если мы делаем снимок в момент времени 98 и обращаемся к оракулу цен в момент времени 4, то прошло 6 секунд. 4 - 98 % 100 = 6, как и ожидалось.
Теперь вы знаете о TWAP чуточку больше. Далее мы продолжим говорить о Uniswap V2, V3 и других DeFi протоколах.
#uniswap #v2 #twap
Сегодня заканчиваем говорить о TWAP и посмотрим на чуть более сложные математические примеры. И для начала поговорим о том, почему TWAP должен отслеживать два ратио. Вернемся немного к первому посту.
Цена A по отношению к B - это просто A/B и наоборот. Например, если у нас есть 2000 USDC в пуле (без учета десятичных дробей) и 1 Ether, то цена 1 Ether равна просто 2000 USDC / 1 ETH.
Таким образом, цена USDC, выраженная в ETH, - это просто это число с перевернутыми числителем и знаменателем.
Однако мы не можем просто "перевернуть" одну из цен, чтобы получить другую, когда мы накапливаем цены (не уверен в точном переводе accumulating pricing в данном случае).
Рассмотрим следующее. Если наш накопитель цен начинается с 2 и прибавляет 3, мы не можем просто сделать так:
1 / 2+3 != 1/2 + 1/3
Однако цены все еще "в некоторой степени симметричны", поэтому выбор арифметического представления с фиксированной точкой должен иметь одинаковую емкость для целых и для десятичных чисел.
Если Eth в 1 000 раз "ценнее", чем USDC, то USDC в 1 000 раз "менее ценен", чем USDC. И чтобы хранить эти значения более точно, число с фиксированной точкой должно иметь одинаковый размер по обе стороны decimals, поэтому Uniswap выбрал u112x112 (объяснение в первом посте).
Далее пара слов о значении PriceCumulativeLast, которое всегда увеличивается, пока не переполнится, а затем снова продолжает рост.
Uniswap V2 был создан до версии Solidity 0.8.0, поэтому арифметика по умолчанию могла привести к overflow/underflow. Корректные современные реализации ценового оракула должны использовать блок unchecked, чтобы все работало как положено.
В конце концов, значение priceAccumulators и временная метка блока будет переполняться в контракте. В этом случае предыдущий резерв будет выше нового. Когда оракул вычислит изменение цены, он получит отрицательное значение. Но это не имеет значения в силу правил модульной арифметики.
Чтобы упростить задачу, давайте представим целые числа, которые переполняются при 100.
Мы фиксируем priceAccumulator на 80, а через несколько транзакций/блоков priceAccumulator поднимается до 110, но переполняется до 10. Мы вычитаем 80 из 10, что дает -70. Но значение хранится как беззнаковое целое, поэтому оно дает -70 mod(100), что равно 30. Это тот же результат, который мы ожидали бы, если бы переполнения не было (110-80=30).
Это справедливо для всех границ переполнения, а не только для 100 в нашем примере.
То же самое произойдет, если мы переполним временную метку. Поскольку для ее представления мы используем uint32, отрицательных чисел не будет. Опять же, предположим, что для простоты мы переполнимся на 100. Если мы делаем снимок в момент времени 98 и обращаемся к оракулу цен в момент времени 4, то прошло 6 секунд. 4 - 98 % 100 = 6, как и ожидалось.
Теперь вы знаете о TWAP чуточку больше. Далее мы продолжим говорить о Uniswap V2, V3 и других DeFi протоколах.
#uniswap #v2 #twap
🔥4👌2
Uniswap V2: UniswapV2Library
Мы продолжаем разбирать код одного из самых опулярны протоколов, и следующие пару дней посвятим библиотеке UniswapV2Library:
https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol
Библиотека Uniswap V2 упрощает некоторые взаимодействия с контрактами пар токенов и активно используется контрактами Router. Она содержит всего восемь функций.
getAmountOut() и getAmountIn()
Если мы хотим предсказать количество токенов У, которое мы получим, если добавим фиксированное количество токенов Х, то мы можем вывести количество исходящих токенов, используя следующую последовательность (для простоты игнорируя комиссию).
amount_out = reserve_out * amount_in / reserve_in + amount_in
Учитывая это, функция getAmountOut() в UniswapV2Library.sol должна быть понятна. Обратите внимание, что числа масштабируются на 1000, чтобы учесть комиссию в 0,3%.
Связка логики между getAmountOut() и getAmountsOut() при проведении операций между различными парами токенов
Если трейдер предоставляет последовательность пар (A, B), (B, C), (C, D) и циклически вызывает getAmountOut, начиная с определенной суммы A, то количество полученного токена D может быть предсказано.
Адрес контракта пары UniswapV2 для каждого (A, B), (B, C) и т. д. детерминировано выводится из адресов токенов и адреса фабрики, развернувшей пару с помощью функции create2. Если даны два токена (A, B) и адрес фабрики, функция pairFor() выводит адрес контракта парного пула UniswapV2 для этой пары, используя в качестве вспомогательной функции sortTokens().
Теперь, когда мы знаем адреса всех пар, мы можем получить резервы каждой из них и предсказать, сколько токенов мы получим в конце цепочки обменов. Ниже приведен код функции getAmountsOut (обратите внимание, что написано"Amounts", а не на "Amount").
Обратите внимание на пару моментов:
1. Смарт-контракт не может самостоятельно определить оптимальную последовательность пар, ему нужно сообщить список пар для расчета цепочки свопов. Это лучше всего делать вне цепочки.
2. Он возвращает не просто конечную сумму токеновOut в цепочке, а сумму на каждом шаге.
Далее поговорим об остальных функциях этой библиотеки.
#uniswap #v2 #UniswapV2Library
Мы продолжаем разбирать код одного из самых опулярны протоколов, и следующие пару дней посвятим библиотеке UniswapV2Library:
https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol
Библиотека Uniswap V2 упрощает некоторые взаимодействия с контрактами пар токенов и активно используется контрактами Router. Она содержит всего восемь функций.
getAmountOut() и getAmountIn()
Если мы хотим предсказать количество токенов У, которое мы получим, если добавим фиксированное количество токенов Х, то мы можем вывести количество исходящих токенов, используя следующую последовательность (для простоты игнорируя комиссию).
amount_out = reserve_out * amount_in / reserve_in + amount_in
Учитывая это, функция getAmountOut() в UniswapV2Library.sol должна быть понятна. Обратите внимание, что числа масштабируются на 1000, чтобы учесть комиссию в 0,3%.
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
Связка логики между getAmountOut() и getAmountsOut() при проведении операций между различными парами токенов
Если трейдер предоставляет последовательность пар (A, B), (B, C), (C, D) и циклически вызывает getAmountOut, начиная с определенной суммы A, то количество полученного токена D может быть предсказано.
Адрес контракта пары UniswapV2 для каждого (A, B), (B, C) и т. д. детерминировано выводится из адресов токенов и адреса фабрики, развернувшей пару с помощью функции create2. Если даны два токена (A, B) и адрес фабрики, функция pairFor() выводит адрес контракта парного пула UniswapV2 для этой пары, используя в качестве вспомогательной функции sortTokens().
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
))));
}Теперь, когда мы знаем адреса всех пар, мы можем получить резервы каждой из них и предсказать, сколько токенов мы получим в конце цепочки обменов. Ниже приведен код функции getAmountsOut (обратите внимание, что написано"Amounts", а не на "Amount").
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}Обратите внимание на пару моментов:
1. Смарт-контракт не может самостоятельно определить оптимальную последовательность пар, ему нужно сообщить список пар для расчета цепочки свопов. Это лучше всего делать вне цепочки.
2. Он возвращает не просто конечную сумму токеновOut в цепочке, а сумму на каждом шаге.
Далее поговорим об остальных функциях этой библиотеки.
#uniswap #v2 #UniswapV2Library
🔥3👍1
Uniswap V2: UniswapV2Library. Часть 2
Продолжаем говорить о библиотеке и ее функциях и разберем последние две.
getReserves()
Функция getReserves - это просто обертка функции getReserves из контракта пула токенов Uniswap V2, за исключением того, что она также удаляет временную метку последнего обновления цены.
quote()
Напомним, что цена актива определяется по следующей формуле:
price(foo) = reserves(bar) / reserves(foo)
Эта функция возвращает цену foo, выраженную в bar, по состоянию на последнее обновление. Эту функцию следует использовать с осторожностью, так как она уязвима для атак флэш-кредитования.
Если вы хотите узнать, сколько нужно вложить или ожидать от сделки, а также последовательность сделок по парам, UniswapV2Library - это инструмент, который следует использовать.
К слову сказать, если хотите потренироваться в работе с поиском уязвимостей, то Damn Vulnerable Defi предлагает прекрасную задачу по этой теме:
https://www.damnvulnerabledefi.xyz/challenges/puppet-v2/
Далее мы будем разбирать контракты Router.
#uniswap #v2 #UniswapV2Library
Продолжаем говорить о библиотеке и ее функциях и разберем последние две.
getReserves()
Функция getReserves - это просто обертка функции getReserves из контракта пула токенов Uniswap V2, за исключением того, что она также удаляет временную метку последнего обновления цены.
//Library
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
//pool
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
quote()
Напомним, что цена актива определяется по следующей формуле:
price(foo) = reserves(bar) / reserves(foo)
Эта функция возвращает цену foo, выраженную в bar, по состоянию на последнее обновление. Эту функцию следует использовать с осторожностью, так как она уязвима для атак флэш-кредитования.
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
Если вы хотите узнать, сколько нужно вложить или ожидать от сделки, а также последовательность сделок по парам, UniswapV2Library - это инструмент, который следует использовать.
К слову сказать, если хотите потренироваться в работе с поиском уязвимостей, то Damn Vulnerable Defi предлагает прекрасную задачу по этой теме:
https://www.damnvulnerabledefi.xyz/challenges/puppet-v2/
Далее мы будем разбирать контракты Router.
#uniswap #v2 #UniswapV2Library
👍3
Сборник ресурсов по Uniswap V2/V3
Недавно в Твиттере кто-то поделился прекрасным репо, где собраны различные материалы по работе с двумя версиями Uniswap - V2 и V3.
Тут вы сможете найти как примеры контрактов, так и официальную документацию протокола, а также различные статьи, видео, подкасты и многое другое.
Думаю, в течение нашего разбора DeFi протоколов мы иногда будем брать оттуда статьи для разбора на нашем канале.
Ссылка на сборник: https://github.com/Sabnock01/uniswap-resources
Интересно, а когда начнут выходить достойные гайды по V4. Она же должна будет выйти через пару месяцев после обновления самой сети.
#uniswap #github
Недавно в Твиттере кто-то поделился прекрасным репо, где собраны различные материалы по работе с двумя версиями Uniswap - V2 и V3.
Тут вы сможете найти как примеры контрактов, так и официальную документацию протокола, а также различные статьи, видео, подкасты и многое другое.
Думаю, в течение нашего разбора DeFi протоколов мы иногда будем брать оттуда статьи для разбора на нашем канале.
Ссылка на сборник: https://github.com/Sabnock01/uniswap-resources
Интересно, а когда начнут выходить достойные гайды по V4. Она же должна будет выйти через пару месяцев после обновления самой сети.
#uniswap #github
🔥5
Разбор Uniswap V2: Router контракты
Мы продолжаем изучать код протокола Uniswap версии V2, и на этой неделе поговорим про контракт Router и его основные функции.
Итак, этот контракт отвечает за:
1. Безопасный майнинг и сжигание токенов LP (добавление и удаление ликвидности)
2. Безопасный своп токенов;
3. Возможный обмен Эфира при помощи его ERC-20 версии - WETH;
4. Проверки проскальзывания при работе с основным контрактом;
5 Поддержка fee on transfer токенов;
Вообще, если мы зайдем по ссылке репо, то увидим, что там два контакта Router:
UniswapV2Router01.sol
UniswapV2Router02.sol
Вот второй, как раз и добавляет поддержку fee on transfer токенов. Если мы заглянем в него, то увидим, что он наследует от первого контракта и добавляет уникальные функции, например:
function removeLiquidityETHSupportingFeeOnTransferTokens(...) ) public virtual override ensure(deadline) returns (uint amountETH) {}
Теперь о самих функциях.
swapExactTokensForTokens и swapTokensForExactTokens
Обе эти функции используются при свапе токенов, с одной разницей:
1. В swapExactTokensForTokens означает, что количество входного токена, который вы обмениваете, фиксировано.
2. В swapTokensForExactTokens - количество выходного токена, который вы получите, фиксировано.
Если пользователь обменивает только два токена, то он передаст этим функциям массив address[] calldata path [address(tokenIn), address(tokenOut)]. Если пользователь обменивается между пулами, то он укажет [address(tokenIn), address(intermediateToken), ..., address(tokenOut)].
swapExactTokensForTokens
В случае с swapExactTokensForTokens пользователь указывает точное количество первого токена, который он собирается внести, и минимальное количество выходного токена, которое он примет.
Например, предположим, что мы хотим обменять 25 токенов_0 на 50 токенов_1. Если это точная цена в текущем состоянии, это не оставляет никаких шансов на то, что цена изменится до подтверждения нашей транзакции, что приведет к реверту. Поэтому вместо этого мы указываем минимальную цену 49,5 токена_1, неявно оставляя проскальзывание в 1 %.
swapTokensForExactTokens
В этом случае мы указываем, что нам нужно ровно 50 токенов_1, но мы готовы обменять до 25,5 токенов_0, чтобы получить их.
Какую функцию использовать?
Большинство пользователей, использующих EOA, скорее всего, предпочтут использовать функцию swapExactTokensForTokens, поскольку им необходим этап подтверждения, и сделка не состоится, если им придется заплатить больше, чем они хотели.
Однако смарт-контракты, интернирующиеся с Uniswap, могут иметь более сложные требования, поэтому маршрутизатор дает им возможность использовать оба варианта.
Как работает своп токенов
Если входные данные точные (swapExactTokensForTokens), функция предсказывает ожидаемый выход для одного свопа или цепочки свопов. Если полученный результат ниже заданной пользователем суммы, функция откатится.
И наоборот, для точного выхода: она вычисляет требуемый вход и возвращается, если он выше заданного пользователем порога.
Затем обе функции передают токены пользователя в контракт пары (мы же помним, что Uniswap V2 Pair требует, чтобы токены были отправлены на сам контракт до вызова функции swap).
Наконец, они вызывают внутреннюю функцию _swap, о которой пойдет речь далее.
#uniswap #router
Мы продолжаем изучать код протокола Uniswap версии V2, и на этой неделе поговорим про контракт Router и его основные функции.
Итак, этот контракт отвечает за:
1. Безопасный майнинг и сжигание токенов LP (добавление и удаление ликвидности)
2. Безопасный своп токенов;
3. Возможный обмен Эфира при помощи его ERC-20 версии - WETH;
4. Проверки проскальзывания при работе с основным контрактом;
5 Поддержка fee on transfer токенов;
Вообще, если мы зайдем по ссылке репо, то увидим, что там два контакта Router:
UniswapV2Router01.sol
UniswapV2Router02.sol
Вот второй, как раз и добавляет поддержку fee on transfer токенов. Если мы заглянем в него, то увидим, что он наследует от первого контракта и добавляет уникальные функции, например:
function removeLiquidityETHSupportingFeeOnTransferTokens(...) ) public virtual override ensure(deadline) returns (uint amountETH) {}
Теперь о самих функциях.
swapExactTokensForTokens и swapTokensForExactTokens
Обе эти функции используются при свапе токенов, с одной разницей:
1. В swapExactTokensForTokens означает, что количество входного токена, который вы обмениваете, фиксировано.
2. В swapTokensForExactTokens - количество выходного токена, который вы получите, фиксировано.
Если пользователь обменивает только два токена, то он передаст этим функциям массив address[] calldata path [address(tokenIn), address(tokenOut)]. Если пользователь обменивается между пулами, то он укажет [address(tokenIn), address(intermediateToken), ..., address(tokenOut)].
swapExactTokensForTokens
В случае с swapExactTokensForTokens пользователь указывает точное количество первого токена, который он собирается внести, и минимальное количество выходного токена, которое он примет.
Например, предположим, что мы хотим обменять 25 токенов_0 на 50 токенов_1. Если это точная цена в текущем состоянии, это не оставляет никаких шансов на то, что цена изменится до подтверждения нашей транзакции, что приведет к реверту. Поэтому вместо этого мы указываем минимальную цену 49,5 токена_1, неявно оставляя проскальзывание в 1 %.
swapTokensForExactTokens
В этом случае мы указываем, что нам нужно ровно 50 токенов_1, но мы готовы обменять до 25,5 токенов_0, чтобы получить их.
Какую функцию использовать?
Большинство пользователей, использующих EOA, скорее всего, предпочтут использовать функцию swapExactTokensForTokens, поскольку им необходим этап подтверждения, и сделка не состоится, если им придется заплатить больше, чем они хотели.
Однако смарт-контракты, интернирующиеся с Uniswap, могут иметь более сложные требования, поэтому маршрутизатор дает им возможность использовать оба варианта.
Как работает своп токенов
Если входные данные точные (swapExactTokensForTokens), функция предсказывает ожидаемый выход для одного свопа или цепочки свопов. Если полученный результат ниже заданной пользователем суммы, функция откатится.
И наоборот, для точного выхода: она вычисляет требуемый вход и возвращается, если он выше заданного пользователем порога.
Затем обе функции передают токены пользователя в контракт пары (мы же помним, что Uniswap V2 Pair требует, чтобы токены были отправлены на сам контракт до вызова функции swap).
Наконец, они вызывают внутреннюю функцию _swap, о которой пойдет речь далее.
#uniswap #router
❤1
Разбор Uniswap V2: Router контракты. Часть 2
Продолжаем говорить о контрактах Router и сегодня обратим внимание на _swap(), addLiquidity() и addLiquidityEth().
Вообще по свапам у нас уже был отдельный цикл постов с детальным разбором, и тут ничего нового я вам не скажу. Думаю, скрин функции с комментариями поможет вам вспомнить основные моменты работы функции.
_addLiquidity
Помните о проверке безопасности при добавлении ликвидности? В частности, мы хотим убедиться, что вносим два токена в точно таком же соотношении, как и у пары в данный момент, иначе количество токенов LP, которые мы майним, будет худшим из двух соотношений между тем, что мы предоставляем, и тем, что есть на балансе пары.
Однако соотношение может измениться между моментом, когда поставщик ликвидности пытается добавить ликвидность, и моментом подтверждения транзакции.
Чтобы защититься от этого, поставщик ликвидности должен предоставить (в качестве аргумента функции) минимальный баланс, который он хочет внести для токена_0 и токена_1 (в UniswapV2 это amountAMin и amountBMin). Затем они переводят сумму, превышающую эти минимумы (UnsiwapV2 называет их amountADesired и amountBDesired). Если соотношение пар изменилось таким образом, что минимумы больше не соблюдаются, то транзакция откатывается.
_addLiquidity возьмет суммуADesired и вычислит правильное количество токенов_В, которое будет соответствовать соотношению. Если это количество больше, чем amountBDesired (количество B, которое отправил поставщик ликвидности), то он начнет с amountBDesired и вычислит оптимальное количество B. Логика показана на скрине.
P.S. Обратите внимание, что добавление ликвидности может создать новый парный контракт, если он еще не существует.
Например, предположим, что текущий баланс пары составляет 100 token_0 и 300 token_1. Мы хотим добавить 20 и 60 токен_0 и токен_1 соответственно, но соотношение пар может измениться.
Поэтому вместо этого мы одобряем маршрутизатор на 21 токен_0 и 63 токена_1, указывая при этом, что минимальное количество, которое мы хотим внести, составляет 20 и 60. Если соотношение изменится так, что оптимальное количество токенов0 для депозита составит 19,9, то транзакция вернется обратно.
Помните, мы говорили, что котировки цен токенов не следует использовать в качестве оракула? И это по-прежнему верно! Однако для целей добавления ликвидности нас интересует не среднее значение предыдущих цен, а текущая цена (коэффициент пула), поскольку поставщик ликвидности должен ее соблюдать.
addLiquidity() и addLiquidityEth()
Эти функции не требуют пояснений. Сначала они рассчитывают оптимальное соотношение с помощью _addLiquidity, описанного выше, затем переводят активы в контракт пары, после чего вызывают майнинг. Единственное отличие заключается в том, что функция addLiquidityEth сначала обернет Ether в ETH.
В следующем посте поговорим об удалении ликвидности, fee-on-transfer токенах и библиотеке UniswapV2Library.
#uniswap #router
Продолжаем говорить о контрактах Router и сегодня обратим внимание на _swap(), addLiquidity() и addLiquidityEth().
Вообще по свапам у нас уже был отдельный цикл постов с детальным разбором, и тут ничего нового я вам не скажу. Думаю, скрин функции с комментариями поможет вам вспомнить основные моменты работы функции.
_addLiquidity
Помните о проверке безопасности при добавлении ликвидности? В частности, мы хотим убедиться, что вносим два токена в точно таком же соотношении, как и у пары в данный момент, иначе количество токенов LP, которые мы майним, будет худшим из двух соотношений между тем, что мы предоставляем, и тем, что есть на балансе пары.
Однако соотношение может измениться между моментом, когда поставщик ликвидности пытается добавить ликвидность, и моментом подтверждения транзакции.
Чтобы защититься от этого, поставщик ликвидности должен предоставить (в качестве аргумента функции) минимальный баланс, который он хочет внести для токена_0 и токена_1 (в UniswapV2 это amountAMin и amountBMin). Затем они переводят сумму, превышающую эти минимумы (UnsiwapV2 называет их amountADesired и amountBDesired). Если соотношение пар изменилось таким образом, что минимумы больше не соблюдаются, то транзакция откатывается.
_addLiquidity возьмет суммуADesired и вычислит правильное количество токенов_В, которое будет соответствовать соотношению. Если это количество больше, чем amountBDesired (количество B, которое отправил поставщик ликвидности), то он начнет с amountBDesired и вычислит оптимальное количество B. Логика показана на скрине.
P.S. Обратите внимание, что добавление ликвидности может создать новый парный контракт, если он еще не существует.
Например, предположим, что текущий баланс пары составляет 100 token_0 и 300 token_1. Мы хотим добавить 20 и 60 токен_0 и токен_1 соответственно, но соотношение пар может измениться.
Поэтому вместо этого мы одобряем маршрутизатор на 21 токен_0 и 63 токена_1, указывая при этом, что минимальное количество, которое мы хотим внести, составляет 20 и 60. Если соотношение изменится так, что оптимальное количество токенов0 для депозита составит 19,9, то транзакция вернется обратно.
Помните, мы говорили, что котировки цен токенов не следует использовать в качестве оракула? И это по-прежнему верно! Однако для целей добавления ликвидности нас интересует не среднее значение предыдущих цен, а текущая цена (коэффициент пула), поскольку поставщик ликвидности должен ее соблюдать.
addLiquidity() и addLiquidityEth()
Эти функции не требуют пояснений. Сначала они рассчитывают оптимальное соотношение с помощью _addLiquidity, описанного выше, затем переводят активы в контракт пары, после чего вызывают майнинг. Единственное отличие заключается в том, что функция addLiquidityEth сначала обернет Ether в ETH.
В следующем посте поговорим об удалении ликвидности, fee-on-transfer токенах и библиотеке UniswapV2Library.
#uniswap #router
❤1
Разбор Uniswap V2: Router контракты. Часть 3
Ну, и заключительная часть разбора Router контракта начинается с функций removeLiquidityEth и removeLiquidity.
Функции удаления ликвидности вызывают burn, и использует параметры amountAMin и amountBMin в качестве проверки безопасности, чтобы гарантировать, чтобы пользователь получил обратно то количество токенов, которое ожидает.
В случае, если соотношение токенов резко изменится до того, как токены ликвидности будут сожжены, функция откатится и пользователь не потеряет свои активы.
При этом в функции removeLiquidityEth также происходит взаимодействие с WETH контрактом, чтобы пользователь мог получить Эфир.
removeLiquidityWithPermit() и removeLiquidityETHWithPermit()
В строке 109 в приведенном выше файле с серым комментарием send liquidity to pair предполагает, что контракт пары имеет разрешение на передачу токенов LP от поставщика ликвидности для их сжигания. Это означает, что для сжигания LP-токенов требуется сначала одобрить пару. Этот шаг можно пропустить с помощью функции permit().
Router02 и поддержка fee-on-transfer токенов
Чтобы справиться подобными токенами, контракт Router не может напрямую производить расчеты по таким аргументам, как amountIn (для свопа) или liquidity (для удаления ликвидности). В этом случае, добавление ликвидности не будет зависеть от комиссии за перевод токенов, поскольку пользователю начисляется только то, что он фактически переводит в пару.
Контракты Router обеспечивают пользовательский механизм обмена токенов с защитой от проскальзывания, в том числе и между несколькими пулами, а также добавляют поддержку торговли ETH и fee on transfer токенами (в Router02).
Функции внесения ликвидности гарантируют, что пользователь вносит средства только в точном соотношении к пулу. Удаление ликвидности может быть как простым переводом токенов LP на маршрутизатор с последующим их сжиганием, так и с использованием WETH для работы с Эфиром.
Кроме того, включена поддержка функций ERC20 Permit.
#uniswap #router
Ну, и заключительная часть разбора Router контракта начинается с функций removeLiquidityEth и removeLiquidity.
Функции удаления ликвидности вызывают burn, и использует параметры amountAMin и amountBMin в качестве проверки безопасности, чтобы гарантировать, чтобы пользователь получил обратно то количество токенов, которое ожидает.
В случае, если соотношение токенов резко изменится до того, как токены ликвидности будут сожжены, функция откатится и пользователь не потеряет свои активы.
При этом в функции removeLiquidityEth также происходит взаимодействие с WETH контрактом, чтобы пользователь мог получить Эфир.
removeLiquidityWithPermit() и removeLiquidityETHWithPermit()
В строке 109 в приведенном выше файле с серым комментарием send liquidity to pair предполагает, что контракт пары имеет разрешение на передачу токенов LP от поставщика ликвидности для их сжигания. Это означает, что для сжигания LP-токенов требуется сначала одобрить пару. Этот шаг можно пропустить с помощью функции permit().
Router02 и поддержка fee-on-transfer токенов
Чтобы справиться подобными токенами, контракт Router не может напрямую производить расчеты по таким аргументам, как amountIn (для свопа) или liquidity (для удаления ликвидности). В этом случае, добавление ликвидности не будет зависеть от комиссии за перевод токенов, поскольку пользователю начисляется только то, что он фактически переводит в пару.
Контракты Router обеспечивают пользовательский механизм обмена токенов с защитой от проскальзывания, в том числе и между несколькими пулами, а также добавляют поддержку торговли ETH и fee on transfer токенами (в Router02).
Функции внесения ликвидности гарантируют, что пользователь вносит средства только в точном соотношении к пулу. Удаление ликвидности может быть как простым переводом токенов LP на маршрутизатор с последующим их сжиганием, так и с использованием WETH для работы с Эфиром.
Кроме того, включена поддержка функций ERC20 Permit.
#uniswap #router
🔥3
Обновление Dencun
Все никак сам не мог дойти, чтобы изучить более подробно последнее обновление Эфира - Dencun.
К счастью, сегодня в 19:00 по мск на Ютуб канале Ильи будет стрим по этой теме.
Крайне рекомендую всем подключиться!
Ссылка на стрим: https://www.youtube.com/watch?v=mugTyD70rDk
Встретимся вечером!
#dencun
Все никак сам не мог дойти, чтобы изучить более подробно последнее обновление Эфира - Dencun.
К счастью, сегодня в 19:00 по мск на Ютуб канале Ильи будет стрим по этой теме.
Крайне рекомендую всем подключиться!
Ссылка на стрим: https://www.youtube.com/watch?v=mugTyD70rDk
Встретимся вечером!
#dencun
👍3🔥2
Хотите перезапуск курса с нуля?
В январе, когда запускался 4 модуль нашего курса для разработчиков смарт контрактов, я писал, что заново с 1 модуля мы будем проводить его уже весной, с обновленной и дополненной программой.
На фоне того, что сейчас в сети вышло так много разнообразных материалов, статей, да и самих курсов (вспомнить хотя бы того же Патрика Коллинса с его бесплатными уроками), у меня возник вопрос, а нужно ли делать перезапуск?
Основной идеей нашего курса было разложить материал в доступной форме от простого к сложному, с простыми заданиями, чтобы ученики могли без надрыва понять тему и написать свой первый код. Более того, некоторые довольно быстро могли понять, а нужно ли им это вообще, без затрат на дорогие курсы и школы.
Для многих наш курс стал стартом карьеры в web3. Одни нашли работу, другие продолжают обучение сами или уже в школах для более профессиональной практики.
Я хотел бы узнать, сколько участников канала хотели бы зайти на 1 модуль курса? Также напомню, что курс рассчитан на тех, кто ни разу не имел дело с программированием.
Прошу пройти опрос ниже.
#курс
В январе, когда запускался 4 модуль нашего курса для разработчиков смарт контрактов, я писал, что заново с 1 модуля мы будем проводить его уже весной, с обновленной и дополненной программой.
На фоне того, что сейчас в сети вышло так много разнообразных материалов, статей, да и самих курсов (вспомнить хотя бы того же Патрика Коллинса с его бесплатными уроками), у меня возник вопрос, а нужно ли делать перезапуск?
Основной идеей нашего курса было разложить материал в доступной форме от простого к сложному, с простыми заданиями, чтобы ученики могли без надрыва понять тему и написать свой первый код. Более того, некоторые довольно быстро могли понять, а нужно ли им это вообще, без затрат на дорогие курсы и школы.
Для многих наш курс стал стартом карьеры в web3. Одни нашли работу, другие продолжают обучение сами или уже в школах для более профессиональной практики.
Я хотел бы узнать, сколько участников канала хотели бы зайти на 1 модуль курса? Также напомню, что курс рассчитан на тех, кто ни разу не имел дело с программированием.
Прошу пройти опрос ниже.
#курс
👍14❤1
🔥4
Как проходит обучение на курсе
Неожиданно много и приятно, что так много участников канала хочет попасть на перезапуск курса. Расскажу, как проходит обучение, чего ожидать, и как долго все длится.
Данный курс состоит из 4 модулей (хотя сейчас я планирую объединить их в 3, для более быстрого прохождения участниками). Каждый модуль длится примерно месяц, или 4 недели.
Задания выходят несколько раз в неделю. Есть текстовая часть урока, практическое задание, дополнительные видео материалы и посты / статьи для самостоятельного изучения.
В конце каждого модуля есть финальный практикум, объединяющий все темы уроков.
В течение курса также создается отдельный чат для учеников, где они могут задавать вопросы и общаться. Канал и чат не удаляются по завершению обучения.
Заходить можно на любой из модулей до его старта.
Вообще идея курса заключается в нескольких пунктах:
1. Упорядочить материал так, чтобы не создавалась "каша" в голове;
2. Написать уроки доступно и просто, чтобы ученику было понятен урок, даже если он никогда ранее не занимался программированием;
3. Низкая цена модулей, чтобы ученики смогли понять для себя, а нужно ли им это вообще;
Вы сможете понять основную суть языка Solidity и работы со смарт контрактами, и, если интерес сохранится, то можно пойти в более профессиональную школу для дальнейшего обучения. Так будете уверены, что понимаете, на что подписываетесь, и на что тратите большие деньги в школе.
Мне нужно будет некоторое время, чтобы обновить материалы.
Начать обучение на 1 модуле, думаю, сможем с 22 апреля.
Буду держать вас в курсе событий: выложу программу, стоимость и что нужно для подготовки.
#курс
Неожиданно много и приятно, что так много участников канала хочет попасть на перезапуск курса. Расскажу, как проходит обучение, чего ожидать, и как долго все длится.
Данный курс состоит из 4 модулей (хотя сейчас я планирую объединить их в 3, для более быстрого прохождения участниками). Каждый модуль длится примерно месяц, или 4 недели.
Задания выходят несколько раз в неделю. Есть текстовая часть урока, практическое задание, дополнительные видео материалы и посты / статьи для самостоятельного изучения.
В конце каждого модуля есть финальный практикум, объединяющий все темы уроков.
В течение курса также создается отдельный чат для учеников, где они могут задавать вопросы и общаться. Канал и чат не удаляются по завершению обучения.
Заходить можно на любой из модулей до его старта.
Вообще идея курса заключается в нескольких пунктах:
1. Упорядочить материал так, чтобы не создавалась "каша" в голове;
2. Написать уроки доступно и просто, чтобы ученику было понятен урок, даже если он никогда ранее не занимался программированием;
3. Низкая цена модулей, чтобы ученики смогли понять для себя, а нужно ли им это вообще;
Вы сможете понять основную суть языка Solidity и работы со смарт контрактами, и, если интерес сохранится, то можно пойти в более профессиональную школу для дальнейшего обучения. Так будете уверены, что понимаете, на что подписываетесь, и на что тратите большие деньги в школе.
Мне нужно будет некоторое время, чтобы обновить материалы.
Начать обучение на 1 модуле, думаю, сможем с 22 апреля.
Буду держать вас в курсе событий: выложу программу, стоимость и что нужно для подготовки.
#курс
👍26❤1
Перезапуск курса: старт 22 апреля
За время старта опроса про курс, успело проголосовать 124 участника канала, из которых 92 уже готовы начать обучение! Это здорово и мотивирует еще больше вкладываться в курс, чтобы сделать его максимально эффективным и практическим.
И если так много желающих, то мы можем начать уже с 22 апреля, а продажи открыть с 17 апреля.
Окончательную программу выложу в эту среду, а пока расскажу про некоторые изменения в структуре курса.
Всего у нас было 4 модуля:
1. Первый для того, чтобы понять и попрактиковаться с базовым синтаксисом языка;
2. Второй - чтобы разбираться как работают "связки" между контрактами и изучить стандарты токенов и nft;
3. Третий - практический, где мы настраивали рабочую среду, знакомились с Hardhat и Foundry, а также некоторыми паттернами и другими стандартами;
4. Четвертый - самый сложный, где у нас был второй преподаватель. Там мы проходили темы прокси, подписей, работу с памятью и другие сложные паттерны кода.
Сейчас темы будут оптимизированы по процессу обучения и выстроены в чуть более интенсивном порядке.
1. На первом модуле вместо 16 уроков, как это было изначально, будут ~23 урока плюс практикумы. Тут мы изучим базовые элементы языка, а также наследования, интерфейсы и библиотеки, которые мы проходили на 2 модуле.
2. На втором модулей будет разбираться настройка рабочего пространства, программы Hardhat / Foundry, а также основные стандарты языка (токены, nft).
3. На место третьего модуля встанет текущий четвертый.
4. А на последнем модуле мы изучим популярные паттерны, а также введение в DeFi, которого не было на данный момент. В нем мы поговорим о таких рабочих схемах, как флешзаймы, стейкинг, ликвидации, долги и rewards за стейкинг.
Зачем это сделано именно так?
Во-первых, уже после первого модуля вы сможете понять для себя, а вообще ваша ли это тема "Написание смарт контрактов и web3 в целом". Если интерес не пропадет, дальше вы сможете записаться на другой модуль, или с уже имеющимися знаниями продолжить свое обучение на других курсах (от того же Патрика Коллинса), или даже записаться в профессиональную школу, типа Guide DAO.
Во-вторых, написание полноценных смарт контрактов будет изучаться только на втором модуле, так как для начала нужно научится работать с синтаксисом языка и обрести понимание, например, "а зачем в коде использовать эту структуру, а не какую-либо другую".
В-третьих, уже после третьего модуля вы получите основные знания практически по всем необходимым элементам разработки смарт контрактов и сможете писать сложные проекты.
Ну, а в-четвертых, в самом конце, после 4 модуля, у вас будет практический опыт разработки и понимание современных систем функционирования DeFi протоколов.
Весь процесс выстроен на 4 месяца обучение. Т.е. примерно к сентябрю, при ежедневном обучении вы сможете получить необходимые знания и опыт для своей первой работы.
Всем приятной рабочей недели!
#курс
За время старта опроса про курс, успело проголосовать 124 участника канала, из которых 92 уже готовы начать обучение! Это здорово и мотивирует еще больше вкладываться в курс, чтобы сделать его максимально эффективным и практическим.
И если так много желающих, то мы можем начать уже с 22 апреля, а продажи открыть с 17 апреля.
Окончательную программу выложу в эту среду, а пока расскажу про некоторые изменения в структуре курса.
Всего у нас было 4 модуля:
1. Первый для того, чтобы понять и попрактиковаться с базовым синтаксисом языка;
2. Второй - чтобы разбираться как работают "связки" между контрактами и изучить стандарты токенов и nft;
3. Третий - практический, где мы настраивали рабочую среду, знакомились с Hardhat и Foundry, а также некоторыми паттернами и другими стандартами;
4. Четвертый - самый сложный, где у нас был второй преподаватель. Там мы проходили темы прокси, подписей, работу с памятью и другие сложные паттерны кода.
Сейчас темы будут оптимизированы по процессу обучения и выстроены в чуть более интенсивном порядке.
1. На первом модуле вместо 16 уроков, как это было изначально, будут ~23 урока плюс практикумы. Тут мы изучим базовые элементы языка, а также наследования, интерфейсы и библиотеки, которые мы проходили на 2 модуле.
2. На втором модулей будет разбираться настройка рабочего пространства, программы Hardhat / Foundry, а также основные стандарты языка (токены, nft).
3. На место третьего модуля встанет текущий четвертый.
4. А на последнем модуле мы изучим популярные паттерны, а также введение в DeFi, которого не было на данный момент. В нем мы поговорим о таких рабочих схемах, как флешзаймы, стейкинг, ликвидации, долги и rewards за стейкинг.
Зачем это сделано именно так?
Во-первых, уже после первого модуля вы сможете понять для себя, а вообще ваша ли это тема "Написание смарт контрактов и web3 в целом". Если интерес не пропадет, дальше вы сможете записаться на другой модуль, или с уже имеющимися знаниями продолжить свое обучение на других курсах (от того же Патрика Коллинса), или даже записаться в профессиональную школу, типа Guide DAO.
Во-вторых, написание полноценных смарт контрактов будет изучаться только на втором модуле, так как для начала нужно научится работать с синтаксисом языка и обрести понимание, например, "а зачем в коде использовать эту структуру, а не какую-либо другую".
В-третьих, уже после третьего модуля вы получите основные знания практически по всем необходимым элементам разработки смарт контрактов и сможете писать сложные проекты.
Ну, а в-четвертых, в самом конце, после 4 модуля, у вас будет практический опыт разработки и понимание современных систем функционирования DeFi протоколов.
Весь процесс выстроен на 4 месяца обучение. Т.е. примерно к сентябрю, при ежедневном обучении вы сможете получить необходимые знания и опыт для своей первой работы.
Всем приятной рабочей недели!
#курс
🔥15👍5