forge testТакже вы можете получить больше информации о транзакциях в тестах, уточнив команду до:
forge test -vvВсего 5 уровней уточнений:
1. По умолчанию срабатывает при обычно команде тестов;
2. -vv - для простого отображения ошибок и console.log;
3. -vvv - для отображения путей теста;
4. -vvvv - более подробное отображения действий теста;
5. -vvvvv - также подробное отображение путей;
Последние два часто применяются при дебагинге.
Итак, прописываем команду и видим, что тест прошел, о чем символизирует надпись [PASS] напротив теста в терминале.
P.S. Что интересно, так это то, что, если раскомментировать console2.log, то он будет показывать значение 0, хотя тест будет пройден!
Если же мы подправим наш тест до:
assertEq(counter.number(), 2);То после команды в терминале, увидим надпись [FAIL] и информацию об ошибке.
Так, думаю, на сегодня этого достаточно, получился какой-то большой пост. А завтра поговорим о других командах для проверки результатов функций.
#foundry #lesson11
👍9
Foundry с 0. Часть 12
Продолжаем тему предыдущего поста и поговорим вообще о вариантах сравнения ожидаемых результатов исполнения функции в тестах с полученными.
Тогда мы узнали о первой функции-помощнике - asserteq(), которая принимает два значения (ожидаемое и полученное) и сравнивает их на равенство. Другими словами, в данном случае, чтобы тест прошел они должны быть равны друг другу.
При этом есть еще множество других сравнений. Например, создадим новый тест, в котором установим переменную number = 150.
assertGt(counter.number(), 140); //проверка, если "а" больше "б";
assertGe(counter.number(), 150); // проверка, если "а" больше или равно "б";
assertLt(counter.number(), 170); // проверка, если "а" меньше "б";
assertLe(counter.number(), 150); // проверка, если "а" меньше или равно "б";
Есть еще две более сложные команды:
assertApproxEqAbs();
assertApproxEqRel();
Первая проверяет, что значение "а" примерно равно "б", но в пределах определенной дельты. Другими словами, "а" не должно превышать "б+с", например:
Кроме того, вы также можете проверять еще несколько параметров и не только числовых:
1. assertTrue(); - проверка, что результат будет равен true
2. assertFalse(); - проверка, что результат будет равен false
3. assertEqDecimal(); - проверка, на равенство decimals
4. assertEq32(); - проверка, что результат будет равен bytes32
5. assertEq0(); - проверка, что результат будет равен bytes32
Хоть они используют реже, чем остальные, все равно их также можно встретить в крупных проектах.
#foundry #lesson12
Продолжаем тему предыдущего поста и поговорим вообще о вариантах сравнения ожидаемых результатов исполнения функции в тестах с полученными.
Тогда мы узнали о первой функции-помощнике - asserteq(), которая принимает два значения (ожидаемое и полученное) и сравнивает их на равенство. Другими словами, в данном случае, чтобы тест прошел они должны быть равны друг другу.
При этом есть еще множество других сравнений. Например, создадим новый тест, в котором установим переменную number = 150.
function test_setNumber() public {
counter.setNumber(150);
}
Теперь с помощью последующих команды мы можем проверить, что:assertGt(counter.number(), 140); //проверка, если "а" больше "б";
assertGe(counter.number(), 150); // проверка, если "а" больше или равно "б";
assertLt(counter.number(), 170); // проверка, если "а" меньше "б";
assertLe(counter.number(), 150); // проверка, если "а" меньше или равно "б";
Есть еще две более сложные команды:
assertApproxEqAbs();
assertApproxEqRel();
Первая проверяет, что значение "а" примерно равно "б", но в пределах определенной дельты. Другими словами, "а" не должно превышать "б+с", например:
assertApproxEqAbs(counter.number(), 200, 200);Вторая же команда делает примерно тоже, но в процентном соотношении, где 100% = 1е18, и пример может выглядеть так:
assertApproxEqRel(counter.number(), 150, 0.4e18);Эти две команды могут быть полезны при работе с DeFi протоколами, где часто идет работа с дельтами значений и ценами на токены.
Кроме того, вы также можете проверять еще несколько параметров и не только числовых:
1. assertTrue(); - проверка, что результат будет равен true
2. assertFalse(); - проверка, что результат будет равен false
3. assertEqDecimal(); - проверка, на равенство decimals
4. assertEq32(); - проверка, что результат будет равен bytes32
5. assertEq0(); - проверка, что результат будет равен bytes32
Хоть они используют реже, чем остальные, все равно их также можно встретить в крупных проектах.
#foundry #lesson12
👍1
Foundry с 0. Часть 13
Вместе с тем, что в своих тестах мы проверяем полученные значение к ожидаемым, нам требуется также проверять и правильную отработку ошибок и событий. Давайте поговорим, как это происходит.
Для начала добавим новую функцию в наш контракт Counter.sol:
Как это протестировать?
Если мы просто хотим убедиться, что данная функция не сработает, если число будет отличное от 100, то можно сделать так:
Когда мы пишем testFail, необходимо чтобы транзакция упала - тогда тест пройдет. Если же транзакция завершится нормально, то тест будет "завален". Тут, скажем, немного обратная от привычной логика.
Чуть позже мы увидим более практическое ее применение, а сейчас поговорим, как отслеживать вызов ошибок при тестах.
Как протестировать require и условия с кастомными ошибками?
В Foundry есть так называемые читкоды, которые сильно облегчаю работу с тестами и взаимодействие с локальным блокчейном. Вместе с библиотекой Test мы можем использовать огромное количество читов.
Одним из них является:
Далее посмотрим на два вида кастомных ошибок.
Для начала добавим их и две новые функции в контракт:
Я видел в нескольких видео, а также в официальной документации, что порой для кастомных ошибок можно использовать более простые конструкции, типа:
Еще интереснее дела обстоят с порождением событий и их тестированием.
Объявим его в нашем контракте Counter:
Далее нам нужно породить это событие в нашем тесте и уже после вызвать функцию.
Немного странно, но так это работает.
Вместе с тем, что в своих тестах мы проверяем полученные значение к ожидаемым, нам требуется также проверять и правильную отработку ошибок и событий. Давайте поговорим, как это происходит.
Для начала добавим новую функцию в наш контракт Counter.sol:
function setNumber100(uint256 newNumber) public {
require(newNumber == 100, 'Wrong numer!');
number = newNumber;
}
Она просто проверяет, чтобы значение для установки в переменную было обязательно равно 100, иначе происходит откат транзакции.Как это протестировать?
Если мы просто хотим убедиться, что данная функция не сработает, если число будет отличное от 100, то можно сделать так:
function testFail_setNumber100() public {
counter.setNumber100(150);
}
Обратите внимание на testFail. Так мы говорим Foundry, что ожидаем откат транзакции. Другими словами, если сейчас транзакция не пройдет, то результатом будет fail и таким образом тест удастся. Поняли, в чем дело?Когда мы пишем testFail, необходимо чтобы транзакция упала - тогда тест пройдет. Если же транзакция завершится нормально, то тест будет "завален". Тут, скажем, немного обратная от привычной логика.
Чуть позже мы увидим более практическое ее применение, а сейчас поговорим, как отслеживать вызов ошибок при тестах.
Как протестировать require и условия с кастомными ошибками?
В Foundry есть так называемые читкоды, которые сильно облегчаю работу с тестами и взаимодействие с локальным блокчейном. Вместе с библиотекой Test мы можем использовать огромное количество читов.
Одним из них является:
vm.expectRevert();Тут мы как бы обращаемся к vm (virtual machine) и говорим, что ожидаем реверт с некоторым сообщением. Наш тест может выглядеть так:
function test_setNumber_Revert() public {
vm.expectRevert("Wrong numer!");
counter.setNumber100(150);
}
Обратите внимание, что читкод мы пишем до исполнения функции, а не после нее. Это очень важно для хода выполнения тестов! Более того, в этом случае мы пишем название теста просто test_testName, а не testFail_testName, т.е. слово fail тут уже не нужно. Далее посмотрим на два вида кастомных ошибок.
Для начала добавим их и две новые функции в контракт:
error WrongNum(address caller, uint256 num);Тесты для обеих функций могут выглядеть так:
error WrongSet();
function setNumber150(uint256 newNumber) public {
if (newNumber != 150) revert WrongSet();
number = newNumber;
}
function setNumber200(uint256 newNumber) public {
if (newNumber != 200) revert WrongNum(msg.sender, newNumber);
number = newNumber;
}
function test_setNumber_Revert150() public {
vm.expectRevert(abi.encodeWithSignature('WrongSet()'));
counter.setNumber150(200);
}
function test_setNumber_Revert200() public {
vm.expectRevert(abi.encodeWithSignature("WrongNum(address,uint256)", address(this),150));
counter.setNumber200(150);
}
Ошибки и их аргументы мы оборачиваем в abi.encodeWithSignature() и это становится нашим ожидаемым результатом тестов.Я видел в нескольких видео, а также в официальной документации, что порой для кастомных ошибок можно использовать более простые конструкции, типа:
function test_setNumber_Revert150() public {
vm.expectRevert('WrongSet.selector);
counter.setNumber150(200);
}
Оба варианта рабочие. Можете смело использовать их в своих тестах.Еще интереснее дела обстоят с порождением событий и их тестированием.
Объявим его в нашем контракте Counter:
event NewEvent(uint256 num);и добавим запись в функцию increment() в виде:
emit NewEvent(number);Наш тест для событий может выглядеть так:
function test_IncrementEvent() public {
vm.expectEmit(true, true, true, false);
emit NewEvent(counter.number());
counter.increment();
}
Сначала мы используем новый читкод vm.expectEmit(), который принимает четыре аргумента в качестве булевых значение. Первые три устанавливаются для отслеживания indexed параметров в событии, последний - нужна ли проверка входных значений. Далее нам нужно породить это событие в нашем тесте и уже после вызвать функцию.
Немного странно, но так это работает.
👍2
Обратите внимание, для того чтобы протестировать порождение событий в контракте-тесте также нужно создать такой же event!
В конце неделе я выложу на канал итоговый сборный файл тестов и контракта, который мы напишем, чтобы бы вы могли сами взглянуть на полный код и попрактиковаться.
#foundry #lesson13
В конце неделе я выложу на канал итоговый сборный файл тестов и контракта, который мы напишем, чтобы бы вы могли сами взглянуть на полный код и попрактиковаться.
#foundry #lesson13
👍1🔥1
Foundry с 0. Часть 14
В прошлом посте мы впервые встретились с читкодами, помните vm.expectRevert() и vm.expectEmit()?
Читкоды Foundry это специальные служебные функции в программе, которые помогают нам писать качественные тесты и манипулировать состоянием локального блокчейна! Вообще это очень крутая штука.
В течение цикла постов мы будем много раз встречать с ними, но для начала стоит разбить их по нескольким категориям использования.
1. Манипуляции локальным блокчейном
С помощью этой категории вы можете управлять состоянием блокчейна для своих тестов. Например, вы можете "перемотать время" или перескочить некоторое количество блоков, пополнить свои счета токенов и nft, установить block.difficulty или block.basefee, управлять nonce и многое другое.
2. Assertions
Другая категория, которая позволяет проводить сравнения ожидаемых результатов действия функции с полученными, примером подобных функций могут служить те же vm.expectRevert() и vm.expectEmit().
3. Форк сетей
Категория функций для работы с форками реальных сетей блокчейна для ваших тестов. Бывает крайне полезно оценить работу контракта на всех сетях, куда планируется загрузка!
4. Работа с переменными среды
Вы можете получать значения и обрабатывать данные из переменных среды, которые помогают управлять разработкой проекта.
5. Функции помощники
Категория функций, которая помогает обрабатывать входные данные и выдавать их в нужном виде, например в байтах и т.д. А также те, которые позволяют работать с "фиктивными" адресами пользователей для ваших тестов.
6. Остальные
Есть также редко используемая категория, которая помогает работать с файлами и rpc ссылками, а также проводить более сложные тесты, типа фаззинга и инвариантов.
Работа с "фиктивными" адресами пользователей
В этом посте мы затронем тему, как писать тесты с использованием подставных адресов.
Для большего понимая объясню, что я имею ввиду под "фиктивными адресами". Это такие адреса, которые создаются только на время проведения теста. Их нельзя использовать больше нигде. Только в ваших тестах, как например, для проверки доступа к функциям.
Для этих целей используется специальный читкод - vm.addr(uint256);
Часто в больших проектах я встречал некий условный паттерн создания и работы с подобными адресами в контрактах, которым я хочу поделиться.
Итак, продолжим работать с нашим контрактом Counter и введем концепцию владельца, добавив в конструктор owner = msg.sender, и создав соответствующую переменную. Более того, для простых тестов добавим еще модификатор и простую функцию:
А теперь самое интересное, переходим в наш файл для тестов Counter.t.sol и создаем адреса пользователей.
Название переменных для ключевых ролей лучше писать заглавными буквами, как константы. В скобках vm.addr() вы можете написать вообще любое число uint256, а Foundry создаст на его основе рабочий уникальный адрес.
Также, в современных тестах используются специальный лейблы, для более понятного отображения адресов при дебаггинге, т.е. вместо адреса там будет стоять имя переменной.
Добавить лейбл к адресу очень просто:
P.S. Чаще всего видел, что лейблы помещали в setUp() функцию.
Теперь еще один интересный момент.
Когда мы в setUp() создаем новый контракт Counter и помещаем его в переменную, то владельцем становится адрес контракта нашего теста - CounterTest. Для "подмены" адреса вызывающего можно использовать еще несколько читкодов - vm.prank(), vm.startPrank() и vm.stopPrank().
vm.prank() - работает только на вызов, который идет после него, а vm.startPrank() - работает до тех пор, пока его не остановит vm.stopPrank().
В прошлом посте мы впервые встретились с читкодами, помните vm.expectRevert() и vm.expectEmit()?
Читкоды Foundry это специальные служебные функции в программе, которые помогают нам писать качественные тесты и манипулировать состоянием локального блокчейна! Вообще это очень крутая штука.
В течение цикла постов мы будем много раз встречать с ними, но для начала стоит разбить их по нескольким категориям использования.
1. Манипуляции локальным блокчейном
С помощью этой категории вы можете управлять состоянием блокчейна для своих тестов. Например, вы можете "перемотать время" или перескочить некоторое количество блоков, пополнить свои счета токенов и nft, установить block.difficulty или block.basefee, управлять nonce и многое другое.
2. Assertions
Другая категория, которая позволяет проводить сравнения ожидаемых результатов действия функции с полученными, примером подобных функций могут служить те же vm.expectRevert() и vm.expectEmit().
3. Форк сетей
Категория функций для работы с форками реальных сетей блокчейна для ваших тестов. Бывает крайне полезно оценить работу контракта на всех сетях, куда планируется загрузка!
4. Работа с переменными среды
Вы можете получать значения и обрабатывать данные из переменных среды, которые помогают управлять разработкой проекта.
5. Функции помощники
Категория функций, которая помогает обрабатывать входные данные и выдавать их в нужном виде, например в байтах и т.д. А также те, которые позволяют работать с "фиктивными" адресами пользователей для ваших тестов.
6. Остальные
Есть также редко используемая категория, которая помогает работать с файлами и rpc ссылками, а также проводить более сложные тесты, типа фаззинга и инвариантов.
Работа с "фиктивными" адресами пользователей
В этом посте мы затронем тему, как писать тесты с использованием подставных адресов.
Для большего понимая объясню, что я имею ввиду под "фиктивными адресами". Это такие адреса, которые создаются только на время проведения теста. Их нельзя использовать больше нигде. Только в ваших тестах, как например, для проверки доступа к функциям.
Для этих целей используется специальный читкод - vm.addr(uint256);
Часто в больших проектах я встречал некий условный паттерн создания и работы с подобными адресами в контрактах, которым я хочу поделиться.
Итак, продолжим работать с нашим контрактом Counter и введем концепцию владельца, добавив в конструктор owner = msg.sender, и создав соответствующую переменную. Более того, для простых тестов добавим еще модификатор и простую функцию:
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner () {
require(msg.sender == owner, "Now an owner!");
_;
}
function setNumberOwner(uint256 newNumber) public onlyOwner{
number = newNumber;
}
А теперь самое интересное, переходим в наш файл для тестов Counter.t.sol и создаем адреса пользователей.
address ADMIN = vm.addr(23432432);
address HACKER = vm.addr(223343212432);
Название переменных для ключевых ролей лучше писать заглавными буквами, как константы. В скобках vm.addr() вы можете написать вообще любое число uint256, а Foundry создаст на его основе рабочий уникальный адрес.
Также, в современных тестах используются специальный лейблы, для более понятного отображения адресов при дебаггинге, т.е. вместо адреса там будет стоять имя переменной.
Добавить лейбл к адресу очень просто:
vm.label(ADMIN, "ADMIN");
vm.label(HACKER, "HACKER");
P.S. Чаще всего видел, что лейблы помещали в setUp() функцию.
Теперь еще один интересный момент.
Когда мы в setUp() создаем новый контракт Counter и помещаем его в переменную, то владельцем становится адрес контракта нашего теста - CounterTest. Для "подмены" адреса вызывающего можно использовать еще несколько читкодов - vm.prank(), vm.startPrank() и vm.stopPrank().
vm.prank() - работает только на вызов, который идет после него, а vm.startPrank() - работает до тех пор, пока его не остановит vm.stopPrank().
👍1🔥1👏1
Другими словами, если вы хотите, чтобы только последующий вызов был совершен из-под определенного адреса - используйте простой vm.prank(), если же требуется череда вызовов из-под адреса, то лучше использовать два последних читкода.
Итак, в функцию setUp(), перед созданием контракта Counter, помещаем следующую строку:
vm.prank(ADMIN);
И наш контракт Counter теперь будет иметь владельцем адрес ADMIN. Проверим это в тестах. Напишем простой тест:
Здесь мы определяем в тесте, что последующие вызовы будут идти от имени адреса админа и делаем вызов на изменение числа.
Если вместо ADMIN мы подставим адрес HACKER, то тест провалится, что обозначает правильную работу доступа к функции.
Кстати, вот небольшой лайфхак по работе со startPrank и stopPrank, который я подсмотрел в одном из конкурсных протоколов.
Обычно мы пишем тест и уже перед тем, как вызвать необходимую функции из под нужного аккаунта (владельца, пользователя, хакера), мы пишем:
vm.startPrank(user);
и в завершении:
vm.stopPrank();
Порой для тестов это приходится прописывать довольно часто. Так вот, в этом контракте придумали поместить это все в модификатор:
А потом использовать его, как обычно, в функциях:
На мой взгляд достаточно элегантное решение.
Завтра мы поговорим о простой организации файлов в папке тестов, а сейчас вы можете скачать файлы контракта и тестов к нему, чтобы попрактиковаться самим.
#foundry #lesson14
Итак, в функцию setUp(), перед созданием контракта Counter, помещаем следующую строку:
vm.prank(ADMIN);
И наш контракт Counter теперь будет иметь владельцем адрес ADMIN. Проверим это в тестах. Напишем простой тест:
function test_SetNumberOwner () public {
vm.startPrank(ADMIN);
counter.setNumberOwner(45);
vm.stopPrank();
}Здесь мы определяем в тесте, что последующие вызовы будут идти от имени адреса админа и делаем вызов на изменение числа.
Если вместо ADMIN мы подставим адрес HACKER, то тест провалится, что обозначает правильную работу доступа к функции.
Кстати, вот небольшой лайфхак по работе со startPrank и stopPrank, который я подсмотрел в одном из конкурсных протоколов.
Обычно мы пишем тест и уже перед тем, как вызвать необходимую функции из под нужного аккаунта (владельца, пользователя, хакера), мы пишем:
vm.startPrank(user);
и в завершении:
vm.stopPrank();
Порой для тестов это приходится прописывать довольно часто. Так вот, в этом контракте придумали поместить это все в модификатор:
modifier prank(address from) {
vm.startPrank(from);
_;
vm.stopPrank();
}А потом использовать его, как обычно, в функциях:
function test_addLiquidity() public prank(user) {}На мой взгляд достаточно элегантное решение.
Завтра мы поговорим о простой организации файлов в папке тестов, а сейчас вы можете скачать файлы контракта и тестов к нему, чтобы попрактиковаться самим.
#foundry #lesson14
❤1👍1🔥1
Foundry с 0. Часть 15
В завершении этой недели практики с Foundry стоит поговорить об организации файлов при написании тестов. Вы ведь сами понимаете, что "пихать" все тесты в один файл это не только глупо, но и трудночитаемо.
Попробуем представить, какие тесты нам могут потребоваться для нашего проекта:
1. Простые тесты, которые проверят работу функций;
2. Фаззинг тесты;
3. Инвариант тесты;
4. Форк тесты;
5. Интеграционные тесты, когда мы проверяем, что наш протокол правильно взаимодействует со сторонними контрактами, типа Uniswap или Chainlink;
Более того, нам, возможно, нужно будет проверить работу протокола с токенами и другими шаблонами, ну, и не стоит забывать про какие-нибудь хелперы или библиотеки, которые мы можем написать исключительно для наших тестов.
В целом, наша папка test может получиться такой:
В mocks хранятся шаблоны токенов и других контрактов, которые мы хотим развернуть для тестов, в scenario - сценарии логических тестов, в unit - тесты для условий и порождения событий, в utils - хелперы и библиотеки.
Это самый полный список папок и тестов, который мне удалось найти и собрать вместе. Также не стоит забывать, что было бы хорошо создать подробный readme.md файл или другую документацию с описанием тестов для последующей работы разработчиков и аудиторов.
Я бы также рекомендовал бы добавить сюда отдельную папку hacks, в которую помещать все poc тесты после аудита.
Как вы можете заметить, написание тестов занимает практически столько же времени, сколько и написание самих контрактов, если не больше. Это очень кропотливая работа, на грани с полноценным аудитом.
Надеюсь, этот пост поможет вам лучше составлять архитектуру своих тестов и проверять свой проект вдоль и поперек.
#foundry #lesson15
В завершении этой недели практики с Foundry стоит поговорить об организации файлов при написании тестов. Вы ведь сами понимаете, что "пихать" все тесты в один файл это не только глупо, но и трудночитаемо.
Попробуем представить, какие тесты нам могут потребоваться для нашего проекта:
1. Простые тесты, которые проверят работу функций;
2. Фаззинг тесты;
3. Инвариант тесты;
4. Форк тесты;
5. Интеграционные тесты, когда мы проверяем, что наш протокол правильно взаимодействует со сторонними контрактами, типа Uniswap или Chainlink;
Более того, нам, возможно, нужно будет проверить работу протокола с токенами и другими шаблонами, ну, и не стоит забывать про какие-нибудь хелперы или библиотеки, которые мы можем написать исключительно для наших тестов.
В целом, наша папка test может получиться такой:
test
|-- differential
|-- fork
|-- fuzzing
|-- integartion
|-- invariant
|-- mocks
|-- scenario
|-- unit
|-- utils
|- simple tests
В mocks хранятся шаблоны токенов и других контрактов, которые мы хотим развернуть для тестов, в scenario - сценарии логических тестов, в unit - тесты для условий и порождения событий, в utils - хелперы и библиотеки.
Это самый полный список папок и тестов, который мне удалось найти и собрать вместе. Также не стоит забывать, что было бы хорошо создать подробный readme.md файл или другую документацию с описанием тестов для последующей работы разработчиков и аудиторов.
Я бы также рекомендовал бы добавить сюда отдельную папку hacks, в которую помещать все poc тесты после аудита.
Как вы можете заметить, написание тестов занимает практически столько же времени, сколько и написание самих контрактов, если не больше. Это очень кропотливая работа, на грани с полноценным аудитом.
Надеюсь, этот пост поможет вам лучше составлять архитектуру своих тестов и проверять свой проект вдоль и поперек.
#foundry #lesson15
👍1🔥1
Foundry c 0. Содержание 1
Прошло уже три недели с момента, как мы начали разбирать тестирование с Foundry. Пришло время собрать материал в один пост для более удобной навигации.
Общий план цикла постов
Итак, за этом время мы прошли:
Введение
Foundry с 0. Часть 0. Установка
Foundry с 0. Часть 1. Из чего состоит Foundry?
Foundry с 0. Часть 2. Cast команды (1)
Foundry с 0. Часть 3. Cast команды (2)
Foundry с 0. Часть 4. Cast команды (3)
Foundry с 0. Часть 5. Chisel
Foundry с 0. Часть 6. Конфигурация Foundry
Foundry с 0. Часть 7. Библиотеки
Foundry с 0. Часть 8. Старт проекта
Foundry с 0. Часть 9. Виды тестов
Foundry с 0. Часть 10. Ресурсы
Базовая практика
Foundry с 0. Часть 11. Простые тесты
Foundry с 0. Часть 12. Разные assert
Foundry с 0. Часть 13. Fail тесты
Foundry с 0. Часть 14. Читкоды и prank()
Foundry с 0. Часть 15. Организация файлов
Даже учитывая то, что по Foundry сейчас можно найти достаточно много информации, все равно бывает сложно перевести ее и написать понятным языком.
Буду рад, если поддержите лайком и сделаете репост.
#foundry #summary
Прошло уже три недели с момента, как мы начали разбирать тестирование с Foundry. Пришло время собрать материал в один пост для более удобной навигации.
Общий план цикла постов
Итак, за этом время мы прошли:
Введение
Foundry с 0. Часть 0. Установка
Foundry с 0. Часть 1. Из чего состоит Foundry?
Foundry с 0. Часть 2. Cast команды (1)
Foundry с 0. Часть 3. Cast команды (2)
Foundry с 0. Часть 4. Cast команды (3)
Foundry с 0. Часть 5. Chisel
Foundry с 0. Часть 6. Конфигурация Foundry
Foundry с 0. Часть 7. Библиотеки
Foundry с 0. Часть 8. Старт проекта
Foundry с 0. Часть 9. Виды тестов
Foundry с 0. Часть 10. Ресурсы
Базовая практика
Foundry с 0. Часть 11. Простые тесты
Foundry с 0. Часть 12. Разные assert
Foundry с 0. Часть 13. Fail тесты
Foundry с 0. Часть 14. Читкоды и prank()
Foundry с 0. Часть 15. Организация файлов
Даже учитывая то, что по Foundry сейчас можно найти достаточно много информации, все равно бывает сложно перевести ее и написать понятным языком.
Буду рад, если поддержите лайком и сделаете репост.
#foundry #summary
👍23🔥4❤1
Foundry с 0. Часть 16
Думал тему из этого поста постепенно поднимать в нескольких других, но понял, что лучше основную идею изложить сейчас, а потом уже дополнять при необходимости.
А поговорим мы о работе с консолью: вызовом тестов и логированием из них.
Из предыдущих постов мы узнали, для того чтобы вызвать процесс тестирования нашего контракта, достаточно в консоли прописать:
или
и других "-v..." для более подробной информации.
Тем не менее, порой, когда тестовых контрактов и самих тестов очень много, нам нужно вызвать что-то конкретное, и ждать выполнения всех тестов из всех контрактов может стоит нам времени.
Для этого существуют удобные дополнения к команде test:
1. forge test --match-test testName
Выполним конкретный тест из контрактов.
2. forge test --match-contract contractName
Выполним все тесты из конкретного контракта.
3. forge test --match-path Path
Выполнить тесты по указанному пути.
Так же мы может исключить какой-нибудь контракт или тест из процесса тестирования с помощью опций:
--no-match-test
--no-match-contract
--no-match-path
Более того, с помощью таких опций-хелперов мы можем подключать optimizer и указывать количество проходов:
--optimize
--optimizer-runs
--via-ir
Или указать EVM версию для наших тестов:
--evm-version
Как вы, надеюсь, поняли, перед опцией должно быть forge test, а после указание на файл / тест / версию и так далее.
Но, что самое интересное, эти команды можно комбинировать для точечных тестов, например:
Также есть еще другие опции для тестов на форках, отслеживание газа и дебаггинг, но о них мы будем говорить уже в своих постах.
Все опции для тестов можно посмотреть тут:
https://book.getfoundry.sh/reference/forge/forge-test
Теперь пара слов о логировании в тестах.
Порой нам нужно будет выводить значения из наших тестов, чтобы узнать, кто был вызывающим функции, изменение баланса или результат деления. Для этого мы можем просто написать:
Однако, если таких вызовов много в ходе выполнения теста, то можно очень просто потеряться в них. Поэтому предлагаю вам пару способов для удобного вывода информации.
1. Подписывать логи:
Будет так:
Logs:
This is owner: 0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
2. Разделять по строкам:
Будет так:
Logs:
This is owner
0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
3. Выделять дефисами:
Будет так:
Logs:
This is owner ---------- 0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
4. Оставлять пробелы, используя пустые логи:
console.log(" ");
5. Выделять разным цветом.
Для этого потребуется в импорте тестов добавить библиотеку StdStyle:
import {Test, console, StdStyle} from "forge-std/Test.sol";
После этого можно применять цвета к логам:
Стилей достаточно много: красный, зеленый, желтый, синий, мажента, голубой, жирный, приглушенный, курсив, зачеркнутый и обычный.
Попробуйте сами, так наглядность логов в ваших тестах станет еще лучше!
#foundry #lesson16
Думал тему из этого поста постепенно поднимать в нескольких других, но понял, что лучше основную идею изложить сейчас, а потом уже дополнять при необходимости.
А поговорим мы о работе с консолью: вызовом тестов и логированием из них.
Из предыдущих постов мы узнали, для того чтобы вызвать процесс тестирования нашего контракта, достаточно в консоли прописать:
forge test
или
forge test -vv
и других "-v..." для более подробной информации.
Тем не менее, порой, когда тестовых контрактов и самих тестов очень много, нам нужно вызвать что-то конкретное, и ждать выполнения всех тестов из всех контрактов может стоит нам времени.
Для этого существуют удобные дополнения к команде test:
1. forge test --match-test testName
Выполним конкретный тест из контрактов.
2. forge test --match-contract contractName
Выполним все тесты из конкретного контракта.
3. forge test --match-path Path
Выполнить тесты по указанному пути.
Так же мы может исключить какой-нибудь контракт или тест из процесса тестирования с помощью опций:
--no-match-test
--no-match-contract
--no-match-path
Более того, с помощью таких опций-хелперов мы можем подключать optimizer и указывать количество проходов:
--optimize
--optimizer-runs
--via-ir
Или указать EVM версию для наших тестов:
--evm-version
Как вы, надеюсь, поняли, перед опцией должно быть forge test, а после указание на файл / тест / версию и так далее.
Но, что самое интересное, эти команды можно комбинировать для точечных тестов, например:
forge test --match-contract myContract --match-test myTest --evm-version london
Также есть еще другие опции для тестов на форках, отслеживание газа и дебаггинг, но о них мы будем говорить уже в своих постах.
Все опции для тестов можно посмотреть тут:
https://book.getfoundry.sh/reference/forge/forge-test
Теперь пара слов о логировании в тестах.
Порой нам нужно будет выводить значения из наших тестов, чтобы узнать, кто был вызывающим функции, изменение баланса или результат деления. Для этого мы можем просто написать:
console.log(param);
Однако, если таких вызовов много в ходе выполнения теста, то можно очень просто потеряться в них. Поэтому предлагаю вам пару способов для удобного вывода информации.
1. Подписывать логи:
console.log("This is owner: ", contract.owner());Будет так:
Logs:
This is owner: 0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
2. Разделять по строкам:
console.log("This is owner");
console.log(contract.owner());Будет так:
Logs:
This is owner
0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
3. Выделять дефисами:
console.log("This is owner ----------- ", contract.owner());Будет так:
Logs:
This is owner ---------- 0x00c7bF7d9E7D071Df3B53dEec96cD4bf0f6c0220
4. Оставлять пробелы, используя пустые логи:
console.log(" ");
5. Выделять разным цветом.
Для этого потребуется в импорте тестов добавить библиотеку StdStyle:
import {Test, console, StdStyle} from "forge-std/Test.sol";
После этого можно применять цвета к логам:
console.log("Owner: ", StdStyle.yellow(counter.owner()));Стилей достаточно много: красный, зеленый, желтый, синий, мажента, голубой, жирный, приглушенный, курсив, зачеркнутый и обычный.
Попробуйте сами, так наглядность логов в ваших тестах станет еще лучше!
#foundry #lesson16
❤7👍1
Foundry с 0. Часть 17
Пару слов, думаю, стоит сказать о том, как работать с отчетами по газу и покрытия тестами. Начнем со второго.
Что мне нравилось в Hardhat, так это то, что отчеты там можно было генерировать в формате html сразу, без дополнительных "плясок с баяном". В Foundry дело обстоит несколько сложнее, особенно для пользователей Windows.
Вообще, для того, чтобы посмотреть отчет о покрытии кода тестами, нужно выполнить простую команду:
В терминале прогонятся все созданные тесты и по итогу появится небольшая таблица, где будет показано покрытие кода в процентном соотношении. Сама таблица состоит из 4 колонок: lines, statements, branches, funcs: которые отвечают за покрытие тестами строк, условий, утверждений и функций.
Да, как вы можете понять, что покрытие 100% функций вовсе не означает, что покрытие всего контракта будет равняется 100%. В самих функция бывает еще много чего для проверки.
Также есть команда:
которая выведет списком в терминале протестированные и нет пункты.
Ну, и последняя команда:
которая сформирует отчет в формате файла .info, и вот тут начинаются "танцы".
С помощью этого файла можно было бы сформировать удобный отчет в формате html (простой веб страницы), как это делается в Hardhat, но есть некоторые проблемы...
В сети я нашел рекомендации формировать такой отчет с помощью программы genhtml. Делается это также в терминале командой:
https://linux.die.net/man/1/lcov
или другими, которые можно посмотреть тут:
https://manpages.ubuntu.com/manpages/xenial/man1/genhtml.1.html
Однако эта прога работает только с системами Linux и на Windows не ставится. По крайней мере, сделать у меня это без напряга не получилось.
Другие программы или даже плаггины в самом VScode работают крайне криво, и я не могу порекомендовать какую-либо из них.
P.S. Если вы знаете что-то, что поможет конвертировать файлы из lcov в html без потери данных, прошу написать в комментариях.
Тем не менее, хочу рассказать о другом плагине, который помогает просматривать покрытие тестами прямо в контракте - Coverage Gutters (у меня от ryanluker).
После его установки и генерации lcov.info с forge, вы можете зайти в Command Palette (ctrl+shift+p), не уверен, как это переводится на русский, и выбрать Display Coverage (ctrl+shift+7). Код в контракте выделится красными, желтыми и зелеными полосками, которые как раз и обозначают места, требующие вашего внимания.
В принципе, достаточно удобно просматривать код и понимать, что еще нужно протестировать.
Единственный момент, о котором редко говорят в теме тестов, это то, что покрытие кода на 100% не значит, что он безопасный на 100% и функции выполняют задумку разработчиков.
Есть еще логические баги и уязвимости, которые необходимо проверять отдельными тестами - сценариями использования протокола. Об этом мы будем говорить чуть позже.
Так же существует отчет по газу, который можно получить командой:
Он показывает расход газа на ту или иную функцию, а также количество вызовов этой функции в контракте.
С помощью этого отчеты вы можете отслеживать свои модификации кода и применение различных паттерном снижения использования газа в своем случае.
Вместе с этим вы можете скачать для себя популярный анализатор кода - 4naly3er.
С помощью него можно быстро сканировать код на предмет популярных недочетов в коде, исправив которые вы сэкономите немного газа и даже сможете найти мелкие недочеты в своем коде.
В общем, полезная штука, которая используется на конкурсной площадке code4rena и на основе которой создаются более мощные программы для анализа кода.
#foundry #lesson17
Пару слов, думаю, стоит сказать о том, как работать с отчетами по газу и покрытия тестами. Начнем со второго.
Что мне нравилось в Hardhat, так это то, что отчеты там можно было генерировать в формате html сразу, без дополнительных "плясок с баяном". В Foundry дело обстоит несколько сложнее, особенно для пользователей Windows.
Вообще, для того, чтобы посмотреть отчет о покрытии кода тестами, нужно выполнить простую команду:
forge coverage
В терминале прогонятся все созданные тесты и по итогу появится небольшая таблица, где будет показано покрытие кода в процентном соотношении. Сама таблица состоит из 4 колонок: lines, statements, branches, funcs: которые отвечают за покрытие тестами строк, условий, утверждений и функций.
Да, как вы можете понять, что покрытие 100% функций вовсе не означает, что покрытие всего контракта будет равняется 100%. В самих функция бывает еще много чего для проверки.
Также есть команда:
forge coverage --report debug
которая выведет списком в терминале протестированные и нет пункты.
Ну, и последняя команда:
forge coverage --report lcov
которая сформирует отчет в формате файла .info, и вот тут начинаются "танцы".
С помощью этого файла можно было бы сформировать удобный отчет в формате html (простой веб страницы), как это делается в Hardhat, но есть некоторые проблемы...
В сети я нашел рекомендации формировать такой отчет с помощью программы genhtml. Делается это также в терминале командой:
https://linux.die.net/man/1/lcov
genhtml -o report --branch-coverage
или другими, которые можно посмотреть тут:
https://manpages.ubuntu.com/manpages/xenial/man1/genhtml.1.html
Однако эта прога работает только с системами Linux и на Windows не ставится. По крайней мере, сделать у меня это без напряга не получилось.
Другие программы или даже плаггины в самом VScode работают крайне криво, и я не могу порекомендовать какую-либо из них.
P.S. Если вы знаете что-то, что поможет конвертировать файлы из lcov в html без потери данных, прошу написать в комментариях.
Тем не менее, хочу рассказать о другом плагине, который помогает просматривать покрытие тестами прямо в контракте - Coverage Gutters (у меня от ryanluker).
После его установки и генерации lcov.info с forge, вы можете зайти в Command Palette (ctrl+shift+p), не уверен, как это переводится на русский, и выбрать Display Coverage (ctrl+shift+7). Код в контракте выделится красными, желтыми и зелеными полосками, которые как раз и обозначают места, требующие вашего внимания.
В принципе, достаточно удобно просматривать код и понимать, что еще нужно протестировать.
Единственный момент, о котором редко говорят в теме тестов, это то, что покрытие кода на 100% не значит, что он безопасный на 100% и функции выполняют задумку разработчиков.
Есть еще логические баги и уязвимости, которые необходимо проверять отдельными тестами - сценариями использования протокола. Об этом мы будем говорить чуть позже.
Так же существует отчет по газу, который можно получить командой:
forge test --gas-report
Он показывает расход газа на ту или иную функцию, а также количество вызовов этой функции в контракте.
С помощью этого отчеты вы можете отслеживать свои модификации кода и применение различных паттерном снижения использования газа в своем случае.
Вместе с этим вы можете скачать для себя популярный анализатор кода - 4naly3er.
С помощью него можно быстро сканировать код на предмет популярных недочетов в коде, исправив которые вы сэкономите немного газа и даже сможете найти мелкие недочеты в своем коде.
В общем, полезная штука, которая используется на конкурсной площадке code4rena и на основе которой создаются более мощные программы для анализа кода.
#foundry #lesson17
🔥6
Foundry с 0. Часть 18
Совсем забыл, что хотел еще поднять тему интеграций Foundry в проект Hardhat и наоборот.
Начнем с того, что у вас есть проект на Hardhat и вы хотите подключить Foundry и писать дальше тесты на нем.
Нужно уточнить, что Foundry в своей работе сильно полагается на работу с Git, поэтому перед началом работы следует убедиться, что в проекте есть связка с git. Если же ее нет, то для начала стоит выполнить команду:
git init
Затем убедитесь, что Foundry также установлен на компьютере, на котором вы работаете:
forge --version
Если терминал выдаст ошибку, то стоит вернуться в самое начало и установить Foundry с нуля.
Однако предположим, что все у нас было установлено и git в проекте есть, поэтому далее выполняем команду:
и после добавляем запись в файл hardhat.config.ts:
Для завершения установки Foundry в проект исполняем:
что создаст файл foundry.toml и загрузит библиотеки forge-std.
После этого можно будет писать тесты на Foundry в Hardhat проекте.
Также, если вы хотите использовать Hardhat в Foundry проекте, то следует выполнить следующую команду:
и добавить запись в файл hardhat.config.js:
Теперь вы сможете писать тесты на javanoscript / typenoscript.
Сейчас я все чаще встречаю комбинированные тесты для больших протоколов, поэтому, уверен, нужно знать оба способа интеграций одной программы для тестов в другую.
#foundry #lesson18
Совсем забыл, что хотел еще поднять тему интеграций Foundry в проект Hardhat и наоборот.
Начнем с того, что у вас есть проект на Hardhat и вы хотите подключить Foundry и писать дальше тесты на нем.
Нужно уточнить, что Foundry в своей работе сильно полагается на работу с Git, поэтому перед началом работы следует убедиться, что в проекте есть связка с git. Если же ее нет, то для начала стоит выполнить команду:
git init
Затем убедитесь, что Foundry также установлен на компьютере, на котором вы работаете:
forge --version
Если терминал выдаст ошибку, то стоит вернуться в самое начало и установить Foundry с нуля.
Однако предположим, что все у нас было установлено и git в проекте есть, поэтому далее выполняем команду:
npm install --save-dev @nomicfoundation/hardhat-foundry
и после добавляем запись в файл hardhat.config.ts:
import "@nomicfoundation/hardhat-foundry";
Для завершения установки Foundry в проект исполняем:
npx hardhat init-foundry
что создаст файл foundry.toml и загрузит библиотеки forge-std.
После этого можно будет писать тесты на Foundry в Hardhat проекте.
Также, если вы хотите использовать Hardhat в Foundry проекте, то следует выполнить следующую команду:
npm install --save-dev hardhat @nomicfoundation/hardhat-foundry
и добавить запись в файл hardhat.config.js:
require("@nomicfoundation/hardhat-foundry"); Теперь вы сможете писать тесты на javanoscript / typenoscript.
Сейчас я все чаще встречаю комбинированные тесты для больших протоколов, поэтому, уверен, нужно знать оба способа интеграций одной программы для тестов в другую.
#foundry #lesson18
👍1
Foundry с 0. Часть 19
Сегодня мы поговорим о деплое контрактов в локальную, тестовые и обычные блокчейн сети нашего контракта, а также о его верификации, публичных сервисах нод и работе с приватными ключами.
Изначально я планировал разделить это все на несколько постов, но решил, что для большей наглядности будет лучше объединить все в одном. Практиковаться будем с нашим контрактом Counter из предыдущих постов.
Для начала поднимем тему rpc-url и приватных ключей, а точнее, работу с ними в написании тестов.
В постах про cast команды нам нужно было часто указывать специальную ссылку для подключения к блокчейн узлам, чтобы можно было получать необходимую информацию. Мы писали что-то типа:
где вместо link нужно было подставить https://blablabla... Или же создавали кошелек на основе приватного ключа:
где вместо PRIVATE_KEY указывали настоящий приватный ключ.
Я точно не могу сказать и рассказать почему, но это считается не безопасным способом, т.е. не рекомендуется указывать свои конфиденциальные данные в терминале. Возможно, потому что эту информацию могут получить хакеры через вирусы, или потому что VScode отправляет массу информации в Microsoft... В общем, не делайте так.
Для вашего личного тестирования смарт контрактов, более безопасным будет использование переменных среды. Как это сделать?
Нам нужно будет в корне нашего проекта создать отдельный файл ".env". Обратите внимание, что точка в начале названия обязательна.
Уже в этом файле мы делаем запись:
RPC_LINK=https://mainnet.infura.io/v3/...
PRIVATE_KEY=ndsf349jf9...
и другие по необходимости.
Затем в терминале выполняем команду:
К слову сказать, что тут вам потребуется установленный python на свой компьютер.
Также, возможно, у вас будут возникать ошибки, типа "command not found" или какие-то вроде них. Это означает, что переменные среды не применяются для проекта. В моем случае помогли:
1. Установка pytnon
2. Переход на bash терминал с PowerShell
3. Написание переменных, знака равенства и значения вместе
4. Возможно, вам также потребуется добавить слово export перед переменной
После этого необходимо проверить, чтобы запись .env была в файле .gitignore во избежание случайной загрузки конфиденциальных данный на сервера GitHub в открытый доступ. Если записи нет, то просто добавьте ее вручную.
Теперь команды cast можно выполнять так:
Обратите внимание на знак $ перед RPC_LINK!
P.S. В Части 2 я писал, где можно получить rpc-url бесплатно.
Так вы сможете скрыть конфиденциальную информацию с команд терминала.
Есть еще более безопасный вариант использования информации о приватных ключах в командах терминала, но он задействует keystore json файл с паролем. Это чуть более продвинутый способ и, если захотите, то напишу отдельный пост на эту тему. А пока идем дальше.
Итак, на данный момент у нас должна быть rpc ссылка от одного из сервисов нод блокчейна, и мы должны понимать, что такое переменные среды. Все это нужно для того, чтобы научиться делать деплой контрактов.
Сам деплой можно делать в локальный блокчейн Anvil, в тестовые сети, типа Goerly, или в основные сети. Начнем с первого.
Для запуска Anvil достаточно выполнить команду в терминале:
anvil
При этом нам предоставят 10 аккаунтов для проведения тестов вместе с их приватными ключами, мнемоник фразу, chain id и rpc ссылку, которая выглядит как: Listening on 127.0.0.1:8...
Более того, блокчейн установит для вас base fee, gas limit и timestamp, которыми вы можете управлять в случае необходимости.
В целом, деплой контрактов в различные сети ничем особым не отличается, происходит это с помощью специального скрипта, который тоже пишется на Solidity.
Заходим в папку noscripts и открываем / создаем новый файл, назовем его Counter.s.sol.
Обратите внимание, что по принципу названия файлов для тестов, где окончание t.sol обязательно, в данном случае для скриптов также обязательно окончание s.sol.
В этом файле создаем следующий скрипт:
Сегодня мы поговорим о деплое контрактов в локальную, тестовые и обычные блокчейн сети нашего контракта, а также о его верификации, публичных сервисах нод и работе с приватными ключами.
Изначально я планировал разделить это все на несколько постов, но решил, что для большей наглядности будет лучше объединить все в одном. Практиковаться будем с нашим контрактом Counter из предыдущих постов.
Для начала поднимем тему rpc-url и приватных ключей, а точнее, работу с ними в написании тестов.
В постах про cast команды нам нужно было часто указывать специальную ссылку для подключения к блокчейн узлам, чтобы можно было получать необходимую информацию. Мы писали что-то типа:
cast chain-id --rpc-url link
где вместо link нужно было подставить https://blablabla... Или же создавали кошелек на основе приватного ключа:
cast wallet address --private-key PRIVATE_KEY
где вместо PRIVATE_KEY указывали настоящий приватный ключ.
Я точно не могу сказать и рассказать почему, но это считается не безопасным способом, т.е. не рекомендуется указывать свои конфиденциальные данные в терминале. Возможно, потому что эту информацию могут получить хакеры через вирусы, или потому что VScode отправляет массу информации в Microsoft... В общем, не делайте так.
Для вашего личного тестирования смарт контрактов, более безопасным будет использование переменных среды. Как это сделать?
Нам нужно будет в корне нашего проекта создать отдельный файл ".env". Обратите внимание, что точка в начале названия обязательна.
Уже в этом файле мы делаем запись:
RPC_LINK=https://mainnet.infura.io/v3/...
PRIVATE_KEY=ndsf349jf9...
и другие по необходимости.
Затем в терминале выполняем команду:
source .env
К слову сказать, что тут вам потребуется установленный python на свой компьютер.
Также, возможно, у вас будут возникать ошибки, типа "command not found" или какие-то вроде них. Это означает, что переменные среды не применяются для проекта. В моем случае помогли:
1. Установка pytnon
2. Переход на bash терминал с PowerShell
3. Написание переменных, знака равенства и значения вместе
4. Возможно, вам также потребуется добавить слово export перед переменной
После этого необходимо проверить, чтобы запись .env была в файле .gitignore во избежание случайной загрузки конфиденциальных данный на сервера GitHub в открытый доступ. Если записи нет, то просто добавьте ее вручную.
Теперь команды cast можно выполнять так:
cast chain-id --rpc-url $RPC_LINK
Обратите внимание на знак $ перед RPC_LINK!
P.S. В Части 2 я писал, где можно получить rpc-url бесплатно.
Так вы сможете скрыть конфиденциальную информацию с команд терминала.
Есть еще более безопасный вариант использования информации о приватных ключах в командах терминала, но он задействует keystore json файл с паролем. Это чуть более продвинутый способ и, если захотите, то напишу отдельный пост на эту тему. А пока идем дальше.
Итак, на данный момент у нас должна быть rpc ссылка от одного из сервисов нод блокчейна, и мы должны понимать, что такое переменные среды. Все это нужно для того, чтобы научиться делать деплой контрактов.
Сам деплой можно делать в локальный блокчейн Anvil, в тестовые сети, типа Goerly, или в основные сети. Начнем с первого.
Для запуска Anvil достаточно выполнить команду в терминале:
anvil
При этом нам предоставят 10 аккаунтов для проведения тестов вместе с их приватными ключами, мнемоник фразу, chain id и rpc ссылку, которая выглядит как: Listening on 127.0.0.1:8...
Более того, блокчейн установит для вас base fee, gas limit и timestamp, которыми вы можете управлять в случае необходимости.
В целом, деплой контрактов в различные сети ничем особым не отличается, происходит это с помощью специального скрипта, который тоже пишется на Solidity.
Заходим в папку noscripts и открываем / создаем новый файл, назовем его Counter.s.sol.
Обратите внимание, что по принципу названия файлов для тестов, где окончание t.sol обязательно, в данном случае для скриптов также обязательно окончание s.sol.
В этом файле создаем следующий скрипт:
🔥1
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console2} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
contract CounterScript is Script {
function run() public {
vm.startBroadcast();
Counter counter = new Counter();
vm.stopBroadcast();
console2.log(address(counter));
}
}
Служебная функция run() как раз и служит для деплоя, vm.startBroadcast и vm.stopBroadcast еще пара читкодов, которые позволяют отправлять транзакции в блокчейн.
Открываем новый терминал, не удаляя тот, где запущен Anvil, и выполняем команду:
forge noscript noscript/Counter.s.sol
В итоге у нас будет будет что-то вроде:
Script ran successfully.
Gas used: 228424
== Logs ==
0x90193C961A926261B75...
Был ли в данном случае выполнен деплой? Нет, сейчас просто прогнался наш контракт как скрипт.
Как я понял из сторонних статей и объяснений на форумах, эта простая команда нужна для того, чтобы проверить наличие ошибок при деплое контрактов.
Также симулировать деплой можно с помощью похожей команды, указав rpc ссылку сети, в которую будет идти деплой. Например, для Anvil:
forge noscript noscript/Counter.s.sol --rpc-url http://127.0.0.1:8...
При этом вам могут показать некоторые предупреждения, как например:
EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 31337.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
И уже для полноценного деплоя нам нужно указать приватный ключ от аккаунта, с которого будет идти деплой контрактов в сеть, а также добавить ключевое слово --broadcast:
forge noscript noscript/Counter.s.sol --rpc-url http://127.0.0.1:8.. --private-key $PRIVATE_KEY --broadcast
Теперь, если мы откроем терминал, где у нас был запущен блокчейн, то можем увидеть новую запись:
[Success]Hash: 0xdb858b56859825d35f6bffe022c9524e640869974e219387a87b4d542ce311f1
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.0009359 ETH (233975 gas * 4 gwei)
Кстати, сюда же мы можем отправлять транзакции через cast send и cast call, так как это полноценный контракт в блокчейне.
Также мы можем заметить, что в древе файлов нашего проекта появилась новая папка broadcast. Здесь находится информация по деплою нашего контракта.
Существует еще более директивный способ деплоя контрактов в сеть с помощью команды
forge create
Здесь вам также нужно будет указывать rpc ссылку и приватный ключ.
Больше информации тут: https://book.getfoundry.sh/reference/forge/forge-create
При этом с create, те же аргументы для контрактов нужно будет также прописывать через дополнительные команды, типа --constructor-args, в то время как для скриптовой версии, все это указывается как в обычном коде Solidity.
Скриптовой вариант более удобнее и проще, в особенности для деплоя множества контрактов.
Ну, и в завершение, пару слов о верификации контрактов.
При обычном способе, нам нужно было бы заходить на тот же etherscan и вручную указывать многие параметры нашего контракта, включая его код на Solidity. При этом, по слухам, было много шансов ошибиться на каком-либо шаге и каждый раз начинать все заново. С Foundry этот процесс стал проще.
Для этого мы можем добавить команду --verify в команду исполнения скрипта:
forge noscript noscript/Counter.s.sol --rpc-url http://127.0.0.1:8... --private-key $PRIVATE_KEY --broadcast --verify
Вообще для скриптов написали достаточно много команд, их можно посмотреть тут:
https://book.getfoundry.sh/reference/forge/forge-noscript
Дополнительно темы деплоя мы будем касаться в постах, где это будет необходимо, указывая на новые нюансы и опции.
А пока что, попробуйте потренироваться сами.
#foundry #lesson19
❤2👍1🔥1
Foundry с 0. Часть 20
В преддверии остальных, более сложных видов тестов, стоит поговорить о некоторых вариантах работы с токенами и nft.
Создадим простой контракт с использованием библиотек от Open Zeppelin: erc20 и erc721.
И поместим их в файл под названием Helper.sol.
Зачем это будет нужно?
Представьте, что вы разработали свой протокол, который работает с токенами и nft и нужно протестировать некоторые процессы: переводы на баланс контракта, минт долей, выдача прав и т.д. Для этого может потребоваться наличие некоторого количества токенов на счету фиктивных пользователей.
Да, можно через prank() подключать аккаунт админа токена и минтить на нужный адрес необходимое количество токенов, а можно все делать и через простые читкоды.
Для начала, как и для остальных тестов, мы подключаем библиотеки Test, контракта, который будем тестировать, и наш файл Helper.
В функции setUp() разворачиваем необходимые контракты из под нужных нам аккаунтов, например:
Итак, у нас есть развернутые контракты. А NFT, в одном из случаев, можно сминтить за Эфир. Но в только что созданных аккаунтах пользователей нет ни Эфира, ни токенов. Что же делать?
Для пополнения баланса Эфира любого аккаунта, можно использовать следующую команду:
где вместо USER вы можете указать любой адрес аккаунта.
А для пополнения баланса токенов следует использовать:
Не забывайте проверять через console.log правильно ли установились балансы!
К слову сказать, на балансе контракта нашего теста, например CounterTest, уже по умолчанию будет лежать достаточно Эфира для проведения операций. Проверить это можно с помощью обычного лога:
console.log(address(this).balance);
Более того, есть специальный читкод, который объединяет deal() и prank()! Т.е. написав:
на счет пользователя зачислится 1000 Эфира и последующий вызов, будет эмулирован из-под аккаунта USER. Это бывает очень удобно, когда в тестах нам нужно, скажем, купить nft и вместо двух команд мы можем написать всего одну!
С помощью этих команд можно управлять балансами пользователей и проводить тесты более просто.
Также старайтесь держать архитектуру папки в правильном виде, сохраняя файлы помощники в папку Utils или mock файлы в одноименную папку.
#foundry #lesson20
В преддверии остальных, более сложных видов тестов, стоит поговорить о некоторых вариантах работы с токенами и nft.
Создадим простой контракт с использованием библиотек от Open Zeppelin: erc20 и erc721.
contract MyERC20 is ERC20 {
address immutable owner;
constructor() ERC20("Token1", "SYM1") {
owner = msg.sender;
_mint(msg.sender, 10_000_000);
}
function mint() external {
require(msg.sender == owner, 'not an owner');
_mint(msg.sender, 10000);
}
}
contract NFT is ERC721, Ownable {
using Strings for uint256;
string public baseURI;
uint256 public currentTokenId;
uint256 public constant TOTAL_SUPPLY = 10_000;
uint256 public constant MINT_PRICE = 0.08 ether;
constructor(
string memory _name,
string memory _symbol,
string memory _baseURI
) ERC721(_name, _symbol) {
baseURI = _baseURI;
}
function mintSimple(address recipient) public payable returns (uint256) {
uint256 newTokenId = ++currentTokenId;
_safeMint(recipient, newTokenId);
return newTokenId;
}
function mintTo(address recipient) public payable returns (uint256) {
if (msg.value != MINT_PRICE) {
revert MintPriceNotPaid();
}
uint256 newTokenId = ++currentTokenId;
if (newTokenId > TOTAL_SUPPLY) {
revert MaxSupply();
}
_safeMint(recipient, newTokenId);
return newTokenId;
}
}И поместим их в файл под названием Helper.sol.
Зачем это будет нужно?
Представьте, что вы разработали свой протокол, который работает с токенами и nft и нужно протестировать некоторые процессы: переводы на баланс контракта, минт долей, выдача прав и т.д. Для этого может потребоваться наличие некоторого количества токенов на счету фиктивных пользователей.
Да, можно через prank() подключать аккаунт админа токена и минтить на нужный адрес необходимое количество токенов, а можно все делать и через простые читкоды.
Для начала, как и для остальных тестов, мы подключаем библиотеки Test, контракта, который будем тестировать, и наш файл Helper.
В функции setUp() разворачиваем необходимые контракты из под нужных нам аккаунтов, например:
function setUp() public {
vm.startPrank(COUNTER_ADMIN);
counter = new Counter();
vm.stopPrank();
vm.startPrank(TOKEN_ADMIN);
token = new MyERC20();
vm.stopPrank();
vm.startPrank(NFT_ADMIN);
nft = new NFT(nftName, nftSymbol, nftLink);
vm.stopPrank();
}Итак, у нас есть развернутые контракты. А NFT, в одном из случаев, можно сминтить за Эфир. Но в только что созданных аккаунтах пользователей нет ни Эфира, ни токенов. Что же делать?
Для пополнения баланса Эфира любого аккаунта, можно использовать следующую команду:
vm.deal(USER, 19 ether);
где вместо USER вы можете указать любой адрес аккаунта.
А для пополнения баланса токенов следует использовать:
deal(address(DAI), USER, 1 ether);
Не забывайте проверять через console.log правильно ли установились балансы!
К слову сказать, на балансе контракта нашего теста, например CounterTest, уже по умолчанию будет лежать достаточно Эфира для проведения операций. Проверить это можно с помощью обычного лога:
console.log(address(this).balance);
Более того, есть специальный читкод, который объединяет deal() и prank()! Т.е. написав:
hoax(USER, 1000);
на счет пользователя зачислится 1000 Эфира и последующий вызов, будет эмулирован из-под аккаунта USER. Это бывает очень удобно, когда в тестах нам нужно, скажем, купить nft и вместо двух команд мы можем написать всего одну!
С помощью этих команд можно управлять балансами пользователей и проводить тесты более просто.
Также старайтесь держать архитектуру папки в правильном виде, сохраняя файлы помощники в папку Utils или mock файлы в одноименную папку.
#foundry #lesson20
👍2🔥1
Вопрос по 4 модулю курса?
Оставлю этот пост и опрос ниже до понедельника, чтобы чуть больше людей его увидели и приняли участие.
Как многие знают, недавно у нас закончился 3 модуль курса "Разработчик смарт контрактов на языке Solidity". Было много практики и информации для изучения. Кроме того, многие участники канала решили докупить и предыдущие модули, чтобы проходить курс в своем темпе.
И сейчас хочу спросить у вас об актуальности 4 модуля.
Тут будут подниматься темы: древа Меркла, подписи и их безопасность, работа с прокси контрактами, разбор storage, memory, calldata, опкоды и assembly, побитовые операции и базовый дебаггинг.
Достаточно много времени будет уходить на изучение каждой темы и детальное ее понимание для учеников.
И у меня возник вопрос к вам: стоит ли его проводить в этом году?
Я хочу сказать, что он точно будет, только нужно выбрать для этого время.
Много учеников еще до проходят предыдущие модули в своем темпе, разбирая каждый урок и делая практические задания. Новый модуль будет "вместить" в себя достаточно сложно.
Я бы предложил запустить его после новогодних праздников, когда все до пройдут текущие модули.
А что думаете вы?
Прошу поучаствуйте в опросе ниже.
Оставлю этот пост и опрос ниже до понедельника, чтобы чуть больше людей его увидели и приняли участие.
Как многие знают, недавно у нас закончился 3 модуль курса "Разработчик смарт контрактов на языке Solidity". Было много практики и информации для изучения. Кроме того, многие участники канала решили докупить и предыдущие модули, чтобы проходить курс в своем темпе.
И сейчас хочу спросить у вас об актуальности 4 модуля.
Тут будут подниматься темы: древа Меркла, подписи и их безопасность, работа с прокси контрактами, разбор storage, memory, calldata, опкоды и assembly, побитовые операции и базовый дебаггинг.
Достаточно много времени будет уходить на изучение каждой темы и детальное ее понимание для учеников.
И у меня возник вопрос к вам: стоит ли его проводить в этом году?
Я хочу сказать, что он точно будет, только нужно выбрать для этого время.
Много учеников еще до проходят предыдущие модули в своем темпе, разбирая каждый урок и делая практические задания. Новый модуль будет "вместить" в себя достаточно сложно.
Я бы предложил запустить его после новогодних праздников, когда все до пройдут текущие модули.
А что думаете вы?
Прошу поучаствуйте в опросе ниже.
Foundry с 0. Часть 21
Эту неделю мы начнем с разговора о работе со временем в наших тестах.
Напишем простой контракт:
В Foundry я встречал 4 самых популярных читкода для манипуляцией временем:
1. vm.warp - устанавливает block.timestamp на конкретное значение времени
2. vm.roll - устанавливает block.number
3. skip - увеличивает текущий timestamp
4. rewind - уменьшает текущий timestamp
Какие же тесты можно тут написать?
Например, что bid() вызывается в определенный период времени:
или так:
Обратите внимание на последовательность вызовов! Если в одном тесте вы добавите время, то при вычитании другого значения позже, действие будет происходить с обновленным значением после первой манипуляции!
Ну, и простой тест для block.number:
Такие читкоды бывают полезны, когда вы пишите тесты для голосований ДАО или проверки возможности вывода депозита пользователя только в определенный промежуток времени.
В прочем, ничего сложного тут нет
#foundry #lesson21
Эту неделю мы начнем с разговора о работе со временем в наших тестах.
Напишем простой контракт:
pragma solidity 0.8.18;
contract Auction {
uint256 public startAt = block.timestamp + 1 days;
uint256 public endAt = block.timestamp + 2 days;
function bid() external {
require(
block.timestamp >= startAt && block.timestamp < endAt, "cannot bid"
);
}
function end() external {
require(block.timestamp >= endAt, "cannot end");
}
}
В Foundry я встречал 4 самых популярных читкода для манипуляцией временем:
1. vm.warp - устанавливает block.timestamp на конкретное значение времени
2. vm.roll - устанавливает block.number
3. skip - увеличивает текущий timestamp
4. rewind - уменьшает текущий timestamp
Какие же тесты можно тут написать?
Например, что bid() вызывается в определенный период времени:
function testBid() public {
vm.warp(startAt + 1 days);
auction.bid();
}или так:
function testTimestamp() public {
uint256 t = block.timestamp;
// set block.timestamp to t + 100
skip(100);
assertEq(block.timestamp, t + 100);
// set block.timestamp to t + 100 - 100;
rewind(100);
assertEq(block.timestamp, t);
}Обратите внимание на последовательность вызовов! Если в одном тесте вы добавите время, то при вычитании другого значения позже, действие будет происходить с обновленным значением после первой манипуляции!
Ну, и простой тест для block.number:
function testBlockNumber() public {
uint256 b = block.number;
// set block number to 11
vm.roll(11);
assertEq(block.number, 11);
}Такие читкоды бывают полезны, когда вы пишите тесты для голосований ДАО или проверки возможности вывода депозита пользователя только в определенный промежуток времени.
В прочем, ничего сложного тут нет
#foundry #lesson21
🔥6
Foundry с 0. Часть 22
Настало время коснуться темы форков в наших тестах. Почему только коснуться? Потому что, когда я искал какие-либо достойные примеры этого тестирования, то чаще всего встречал либо базовые объяснения, либо копипасту.
И сейчас мы просто поговорим о читкодах и базовых примерах, а уже когда я найду хороший реальный пример реального протокола, то разберем его детальнее.
Итак, поехали.
Для начала, что есть вообще форк-тест?
Форк-тест - это такой вид тестов, когда вы проверяете код своего контракта не на локальном блокчейне, тот же anvil, а на реальной сети, типа Ethereum или Optimism. Это бывает очень полезно, когда вы планируете работу своего протокола на разных сетях и хотите удостовериться, что код работает корректно на каждой выбранной сети.
Есть два способа проводить форк-тесты.
Вот простой тест для контракта с использованием WETH:
Здесь мы проверяем, что после депозита на реальный контракт в сети Ethereum у пользователя действительно повысится баланс.
После этого выполняем команду в терминале:
Видим, что все прошло и работает корректно. Мы также можем добавлять другие функции из нашего протокола для таких тестов.
Во втором варианте мы создаем форк не в терминале при помощи --fork-url, а используя читкоды.
И наш тест тогда будет выглядеть так:
С vm.createSelectFork мы создаем и выбираем сеть для форка. Да, это можно было бы делать двумя различными читкодами - createFork() и selectFork() - но с одним это проще.
Также стоит обратить внимание, что мы можем создать:
1. Просто форк сети с использованием последнего созданного блока:
2. Форк с использованием конкретного номера блока:
3. Форк с блоком, в котором есть определенная транзакция:
при этом перед самим тестом будет проведена эмуляция транзакций в данном блоке.
При этом вам также доступно манипулированием состоянием форк. Например, выполнив определенные действия в тесте на конкретном блоке или транзакции, вы можете "промотать" вперед-назад форк и выполнить другие действия. Сделать это поможет читкод - rollFork(), который принимает те же аргументы, что и его собрат выше.
Более того, необходимо отметить, что в рамках одного теста вы можете менять сети форка с одного на другой. При этом память теста будет обнуляться. Другими словами, если вы указали какие-либо данные в переменных для первой сети, то для второй - они будут обнулены.
Избежать этого поможет читкод - makePersistent(), который сохраняет данные памяти при переходе форков. Чуть подробнее об этом можно прочитать тут:
https://book.getfoundry.sh/cheatcodes/make-persistent
Вообще сказать, я не часто видел тесты с форками в файлах крупных протоколов. Может поэтому там и находят большое количество багов...
#foundry #lesson22
Настало время коснуться темы форков в наших тестах. Почему только коснуться? Потому что, когда я искал какие-либо достойные примеры этого тестирования, то чаще всего встречал либо базовые объяснения, либо копипасту.
И сейчас мы просто поговорим о читкодах и базовых примерах, а уже когда я найду хороший реальный пример реального протокола, то разберем его детальнее.
Итак, поехали.
Для начала, что есть вообще форк-тест?
Форк-тест - это такой вид тестов, когда вы проверяете код своего контракта не на локальном блокчейне, тот же anvil, а на реальной сети, типа Ethereum или Optimism. Это бывает очень полезно, когда вы планируете работу своего протокола на разных сетях и хотите удостовериться, что код работает корректно на каждой выбранной сети.
Есть два способа проводить форк-тесты.
Вот простой тест для контракта с использованием WETH:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "forge-std/console.sol";
interface IWETH {
function balanceOf(address) external view returns (uint256);
function deposit() external payable;
}
contract ForkTest is Test {
IWETH public weth;
function setUp() public {
weth = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
}
function testDeposit() public {
uint256 balBefore = weth.balanceOf(address(this));
console.log("balance before", balBefore);
weth.deposit{value: 100}();
uint256 balAfter = weth.balanceOf(address(this));
console.log("balance after", balAfter);
}
}
Здесь мы проверяем, что после депозита на реальный контракт в сети Ethereum у пользователя действительно повысится баланс.
После этого выполняем команду в терминале:
forge test --fork-url $RPC_LINK --match-contract ForkTest
Видим, что все прошло и работает корректно. Мы также можем добавлять другие функции из нашего протокола для таких тестов.
Во втором варианте мы создаем форк не в терминале при помощи --fork-url, а используя читкоды.
И наш тест тогда будет выглядеть так:
function testDeposit() public {
uint256 forkId = vm.createSelectFork("https://mainnet.infura.io/v3/...", blockNumber);
uint256 balBefore = weth.balanceOf(address(this));
console.log("balance before", balBefore);
weth.deposit{value: 100}();
uint256 balAfter = weth.balanceOf(address(this));
console.log("balance after", balAfter);
}С vm.createSelectFork мы создаем и выбираем сеть для форка. Да, это можно было бы делать двумя различными читкодами - createFork() и selectFork() - но с одним это проще.
Также стоит обратить внимание, что мы можем создать:
1. Просто форк сети с использованием последнего созданного блока:
vm.createSelectFork("https://mainnet.infura.io/v3/...");2. Форк с использованием конкретного номера блока:
vm.createSelectFork("https://mainnet.infura.io/v3/...", blockNumber);3. Форк с блоком, в котором есть определенная транзакция:
vm.createSelectFork("https://mainnet.infura.io/v3/...", txBytes32);при этом перед самим тестом будет проведена эмуляция транзакций в данном блоке.
При этом вам также доступно манипулированием состоянием форк. Например, выполнив определенные действия в тесте на конкретном блоке или транзакции, вы можете "промотать" вперед-назад форк и выполнить другие действия. Сделать это поможет читкод - rollFork(), который принимает те же аргументы, что и его собрат выше.
Более того, необходимо отметить, что в рамках одного теста вы можете менять сети форка с одного на другой. При этом память теста будет обнуляться. Другими словами, если вы указали какие-либо данные в переменных для первой сети, то для второй - они будут обнулены.
Избежать этого поможет читкод - makePersistent(), который сохраняет данные памяти при переходе форков. Чуть подробнее об этом можно прочитать тут:
https://book.getfoundry.sh/cheatcodes/make-persistent
Вообще сказать, я не часто видел тесты с форками в файлах крупных протоколов. Может поэтому там и находят большое количество багов...
#foundry #lesson22
🔥2❤1
Foundry с 0. Часть 23
В этом небольшом посте хотел бы написать про FFI тесты, о которых узнал из этого видео:
https://youtu.be/DTyn5ShI2vQ
FFI, или foreign function interface, позволяет выполнять сторонние команды в рамках вашего Foundry теста. Например, вы можете выполнить какую-либо python, linux или другую команду и получить ее значение внутри вашего теста.
Честно признаться, я вообще не встречал не встречал такие тесты в контрактах и не совсем понимаю, для чего они могут потребоваться. Скорее всего, это может быть какой-то уникальный нишевый случай... Или может при связи фронтенда с протоколом.
В видео показывается, как разработчик может эмулировать исполнение команды cat в Linux и прочитать содержимое txt файла в тесте.
Мы создаем обычный файл и пишем там "Hello Foundry". Затем создаем простой тест:
Команда - cmds - это обычный массив строк, где первым элементом будет идти название команды, а вторым - название файла. Также можно сделать строку пропуска с символом "/n", тогда в массиве будет уже три элемента:
string[] memory cmds = new string[](3);
cmds[0] = "cat";
cmds[1] = "-n";
cmds[2] = "ffi_test.txt";
Как вы можете заметить, результатом выполнения этого читкода является байтовое значение, которое нам нужно будет конвертировать в строковое по итогу.
В терминале также потребуется добавить новую опцию для корректной работы теста:
Мы увидим, что в консоли вывелось содержание нашего txt, и значит все сработало как надо.
Если у вас есть примеры реальных тестов протоколов с использованием FFI, поделитесь в комментариях, очень интересно попробовать разобрать их.
#foundry #lesson23
В этом небольшом посте хотел бы написать про FFI тесты, о которых узнал из этого видео:
https://youtu.be/DTyn5ShI2vQ
FFI, или foreign function interface, позволяет выполнять сторонние команды в рамках вашего Foundry теста. Например, вы можете выполнить какую-либо python, linux или другую команду и получить ее значение внутри вашего теста.
Честно признаться, я вообще не встречал не встречал такие тесты в контрактах и не совсем понимаю, для чего они могут потребоваться. Скорее всего, это может быть какой-то уникальный нишевый случай... Или может при связи фронтенда с протоколом.
В видео показывается, как разработчик может эмулировать исполнение команды cat в Linux и прочитать содержимое txt файла в тесте.
Мы создаем обычный файл и пишем там "Hello Foundry". Затем создаем простой тест:
contract FFITest is Test {
function testFFI() public {
string[] memory cmds = new string[](2);
cmds[0] = "cat";
cmds[1] = "ffi_test.txt";
bytes memory res = vm.ffi(cmds);
console.log(string(res));
}
}Команда - cmds - это обычный массив строк, где первым элементом будет идти название команды, а вторым - название файла. Также можно сделать строку пропуска с символом "/n", тогда в массиве будет уже три элемента:
string[] memory cmds = new string[](3);
cmds[0] = "cat";
cmds[1] = "-n";
cmds[2] = "ffi_test.txt";
Как вы можете заметить, результатом выполнения этого читкода является байтовое значение, которое нам нужно будет конвертировать в строковое по итогу.
В терминале также потребуется добавить новую опцию для корректной работы теста:
forge test --ffi -vv
Мы увидим, что в консоли вывелось содержание нашего txt, и значит все сработало как надо.
Если у вас есть примеры реальных тестов протоколов с использованием FFI, поделитесь в комментариях, очень интересно попробовать разобрать их.
#foundry #lesson23
👍5❤1🔥1
Foundry с 0. Часть 24
При всей важности fuzz тестов все обучающие видео и практические примеры сводятся к двум функциям и паре настроек конфигурации Foundry.
Fuzz тесты, или фаззинг, или нечеткое тестирование, по своей сути, это простой программный подбор таких значений в функции, при которых ее исполнение будет "ломаться".
Возьмём простой пример для наглядности:
Тут у нас простая функция для проверки какого-то вводимого числа. Из возможных 256 вариантов num - два не пройдут проверку. Но писать тест для каждого числа было бы достаточно накладно и долго.
Для этих целей и придумали фаззинг. Посмотрите на простой тест для этой функции.
Для начала мы создаем переменную и объект контракта в setUp(), затем пишем простой тест.
Фаззинг отличается от юнит тестов тем, что для функции теста мы оставляем аргументы, которые сам Foundry и будет подбирать.
Вспомните наши предыдущие тесты, где были такие функции как:
function test_IncrementEvent() public {}
в которых нет аргументов, и функцию для фаззинга:
function testFuzz_checkNum(uint8 num) public view {}
Тут мы как бы предлагаем Foundry поработать самому и найти такие числа, которые могут поломать функцию checkNum() в нашем контракте.
Если же упростить тестовую функцию до:
то при вызове теста, через forge test, мы получим примерно такую запись в терминале:
Running 1 test for test/Counter.t.sol:FuzzTest
[FAIL. Reason: EQ 100 Counterexample: calldata=0x6bee15a90000000000000000000000000000000000000000000000000000000000000064, args=[100]] testFuzz_checkNum(uint8) (runs: 95, μ: 8571, ~: 8571)
Обратите внимание на args=[100] - то значение, которые было найдено через фаззинг, и которое ломало вызов наш вызов.
(runs: 95, μ: 8571, ~: 8571) - runs - означает количество "пробегов" Foundry по нашему тесты до момента, когда он нашел значение "на вылет", μ: 8571 - среднее количество потраченного газа, ~: 8571 - значение по газу, которое потратили на "средний" тест. Например, у нас было проведено 95 прогонов, они были проранжированы по мере потребления газа от 1 до 95, и вот под ~ - будет тест находящийся на 48 позиции, т.е. средний из всех 95.
Но вернемся к записи в терминале. Сейчас у нас тест провалился, поэтому добавим новый читкод vm.assume().
vm.assume(num != 0); - будет означать, что наш тест должен "предполагать", что подставленное значение не должно быть равным нулю. Если такое случается, то тест не проваливается, а продолжает цикл.
num = uint8(bound(num, 1, 99)); - эта запись означает, что мы как бы ставим в рамки num значение от 1 до 99. И все значения, которые будет подбирать Foundry будут преобразовываться в числа в промежутке от 1 до 99.
Также стоит сказать пару слов об inline-config. Это такие настройки, которые мы указываем перед тестом через комментарии.
/// forge-config: default.fuzz.runs = 100
В этом случаем мы устанавливаем "пробег" тестов на количестве 100, в то время, как значение по умолчанию равно 256.
Конечно, мы можем установить и 100_000, но проведение такого теста займет куда больше времени, особенно, если вызываемая функция будет намного сложнее, чем в данном примере.
Также есть еще одна настройка:
/// forge-config: default.fuzz.max-test-rejects = 500
которая устанавливает количество "fail" теста перед тем, как он может считаться окончательно проваленным.
При всей важности fuzz тестов все обучающие видео и практические примеры сводятся к двум функциям и паре настроек конфигурации Foundry.
Fuzz тесты, или фаззинг, или нечеткое тестирование, по своей сути, это простой программный подбор таких значений в функции, при которых ее исполнение будет "ломаться".
Возьмём простой пример для наглядности:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract Fuzz {
function checkNum(uint8 num) external view returns(bool){
if (num == 100) revert ("EQ 100");
if (num == 0) revert ("EQ 0");
return true;
}
}
Тут у нас простая функция для проверки какого-то вводимого числа. Из возможных 256 вариантов num - два не пройдут проверку. Но писать тест для каждого числа было бы достаточно накладно и долго.
Для этих целей и придумали фаззинг. Посмотрите на простой тест для этой функции.
contract FuzzTest is Test {
Fuzz public fuzz;
function setUp() public {
fuzz = new Fuzz();
}
/// forge-config: default.fuzz.runs = 100
function testFuzz_checkNum(uint8 num) public view {
vm.assume(num != 0);
num = uint8(bound(num, 1, 99));
fuzz.checkNum(num);
}
}Для начала мы создаем переменную и объект контракта в setUp(), затем пишем простой тест.
Фаззинг отличается от юнит тестов тем, что для функции теста мы оставляем аргументы, которые сам Foundry и будет подбирать.
Вспомните наши предыдущие тесты, где были такие функции как:
function test_IncrementEvent() public {}
в которых нет аргументов, и функцию для фаззинга:
function testFuzz_checkNum(uint8 num) public view {}
Тут мы как бы предлагаем Foundry поработать самому и найти такие числа, которые могут поломать функцию checkNum() в нашем контракте.
Если же упростить тестовую функцию до:
function testFuzz_checkNum(uint8 num) public view {
fuzz.checkNum(num);
}то при вызове теста, через forge test, мы получим примерно такую запись в терминале:
Running 1 test for test/Counter.t.sol:FuzzTest
[FAIL. Reason: EQ 100 Counterexample: calldata=0x6bee15a90000000000000000000000000000000000000000000000000000000000000064, args=[100]] testFuzz_checkNum(uint8) (runs: 95, μ: 8571, ~: 8571)
Обратите внимание на args=[100] - то значение, которые было найдено через фаззинг, и которое ломало вызов наш вызов.
(runs: 95, μ: 8571, ~: 8571) - runs - означает количество "пробегов" Foundry по нашему тесты до момента, когда он нашел значение "на вылет", μ: 8571 - среднее количество потраченного газа, ~: 8571 - значение по газу, которое потратили на "средний" тест. Например, у нас было проведено 95 прогонов, они были проранжированы по мере потребления газа от 1 до 95, и вот под ~ - будет тест находящийся на 48 позиции, т.е. средний из всех 95.
Но вернемся к записи в терминале. Сейчас у нас тест провалился, поэтому добавим новый читкод vm.assume().
vm.assume(num != 0); - будет означать, что наш тест должен "предполагать", что подставленное значение не должно быть равным нулю. Если такое случается, то тест не проваливается, а продолжает цикл.
num = uint8(bound(num, 1, 99)); - эта запись означает, что мы как бы ставим в рамки num значение от 1 до 99. И все значения, которые будет подбирать Foundry будут преобразовываться в числа в промежутке от 1 до 99.
Также стоит сказать пару слов об inline-config. Это такие настройки, которые мы указываем перед тестом через комментарии.
/// forge-config: default.fuzz.runs = 100
В этом случаем мы устанавливаем "пробег" тестов на количестве 100, в то время, как значение по умолчанию равно 256.
Конечно, мы можем установить и 100_000, но проведение такого теста займет куда больше времени, особенно, если вызываемая функция будет намного сложнее, чем в данном примере.
Также есть еще одна настройка:
/// forge-config: default.fuzz.max-test-rejects = 500
которая устанавливает количество "fail" теста перед тем, как он может считаться окончательно проваленным.
👍3🔥1
На самом деле, я ни разу не видел этой настройки в реальных примерах, и не могу со 100% уверенностью обозначить ситуацию, когда она может потребоваться.
Вообще, фаззинг тесты стали популярными в последнее время и все больше команд начинают уделять время на их написание. Они бывают особенно полезны для DeFi протоколов и проверки вводов-выводом активов пользователей.
Уверен, мы не раз встретим такие примеры в рамках разбора реальных тестов от действующих протоколов.
#foundry #lesson24
Вообще, фаззинг тесты стали популярными в последнее время и все больше команд начинают уделять время на их написание. Они бывают особенно полезны для DeFi протоколов и проверки вводов-выводом активов пользователей.
Уверен, мы не раз встретим такие примеры в рамках разбора реальных тестов от действующих протоколов.
#foundry #lesson24
👍4🔥1