Хочу узнать, сколько человек планирует пойти на 4 модуль?
Anonymous Poll
48%
Я точно иду!
52%
Пропускаю в этот раз
4 модуль и результат опроса
Интересная статистика опроса получилась в этот раз: 32 - за, 35 - пропускают (на момент написания поста). Полагаю, это из-за того, что 4 модуль проводится с достаточно большим перерывом и многие участники уже успели пройти эти темы самостоятельно. Если у кого-то не так, буду рад узнать в комментариях.
А для тех, кто решился пойти, будет больше преимуществ и возможностей задавать вопросы в небольшой группе. Обучение в этом случае пойдет чуть более интенсивнее и эффективнее!
Я также хочу узнать, какие темы для модулей или канала вам было бы интересно изучить? За полтора года ведения канала я написал кучу постов на самые разные темы Solidity и аудита. Может я что-то пропустил?
#курс
Интересная статистика опроса получилась в этот раз: 32 - за, 35 - пропускают (на момент написания поста). Полагаю, это из-за того, что 4 модуль проводится с достаточно большим перерывом и многие участники уже успели пройти эти темы самостоятельно. Если у кого-то не так, буду рад узнать в комментариях.
А для тех, кто решился пойти, будет больше преимуществ и возможностей задавать вопросы в небольшой группе. Обучение в этом случае пойдет чуть более интенсивнее и эффективнее!
Я также хочу узнать, какие темы для модулей или канала вам было бы интересно изучить? За полтора года ведения канала я написал кучу постов на самые разные темы Solidity и аудита. Может я что-то пропустил?
#курс
👍5
Сложные темы 4 модуля
Уже достаточно долгое время мы пишем 4 модуль курса, стараясь сделать его более простым и понятным для учеников. Огромное количество уязвимостей постоянно находится на конкурсных аудитах по каждой из этих тем. Если зайти на проект Solodit (тот, который собирает отчеты с аудитов) и поискать сколько багов было найдено, то мы увидим, что:
- 594 проблемы в контрактах с подписями,
- 513 репортов по теме прокси,
- 160 не правильно использовали assembly,
- 37 ошиблись с Merkle tree,
И это количество постоянно растет! После последних трех конкурсов на code4rena, с уверенностью могу сказать, что прибавятся еще репорты связанные с неправильной работой с EIP-712!
Нашей задачей на 4 модуль будет научить вас не только, как правильно работать с этими темами, но и дать практические навыки по поиску уязвимостей, чтобы вы могли просматривать свой проект после его завершения и делать его еще более безопасным.
В итоге, у нас будет 17 уроков и 3 мини аудита реальных протокола, как финальный практикум! 2 преподавателей будут отвечать на ваши вопросы по темам уроков.
В этот раз стоимость чуть выше: 5000 рублей или 60 USDT.
Время запуска зависит от количества учеников, которые продолжат обучение. Если вас будет мало, то придется еще немного подождать.
А так, сможем начать уже совсем скоро!
#курс
Уже достаточно долгое время мы пишем 4 модуль курса, стараясь сделать его более простым и понятным для учеников. Огромное количество уязвимостей постоянно находится на конкурсных аудитах по каждой из этих тем. Если зайти на проект Solodit (тот, который собирает отчеты с аудитов) и поискать сколько багов было найдено, то мы увидим, что:
- 594 проблемы в контрактах с подписями,
- 513 репортов по теме прокси,
- 160 не правильно использовали assembly,
- 37 ошиблись с Merkle tree,
И это количество постоянно растет! После последних трех конкурсов на code4rena, с уверенностью могу сказать, что прибавятся еще репорты связанные с неправильной работой с EIP-712!
Нашей задачей на 4 модуль будет научить вас не только, как правильно работать с этими темами, но и дать практические навыки по поиску уязвимостей, чтобы вы могли просматривать свой проект после его завершения и делать его еще более безопасным.
В итоге, у нас будет 17 уроков и 3 мини аудита реальных протокола, как финальный практикум! 2 преподавателей будут отвечать на ваши вопросы по темам уроков.
В этот раз стоимость чуть выше: 5000 рублей или 60 USDT.
Время запуска зависит от количества учеников, которые продолжат обучение. Если вас будет мало, то придется еще немного подождать.
А так, сможем начать уже совсем скоро!
#курс
❤13🔥2🌭1
Assembly, yul и opcodes на 4 модуле
Я встречал на многих курсах и обучениях, что эти темы дают чуть ли не в начале всего. Типа: "Посмотрите, как работает EVM, что использует и это все может быть в контракте". Я был слегка не согласен с этим и на своем курсе решил подождать до 4 модуля с этими темами, когда ученики уже освоят обычный Solidity, получат навыки написания контрактов и будут готовы воспринимать более сложную информацию.
Признаться честно, мне и сейчас порой сложно работать с низкоуровневым языком, а аудировать его сложнее. Для меня эти операции все еще вызывают желание закрыть окно редактора кода и уйти из web3...
Именно по этой причине на 4 модуле будет второй преподаватель @elawbek, который прекрасно понимает все детали работы storage, memory, calldata, opcodes и может с нуля писать сложные функции.
Например, в одной из библиотек, что он лично писал, есть такая функция:
Она вызовет "взрыв мозга" даже у бывалого разработчика.
Да, для такого уровня придется потратить немалое количество часов практики с assembly, а на курсе мы научим вас основам и вы сможете сами начать разбираться в этих самых опкодах и читать более простые функции.
Это будет еще один крутой месяц обучения!
До старта продаж уже совсем немного времени!
#курс
Я встречал на многих курсах и обучениях, что эти темы дают чуть ли не в начале всего. Типа: "Посмотрите, как работает EVM, что использует и это все может быть в контракте". Я был слегка не согласен с этим и на своем курсе решил подождать до 4 модуля с этими темами, когда ученики уже освоят обычный Solidity, получат навыки написания контрактов и будут готовы воспринимать более сложную информацию.
Признаться честно, мне и сейчас порой сложно работать с низкоуровневым языком, а аудировать его сложнее. Для меня эти операции все еще вызывают желание закрыть окно редактора кода и уйти из web3...
Именно по этой причине на 4 модуле будет второй преподаватель @elawbek, который прекрасно понимает все детали работы storage, memory, calldata, opcodes и может с нуля писать сложные функции.
Например, в одной из библиотек, что он лично писал, есть такая функция:
function pow(uint256 a, uint256 b) pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
switch b
case 0x00 {
result := 0x01
}
case 0x01 {
result := a
}
default {
switch a
case 0x00 {
// do nothing, result already zero
}
case 0x01 {
result := 0x01
}
case 0x02 {
if gt(b, 0xff) {
mstore(0x00, 0x35278d12)
revert(0x1c, 0x04)
}
result := shl(b, 0x01)
}
default {
switch or(
and(lt(a, 0x0b), lt(b, 0x4e)),
and(lt(a, 0x133), lt(b, 0x20))
)
case 0x01 {
result := exp(a, b)
}
default {
let maxUint256 := not(0x00)
let helper := 0x01
for {
let one := helper
} one {
} {
if gt(a, div(maxUint256, a)) {
mstore(0x00, 0x35278d12)
revert(0x1c, 0x04)
}
switch and(b, one)
case 0x00 {
b := shr(one, b)
}
case 0x01 {
helper := mul(a, helper)
b := shr(one, b)
}
a := mul(a, a)
if gt(b, one) {
continue
}
break
}
if gt(helper, div(maxUint256, a)) {
mstore(0x00, 0x35278d12)
revert(0x1c, 0x04)
}
result := mul(helper, a)
}
}
}
}
}
Она вызовет "взрыв мозга" даже у бывалого разработчика.
Да, для такого уровня придется потратить немалое количество часов практики с assembly, а на курсе мы научим вас основам и вы сможете сами начать разбираться в этих самых опкодах и читать более простые функции.
Это будет еще один крутой месяц обучения!
До старта продаж уже совсем немного времени!
#курс
❤11🤯1
4-5 модули, перезапуск и планы
Уже завтра стартуют продажи 4 модуля нашего курса, а пока я хочу поделиться некоторыми планами.
Прежде всего, 4 модуль - это завершающая часть курса. Если вы прошли темы всех четырех модулей, то уже понимаете как работает язык Solidity, знаете, где брать информацию по нюансам и у вас достаточно ресурсов для дальнейшего самостоятельного обучения.
Вы можете дальше изучать язык JavaScript, для получения профессии фронтенд разработчика, где вам потребуется изучить TypeScript, Hardhat, ethers.js, web.js и другие библиотеки для "связывания" контрактов с обычным сайтом.
Либо начать изучать Foundry, чтобы писать продвинутые тесты.
Да, я также говорил и о 5 модуле курса, где разбирались бы темы DeFi протоколов, их версий и интеграций, а также работа с оракулами, сложные паттерны и EIP стандарты, как например account abstraction. Но это уже скорее всего будет осенью, когда пройдет еще один поток курса. Кстати, о нем пару слов...
Весной я планирую перезапустить обучение с нуля с 1 модуля. Я добавлю материал в программу, сделаю его еще более понятным (основываясь на вопросах и затруднениях первых учеников) и, скорее всего, еще более практическим.
На канале же дальше будут идти общетематические посты про Solidity, тестирование и все новое, что появляется в сети.
Приятного дня и позитивного обучения!
#курс
Уже завтра стартуют продажи 4 модуля нашего курса, а пока я хочу поделиться некоторыми планами.
Прежде всего, 4 модуль - это завершающая часть курса. Если вы прошли темы всех четырех модулей, то уже понимаете как работает язык Solidity, знаете, где брать информацию по нюансам и у вас достаточно ресурсов для дальнейшего самостоятельного обучения.
Вы можете дальше изучать язык JavaScript, для получения профессии фронтенд разработчика, где вам потребуется изучить TypeScript, Hardhat, ethers.js, web.js и другие библиотеки для "связывания" контрактов с обычным сайтом.
Либо начать изучать Foundry, чтобы писать продвинутые тесты.
Да, я также говорил и о 5 модуле курса, где разбирались бы темы DeFi протоколов, их версий и интеграций, а также работа с оракулами, сложные паттерны и EIP стандарты, как например account abstraction. Но это уже скорее всего будет осенью, когда пройдет еще один поток курса. Кстати, о нем пару слов...
Весной я планирую перезапустить обучение с нуля с 1 модуля. Я добавлю материал в программу, сделаю его еще более понятным (основываясь на вопросах и затруднениях первых учеников) и, скорее всего, еще более практическим.
На канале же дальше будут идти общетематические посты про Solidity, тестирование и все новое, что появляется в сети.
Приятного дня и позитивного обучения!
#курс
🔥9👍4
Первые ученики уже на канале!
Если вы только пришли с работы или учебы, то пост для вас! Сегодня старт продаж 4 модуля нашего курса!
Если вы уже работали с темами прокси контрактов и подписями в Solidity, то сейчас у вас есть хорошая возможность еще лучше разобраться с работой storage, memory и calldata, а также понять как работают опкоды и assembly.
В ру чатах и каналах мало кто может дать дельный совет по работе с низкоуровневым языком, а на курсе у вас будет возможность задать свой вопрос напрямую и получить ответ от преподавателя, который написал не один контракт с assembly!
Продажи открыты только до конца недели! Успейте занять свое место!
Программа и условия оплаты в посте выше.
Старт 5 февраля!
#курс
Если вы только пришли с работы или учебы, то пост для вас! Сегодня старт продаж 4 модуля нашего курса!
Если вы уже работали с темами прокси контрактов и подписями в Solidity, то сейчас у вас есть хорошая возможность еще лучше разобраться с работой storage, memory и calldata, а также понять как работают опкоды и assembly.
В ру чатах и каналах мало кто может дать дельный совет по работе с низкоуровневым языком, а на курсе у вас будет возможность задать свой вопрос напрямую и получить ответ от преподавателя, который написал не один контракт с assembly!
Продажи открыты только до конца недели! Успейте занять свое место!
Программа и условия оплаты в посте выше.
Старт 5 февраля!
#курс
👍3
Использование структур, маппингов и событий с библиотеками
Неделя продаж завершена и канал возвращается к своему привычному ритму.
Недавно мое внимание привлек контракт Uniswap V3, где были использованы библиотеки для работы со структурами и и маппингами. Я не так часто встречал этот способ и решил рассказать о нем на канале.
Структуры в библиотеках
Посмотрите на код:
При том, что у библиотек не может быть собственного хранилища (storage), они могут работать и изменять память контракта, к которому подключены. В данном примере в бибилотеке мы создали структуру данных и функцию, которая инкрементирует значение. Обратите внимание на self, который в данном случае будет помогать обновлять память контракта.
Далее в самом контракте мы создаем переменную counter и подключаем библиотеку к структуре.
Порождение событий
Порождение событий также сопряжено с изменением памяти контракта и записи логов. Но библиотеки и тут можно использовать:
Хоть сам even был определен в библиотеке, логи будут записаны в нашем контракте. Единственное что, при попытке сязать сайт и контракт на получение данного события, будет возникать ошибка, поэтому также рекомендуют дублировать event и в самом контракте.
Структура библиотеки в маппинге
Еще один не самый популярный способ:
Тут мы создали мапиинг, в котором значение по ключу является структурой библиотеки. Не помню реальных примеров такого использования в протоколах, но такая реализация также возможна.
Использование библиотеки для маппинга
С простым примером для этого способа были некоторые трудности, поэтому покажу на примере контракта Uniswap.
Пришлось много вырезать для лучшего восприятия кода, но полную версию вы можете посмотреть по ссылке в начале поста.
Тут мы используем библиотеку для определенного вида маппинга с нужными размерностями ключа и значений. Далее в контракте в функции мы вызываем функцию flipTick по переменной маппинга tickBitmap и передаем в библиотеку сам маппинг и две аргумента.
Неделя продаж завершена и канал возвращается к своему привычному ритму.
Недавно мое внимание привлек контракт Uniswap V3, где были использованы библиотеки для работы со структурами и и маппингами. Я не так часто встречал этот способ и решил рассказать о нем на канале.
Структуры в библиотеках
Посмотрите на код:
Solidity
library CounterLib {
struct Counter { uint i; }
function incremented(Counter storage self) internal returns (uint) {
return ++self.i;
}
}
contract CounterContract {
using CounterLib for CounterLib.Counter;
CounterLib.Counter public counter;
function increment() public returns (uint) {
return counter.incremented();
}
}
При том, что у библиотек не может быть собственного хранилища (storage), они могут работать и изменять память контракта, к которому подключены. В данном примере в бибилотеке мы создали структуру данных и функцию, которая инкрементирует значение. Обратите внимание на self, который в данном случае будет помогать обновлять память контракта.
Далее в самом контракте мы создаем переменную counter и подключаем библиотеку к структуре.
Порождение событий
Порождение событий также сопряжено с изменением памяти контракта и записи логов. Но библиотеки и тут можно использовать:
Solidity
library EventEmitterLib {
function emitEvent(string memory s) internal{
emit Emit(s);
}
event Emit(string s);
}
contract EventEmitterContract {
using EventEmitterLib for string;
function emitEvent(string memory s) public {
s.emitEvent();
}
}
Хоть сам even был определен в библиотеке, логи будут записаны в нашем контракте. Единственное что, при попытке сязать сайт и контракт на получение данного события, будет возникать ошибка, поэтому также рекомендуют дублировать event и в самом контракте.
Структура библиотеки в маппинге
Еще один не самый популярный способ:
Solidity
library Library {
struct data {
uint val;
bool isValue;
}
}
contract Array{
using Library for Library.data;
mapping(address => Library.data) clusterContract;
function addCluster(address id) public view returns(bool){
if(clusterContract[id].isValue) revert();
return true;
}
}
Тут мы создали мапиинг, в котором значение по ключу является структурой библиотеки. Не помню реальных примеров такого использования в протоколах, но такая реализация также возможна.
Использование библиотеки для маппинга
С простым примером для этого способа были некоторые трудности, поэтому покажу на примере контракта Uniswap.
Solidity
library TickBitmap {
...
function flipTick(
mapping(int16 => uint256) storage self,
int24 tick,
int24 tickSpacing
) internal {
unchecked {
require(tick % tickSpacing == 0); // ensure that the tick is spaced
(int16 wordPos, uint8 bitPos) = position(tick / tickSpacing);
uint256 mask = 1 << bitPos;
self[wordPos] ^= mask;
}
}
...
}
contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
using TickBitmap for mapping(int16 => uint256);
mapping(int16 => uint256) public override tickBitmap;
function _updatePosition(
...
) private returns (Position.Info storage position) {
...
if (flippedLower) {
tickBitmap.flipTick(tickLower, tickSpacing);
}
if (flippedUpper) {
tickBitmap.flipTick(tickUpper, tickSpacing);
}
...
}
}
Пришлось много вырезать для лучшего восприятия кода, но полную версию вы можете посмотреть по ссылке в начале поста.
Тут мы используем библиотеку для определенного вида маппинга с нужными размерностями ключа и значений. Далее в контракте в функции мы вызываем функцию flipTick по переменной маппинга tickBitmap и передаем в библиотеку сам маппинг и две аргумента.
❤1
Простые инвариант тесты с Foundry
Мы уже разбирали как писать инвариант тесты в цикле постов про Foundry, где разбирали пример с WETH контрактом.
Я нашел еще один прекрасный пример урока от RareSkills по этой теме.
В нем разбираются как просты примеры, так и с Handlers / Actors, показываются примеры контрактов, команды forge и многое чего другого!
Если хотите сами еще раз разобраться, что такое инварианты, то эта статья как раз для вас. В целом на практическое изучение урока может уйти 2-3 часа! Но это того стоит!
Урок от RareSkills - https://www.rareskills.io/post/invariant-testing-solidity
Приятного обучения! Вопросы по Foundry вы можете смело задавать в чате.
#foundry #invariant
Мы уже разбирали как писать инвариант тесты в цикле постов про Foundry, где разбирали пример с WETH контрактом.
Я нашел еще один прекрасный пример урока от RareSkills по этой теме.
В нем разбираются как просты примеры, так и с Handlers / Actors, показываются примеры контрактов, команды forge и многое чего другого!
Если хотите сами еще раз разобраться, что такое инварианты, то эта статья как раз для вас. В целом на практическое изучение урока может уйти 2-3 часа! Но это того стоит!
Урок от RareSkills - https://www.rareskills.io/post/invariant-testing-solidity
Приятного обучения! Вопросы по Foundry вы можете смело задавать в чате.
#foundry #invariant
🔥4
Разбор Uniswap V2: swap(). Часть 1
Я все еще не могу подступиться к математике в DeFi, поэтому решил маленькими шажками изучать код контрактов и работу протоколов в целом. В RareSkills есть несколько прекрасных статей по темам Uniswap, Compound, Aave и других, которые можно читать и публиковать на канале. По сути, это будут короткие переводы.
И начнем мы сегодня с функции swap в контракте Uniswap V2. Именно она как раз на скрине.
1. Обратите внимание на область, помеченную желтым блоком. Именно тут токены после обмена пересылаются адресату. Примечательно то, что тут вообще нет вызовов для получения токенов от адресата для обмена! Это делается на другом уровне.
2. Причина этому то, что мы можем использовать эту функцию и для флеш займов, и оранжевая стрелка на 182 строке как раз и делает проверку на баланс токенов в резервах!
3. В самом начале функции в комментариях обозначено, что вызов swap() должен идти из другого контракта, где необходимо выполнять все проверки безопасности (красное подчеркивание на скрине).
4. Переменные _reserve0 и _reserve1 (синее подчеркивание) получаются на строке 161, и читаются по ходу функции, но их обновление происходит в другой служебной функции.
5. На 182 строке нет строгой проверки на равенство Константы (X x Y = K), здеь она выполнена по условию >=.
6. Балансы токенов (в переменных balance0 и balance1) читаются напрямую из контракта токенов по balanceOf().
7. Код на линии 172 исполняется только когда передаются данные при вызове.
Эти пункты помогут нам при дальнейшем разборе функции swap().
#swap #uniswap #v2
Я все еще не могу подступиться к математике в DeFi, поэтому решил маленькими шажками изучать код контрактов и работу протоколов в целом. В RareSkills есть несколько прекрасных статей по темам Uniswap, Compound, Aave и других, которые можно читать и публиковать на канале. По сути, это будут короткие переводы.
И начнем мы сегодня с функции swap в контракте Uniswap V2. Именно она как раз на скрине.
1. Обратите внимание на область, помеченную желтым блоком. Именно тут токены после обмена пересылаются адресату. Примечательно то, что тут вообще нет вызовов для получения токенов от адресата для обмена! Это делается на другом уровне.
2. Причина этому то, что мы можем использовать эту функцию и для флеш займов, и оранжевая стрелка на 182 строке как раз и делает проверку на баланс токенов в резервах!
3. В самом начале функции в комментариях обозначено, что вызов swap() должен идти из другого контракта, где необходимо выполнять все проверки безопасности (красное подчеркивание на скрине).
4. Переменные _reserve0 и _reserve1 (синее подчеркивание) получаются на строке 161, и читаются по ходу функции, но их обновление происходит в другой служебной функции.
5. На 182 строке нет строгой проверки на равенство Константы (X x Y = K), здеь она выполнена по условию >=.
6. Балансы токенов (в переменных balance0 и balance1) читаются напрямую из контракта токенов по balanceOf().
7. Код на линии 172 исполняется только когда передаются данные при вызове.
Эти пункты помогут нам при дальнейшем разборе функции swap().
#swap #uniswap #v2
👍5👌2
Uniswap V2: swap(). Часть 2
Из прошлого поста мы узнали, что функция swap() не имеет кода для получения пользовательских токенов, что может открывать опцию флеш займов!
Контракт может просто запросить некоторое количество токенов, которое требуется к займу (отметка А на скрине), без какого-либо залога. Сами токены отправляются чуть позже (отметка В).
Вместе с вызовом функции также должны быть отправлены данные, которые позже перенаправляются в контракт, который реализует интерфейс IUniswapV2Callee, где присутствует всего лишь одна функция:
Вообще, нужно сказать, что вызывать функцию swap(), т.е. обменивать токены или брать флеш займы, могут только контракты, так как ЕОА не могут одновременно отправлять токены и вызывать свап в одной транзакции без помощи смарт контрактов.
#uniswap #v2 #swap
Из прошлого поста мы узнали, что функция swap() не имеет кода для получения пользовательских токенов, что может открывать опцию флеш займов!
Контракт может просто запросить некоторое количество токенов, которое требуется к займу (отметка А на скрине), без какого-либо залога. Сами токены отправляются чуть позже (отметка В).
Вместе с вызовом функции также должны быть отправлены данные, которые позже перенаправляются в контракт, который реализует интерфейс IUniswapV2Callee, где присутствует всего лишь одна функция:
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
Вообще, нужно сказать, что вызывать функцию swap(), т.е. обменивать токены или брать флеш займы, могут только контракты, так как ЕОА не могут одновременно отправлять токены и вызывать свап в одной транзакции без помощи смарт контрактов.
#uniswap #v2 #swap
🔥5
Uniswap V2: swap(). Часть 3
Продолжаем разбирать функцию свапа и сегодня поговорим о том, как Юнисвап понимает, сколько токенов было послано в пул.
За это отвечают, по сути, всего две строки (176-177):
Из предыдущих постов мы помним, что переменные _reserve0 и _reserve1 не обновляются внутри функции. Они отражают баланс контракта до того, как новые токены были добавлены при свапе.
С каждым из двух токенов в паре может случится одна из ситуаций:
1. В пуле произошло чистое увеличение количества определенного токена;
2. В пуле произошло чистое уменьшение количества определенного токена или оно осталось неизменным;
То как код контракта определяет ситуацию,, можно переложить на формулу:
currentContractbalanceX > _reserveX - _amountXOut
или
currentContractBalanceX > previousContractBalanceX - _amountXOut
В случае, если зафиксировано уменьшение, то тернарный оператор возвращает ноль, в противном случае учитывается увеличение количества токенов.
amountXIn = balanceX - (_reserveX - amountXOut)
Также можно указать на то, что в любом случае _reserveX > amountXOut, так как идет проверка на строке 162:
Несколько примеров:
1. Предположим, что предыдущий баланс был равен 10, amountOut = 0, и currentBalance = 12. Это значит, что было добавлено 2 токена и amountXIn будет равен 2.
2. Или предыдущий баланс равен 10, amountOut = 7, и currentBalance = 3, поэтому amountXIn = 0.
3. Предыдущий баланс был равен 10, amountOut = 7, и currentBalance = 2. В этом случае amountXIn будет также равен 0, а не -1. Пул получает уменьшение на 8 токенов, а amountXIn не может быть негативным числом.
4. Или же предыдущий баланс равен 10, amountOut = 6. Если currentBalance равен 18, это означает, что пользователь "занял" 6 токенов и вернул 8.
В завершении можно сказать, что amount0In и amount1In отражают чистое увеличение токенов при добавлении в пул, и равняются нулю, если идет уменьшение.
#uniswap #swap
Продолжаем разбирать функцию свапа и сегодня поговорим о том, как Юнисвап понимает, сколько токенов было послано в пул.
За это отвечают, по сути, всего две строки (176-177):
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
Из предыдущих постов мы помним, что переменные _reserve0 и _reserve1 не обновляются внутри функции. Они отражают баланс контракта до того, как новые токены были добавлены при свапе.
С каждым из двух токенов в паре может случится одна из ситуаций:
1. В пуле произошло чистое увеличение количества определенного токена;
2. В пуле произошло чистое уменьшение количества определенного токена или оно осталось неизменным;
То как код контракта определяет ситуацию,, можно переложить на формулу:
currentContractbalanceX > _reserveX - _amountXOut
или
currentContractBalanceX > previousContractBalanceX - _amountXOut
В случае, если зафиксировано уменьшение, то тернарный оператор возвращает ноль, в противном случае учитывается увеличение количества токенов.
amountXIn = balanceX - (_reserveX - amountXOut)
Также можно указать на то, что в любом случае _reserveX > amountXOut, так как идет проверка на строке 162:
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
Несколько примеров:
1. Предположим, что предыдущий баланс был равен 10, amountOut = 0, и currentBalance = 12. Это значит, что было добавлено 2 токена и amountXIn будет равен 2.
2. Или предыдущий баланс равен 10, amountOut = 7, и currentBalance = 3, поэтому amountXIn = 0.
3. Предыдущий баланс был равен 10, amountOut = 7, и currentBalance = 2. В этом случае amountXIn будет также равен 0, а не -1. Пул получает уменьшение на 8 токенов, а amountXIn не может быть негативным числом.
4. Или же предыдущий баланс равен 10, amountOut = 6. Если currentBalance равен 18, это означает, что пользователь "занял" 6 токенов и вернул 8.
В завершении можно сказать, что amount0In и amount1In отражают чистое увеличение токенов при добавлении в пул, и равняются нулю, если идет уменьшение.
#uniswap #swap
❤2
Uniswap V2: swap(). Часть 4
Сегодня мы поговорим про всем известную константу XY = K, а именно, как она работает при свапах и расчете комиссии за свап.
Посмотрите на эти строки в функции свапа:
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
Uniswap V2 взымает установленные в коде 0.3% за каждый свап, поэтому мы видим цифры 1000 и 3.
Для упрощения объяснения работы константы, давайте для начала представим, что Юнисвап вообще не берет комиссию и напишем небольшую проверку, заменяющую предыдущие две строки:
require(balance0 * balance1 >= reserve0 * reserve1, "K");
Если внимательно присмотреться, К - это не совсем константа, не смотря на то, что в документации и примерах ее всегда называют AMM “constant product formula”, или константа формулы продукта.
Объяснение может быть очень простым: смотрите, когда кто-то переводит токены в пул и изменяет константу К, мы же не хотим его ограничивать, так как сами заинтересованы в обогащении наших поставщиков ликвидности и увеличении пула?
Так и Юнисвап не ограничивает вас от перевода очень больших объемов токенов и изменении К. Когда К становится больше, объем пула становится больше, а это именно то, что хотят поставщики ликвидности.
Теперь вернемся к процентам за перевод.
Мы хотим, чтобы не только К становился больше, мы хотим, чтобы он становился больше как минимум на такое количество, чтобы можно было взымать комиссию!
Глядя на код, мы можем сказать, что сама комиссия берется не по размеру пула, а по объему переводов. И применяется только к количеству токенов, которые поступают в пул, а не тех, что отправляются пользователю!
Например:
1. Мы отправили 1000 токенов_0 и забрали 1000 токенов_1. Нам потребуется заплатить комиссию в 3 токена_0;
2. Мы берем займ 1000 токенов_0 и должны будем вернуть 1000 токенов_0 плюс комиссию в 3 токена_0. Токен_1 здесь вообще не участвует, как можно заметить.
Теперь, глядя на изначальные строки расчета комиссии при свапе, мы понимаем, как они работают. Умножение на 1000 необходимо, так как Solidity не работает с "плавающей точкой" и требуется делать дополнительные умножения для проверки получения этих 0.3% комиссии.
#uniswap #constant #swap #fee
Сегодня мы поговорим про всем известную константу XY = K, а именно, как она работает при свапах и расчете комиссии за свап.
Посмотрите на эти строки в функции свапа:
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
Uniswap V2 взымает установленные в коде 0.3% за каждый свап, поэтому мы видим цифры 1000 и 3.
Для упрощения объяснения работы константы, давайте для начала представим, что Юнисвап вообще не берет комиссию и напишем небольшую проверку, заменяющую предыдущие две строки:
require(balance0 * balance1 >= reserve0 * reserve1, "K");
Если внимательно присмотреться, К - это не совсем константа, не смотря на то, что в документации и примерах ее всегда называют AMM “constant product formula”, или константа формулы продукта.
Объяснение может быть очень простым: смотрите, когда кто-то переводит токены в пул и изменяет константу К, мы же не хотим его ограничивать, так как сами заинтересованы в обогащении наших поставщиков ликвидности и увеличении пула?
Так и Юнисвап не ограничивает вас от перевода очень больших объемов токенов и изменении К. Когда К становится больше, объем пула становится больше, а это именно то, что хотят поставщики ликвидности.
Теперь вернемся к процентам за перевод.
Мы хотим, чтобы не только К становился больше, мы хотим, чтобы он становился больше как минимум на такое количество, чтобы можно было взымать комиссию!
Глядя на код, мы можем сказать, что сама комиссия берется не по размеру пула, а по объему переводов. И применяется только к количеству токенов, которые поступают в пул, а не тех, что отправляются пользователю!
Например:
1. Мы отправили 1000 токенов_0 и забрали 1000 токенов_1. Нам потребуется заплатить комиссию в 3 токена_0;
2. Мы берем займ 1000 токенов_0 и должны будем вернуть 1000 токенов_0 плюс комиссию в 3 токена_0. Токен_1 здесь вообще не участвует, как можно заметить.
Теперь, глядя на изначальные строки расчета комиссии при свапе, мы понимаем, как они работают. Умножение на 1000 необходимо, так как Solidity не работает с "плавающей точкой" и требуется делать дополнительные умножения для проверки получения этих 0.3% комиссии.
#uniswap #constant #swap #fee
❤4👍2
Uniswap V2: swap(). Часть 5
Ну, и заключительная часть про свап функцию, в которой поговорим про обновление резервов.
Если посмотреть на функцию, то можно увидеть, что все действие происходит в другой служебной функции:
_update(balance0, balance1, _reserve0, _reserve1);
Именно она и показана на скрине.
Там есть логика для обновления данных оракула, но этого мы коснемся позже. В данном случае нас интересуют строки 82-83, там обновляются переменные состояния контракта:
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
При свапе могут возникнуть некоторые проблемы:
1. Параметр amountIn не обязательно может быть оптимальным для свапа и тогда пользователь переплачивает за сам свап;
2. AmountOut не такой гибкий, как мог бы быт, так как он передается в функцию в качестве аргумента. Если amountIn окажется недостаточным по отношению к AmountOut, то транзакция откатится;
Если условия могут получиться, если один пользователь сделает фронтран транзакции другого пользователя и изменит ратио активов в пуле.
P.S. Напомню, что эти несколько постов были написаны, как перевод статьи от RareSkills.
#uniswap #swap
Ну, и заключительная часть про свап функцию, в которой поговорим про обновление резервов.
Если посмотреть на функцию, то можно увидеть, что все действие происходит в другой служебной функции:
_update(balance0, balance1, _reserve0, _reserve1);
Именно она и показана на скрине.
Там есть логика для обновления данных оракула, но этого мы коснемся позже. В данном случае нас интересуют строки 82-83, там обновляются переменные состояния контракта:
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
При свапе могут возникнуть некоторые проблемы:
1. Параметр amountIn не обязательно может быть оптимальным для свапа и тогда пользователь переплачивает за сам свап;
2. AmountOut не такой гибкий, как мог бы быт, так как он передается в функцию в качестве аргумента. Если amountIn окажется недостаточным по отношению к AmountOut, то транзакция откатится;
Если условия могут получиться, если один пользователь сделает фронтран транзакции другого пользователя и изменит ратио активов в пуле.
P.S. Напомню, что эти несколько постов были написаны, как перевод статьи от RareSkills.
#uniswap #swap
🔥4
Разбор Uniswap V2: mint() & burn()
Переходим потихоньку к другим функциям в Юнисвап, и в этот раз поговорим о минте и сжигании.
На скрине выше представлена функция сжигания токенов, давайте разберем ее:
1. На линии 140 (фиолетовая отметка) ликвидность измеряется в LP токенах, которые в данный момент есть на контракте. Предполагается, что пользователь заранее отправит LP токены перед вызовом burn(). При этом, в рамках безопасности, перевод LP токенов и вызов сжигания следует производить в одной транзакции, чтобы никто не смог сжечь ваши токены и получить другие бесплатно.
2. Красная отметка (линии 142 и 154) отвечает за комиссию, которая может быть введена Юнисвапом для поставщиков ликвидности. На данный момент она отсутствует.
3. Оранжевая отметка (линии 144-145) показывает количество токенов, которые получит пользователь за свои LP токены.
4. Голубая отметка (147-149) - место, где LP токены сжигаются и token0 и token1 пересылаются пользователю.
5. Желтая (линии 150-151) - получают обновленные балансы токенов, для того, чтобы ниже, на линии 153, могла быть вызвана функция для обновления данных _update().
Количество токенов, которые пользователь получит после сжигания, зависит от ратио LP токенов к totalSupply LP. Однако, totalSupply может измениться перед самым моментом сжигания, поэтому для безопасности требуется учитывать проскальзывание (slippage) в транзакции.
#uniswap #v2 #burn
Переходим потихоньку к другим функциям в Юнисвап, и в этот раз поговорим о минте и сжигании.
На скрине выше представлена функция сжигания токенов, давайте разберем ее:
1. На линии 140 (фиолетовая отметка) ликвидность измеряется в LP токенах, которые в данный момент есть на контракте. Предполагается, что пользователь заранее отправит LP токены перед вызовом burn(). При этом, в рамках безопасности, перевод LP токенов и вызов сжигания следует производить в одной транзакции, чтобы никто не смог сжечь ваши токены и получить другие бесплатно.
2. Красная отметка (линии 142 и 154) отвечает за комиссию, которая может быть введена Юнисвапом для поставщиков ликвидности. На данный момент она отсутствует.
3. Оранжевая отметка (линии 144-145) показывает количество токенов, которые получит пользователь за свои LP токены.
4. Голубая отметка (147-149) - место, где LP токены сжигаются и token0 и token1 пересылаются пользователю.
5. Желтая (линии 150-151) - получают обновленные балансы токенов, для того, чтобы ниже, на линии 153, могла быть вызвана функция для обновления данных _update().
Количество токенов, которые пользователь получит после сжигания, зависит от ратио LP токенов к totalSupply LP. Однако, totalSupply может измениться перед самым моментом сжигания, поэтому для безопасности требуется учитывать проскальзывание (slippage) в транзакции.
#uniswap #v2 #burn
👍2❤1
Разбор Uniswap V2: mint() & burn()
А теперь поговорим о функции минта, которая представлена на скрине выше.
Некоторый функционал в ней схож с функцией burn(), поэтому некоторые моменты можно посмотреть в предыдущем посте.
В пуле может быть, как бы, два состояние: без ликвидности и с ликвидность. Оба этих случая учитываются в коде (отмечены желтым маркером). В данном разборе мы коснёмся второго случая, а именно того, как рассчитывается объем минта при депозите токенов в пул.
Посмотрите на эту строку:
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
именно тут происходит вся магия. Здесь выбирается минимальное значение из двух возможных, которые будут сминчены пользователю.
Например, у нас есть 10 токенов_1 и 10 токенов_2. Если пользователь добавит 10 токенов_1 и 0 токенов_2, то он получит 0 LP токенов - (10/10, 0/10)! Или другой пример: если пользователь отправит 5% токена_1 и 10% токена_2, он получит всего 5% LP токенов!
Причина такому подсчёту проста: таким образом протокол мотивирует своих пользователей добавлять оба токена в пул, сохраняя его ратио. Зачем? Давайте разбираться.
Представим, что пул содержит в себе 10 токенов_1 и всего 1 токен_2, а также количество LP равно 1. Также допустим, что цена каждого токена 100$ (за каждый), т.е. общая стоимость пула равна 200$.
Если мы будет минтить по максимальному ратио, хакер может добавить всего 1 токен_2 (стоимостью 100$) и увеличить общую стоимость пула до 300$, т.е. увеличение на 50%. Также он получит 1 LP токен, что фактически равно 50% от общего количества LP токенов. В итоге получается, что он теперь контролирует 50% от пула стоимостью 300$, вложив всего 100$! А это вполне может считаться воровством у других поставщиков ликвидности.
Также как и в случае работы burn() функции, общее количество LP токенов и баланс пула может быть изменен прямо перед нашей транзакцией, поэтому тут также требуется писать дополнительные проверки на проскальзывание (slippage).
#unoswap #v2 #mint
А теперь поговорим о функции минта, которая представлена на скрине выше.
Некоторый функционал в ней схож с функцией burn(), поэтому некоторые моменты можно посмотреть в предыдущем посте.
В пуле может быть, как бы, два состояние: без ликвидности и с ликвидность. Оба этих случая учитываются в коде (отмечены желтым маркером). В данном разборе мы коснёмся второго случая, а именно того, как рассчитывается объем минта при депозите токенов в пул.
Посмотрите на эту строку:
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
именно тут происходит вся магия. Здесь выбирается минимальное значение из двух возможных, которые будут сминчены пользователю.
Например, у нас есть 10 токенов_1 и 10 токенов_2. Если пользователь добавит 10 токенов_1 и 0 токенов_2, то он получит 0 LP токенов - (10/10, 0/10)! Или другой пример: если пользователь отправит 5% токена_1 и 10% токена_2, он получит всего 5% LP токенов!
Причина такому подсчёту проста: таким образом протокол мотивирует своих пользователей добавлять оба токена в пул, сохраняя его ратио. Зачем? Давайте разбираться.
Представим, что пул содержит в себе 10 токенов_1 и всего 1 токен_2, а также количество LP равно 1. Также допустим, что цена каждого токена 100$ (за каждый), т.е. общая стоимость пула равна 200$.
Если мы будет минтить по максимальному ратио, хакер может добавить всего 1 токен_2 (стоимостью 100$) и увеличить общую стоимость пула до 300$, т.е. увеличение на 50%. Также он получит 1 LP токен, что фактически равно 50% от общего количества LP токенов. В итоге получается, что он теперь контролирует 50% от пула стоимостью 300$, вложив всего 100$! А это вполне может считаться воровством у других поставщиков ликвидности.
Также как и в случае работы burn() функции, общее количество LP токенов и баланс пула может быть изменен прямо перед нашей транзакцией, поэтому тут также требуется писать дополнительные проверки на проскальзывание (slippage).
#unoswap #v2 #mint
👍5🔥2