Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Foundry с 0. Часть 6

Во введении курса по Foundry, думаю, стоит пару слов сказать о настройках самой программы и различных опциях, которые вы можете задавать в начале пути тестирования своих смарт контрактов.

Вся конфигурация Foundry лежит в файле foundry.toml, который появляется в момент инициализации проекта и лежит в корневой папке.

Вообще, формат toml для меня был некоторым открытием при изучении Foundry. Вы знали, что это, по своей сути, отдельный язык?

Toml, или Tom's Obvious Minimal Language, это специальный язык для конфигурационных файлов, который просто читать и писать. Более того, из него достаточно просто парсить информацию!

В общем, если хотите узнать о нем чуть больше, рекомендую посетить его официальную страницу:

https://toml.io/en/

Но вернемся к Foundry.

Итак, все настройки лежат в файле foundry.toml. Если открыть его сразу после старта проекта, то можно увидеть что-то типа такого:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]

P.S. Актуально на момент написания поста. Среда развивается и вскоре могут появиться другие записи.

Настройки можно разделить на три категории: по умолчанию, общие и профильные.

profile.default - как можно догадаться, это профиль по умолчанию в Foundry. Скажем так, он самый главный.

Если вы создадите другие профили, например [profile.local], то тут можно указать какие-то специальные настройки для текущего проекта, которые в свою очередь, будут наследовать настройки из profile.default.

Смотрите, что получается. Где-то на своем компьютере вы можете создать корневой файл foundry.toml с дефолтным профилем и записать самые общие настройки там. А уже в каком-либо конкретном проекте - создавать новый файл foundry.toml с новым профилем для данного протокола и получать дефолтные настройки из главного файла, настроив предварительно пути.

На самом деле, я ни разу не видел такого применения, и для каждого проекта, который я аудировал, в корневой папке был дефолтный профиль. Но возможность есть, и о ней стоило рассказать.

Существует огромное количество настроек программы под свои нужды, попробую перечислить некоторые из них:

1. src - путь до контрактов от корня проекта;
2. test - путь до тестов от корня проекта;
3. out - путь до артефактов от корня проекта;
4. libs - массив путей с библиотеками от корня проекта;
5. cache - включить / выключить кэширования.
6. broadcast - путь для транслирования записей транзакций;
7. force - включение / выключение очистки кэша при сборке проекта;
8. solc_version - версия Solidity для компилятора;
9. ignored_error_codes - игнорировать ошибки в коде при сборке проекта;
10. evm_version - версия EVM, например london или byantium;
11. optimizer - вкл/выкл оптимизатора;
12. optimizer_runs - количество "пробегов" оптимизатора;
13. via_ir - вкл/выкл "прохода" компиляции через IR оптимизатор;
14. etherscan - установка параметров для работы с etherscan;

Более подробно об этих и множестве других настроек можно узнать отсюда:

https://book.getfoundry.sh/reference/config/

Для установки настроек в Foundry достаточно добавлять соответствующие записи в файл foundry.toml. Например, просто прописав:

optimizer = true
optimizer_runs = 20_000

Мы включим оптимизатор кода и установим его "пробег" на 20 000.

Надо также сказать, что какие-то уникальные настройки программы нам потребуются не так часто. По мере прохождения уроков по написанию тестов, я будут обращать ваше внимание на те или иные возможности конфигурации.

А если вы встретите какой-либо не понятный параметр в этом файле, то смело можете зайти на страницу официальной документации и через поиск решить свой вопрос.

#foundry #lesson6
👍7🔥1
Foundry с 0. Часть 7

Постепенно переходя к практической части написания тестов, стоит также сделать небольшой пост про библиотеки Foundry, которые используются для этого.

На данный момент в Foundry используется набор стандартных библиотек - Forge Std, которые хранятся в папке lib в корне проекта. Всего их около 17 штук, но чаще всего используют всего 3-4 из них, а другие уже по умолчанию наследуются.

1. Test.sol - основная библиотека, которую мы наследуем в контракте-тесте. Она уже содержит в себе практически все остальные библиотеки.

2. console.sol или console2.sol - использование логирования данных, как в JavaScript через console.log().

3. Vm.sol - библиотеки для возможности использования специальных читкодов для управления локальным состоянием блокчейна.

4. Script.sol - базовые инструменты для написания скриптов.

5. StdAssertions - тестирование и сравнение результатов.

6. StdErrors - вывод ошибок.

7. StdStorage - работа с памятью при тестировании.

Более развернутую информацию по доступным библиотекам можно почитать тут:

https://book.getfoundry.sh/reference/forge-std/

Но, как я уже написал выше, в своей практике достаточно будет использовать Test.sol для первых этапов. Позже будем подключать и другие по мере необходимости.

#foundry #forgestd #lesson7
👍5🔥1
Foundry с 0. Часть 8

Сегодня мы поговорим о старте нового проекта на Foundry и подготовке рабочей среды.

P.S. Ранее, в нулевом разделе, был описан процесс установки программы на свой компьютер, поэтому дальше текст будет подразумевать, что он уже у вас имеется.

Итак, в самом начале нам нужно зайти в папку, в котором мы будем писать наш проект и выполнить команду:

forge init

Она создаст несколько файлов и папок, в которых мы уже и будем дальше учиться писать тесты. Пройдемся по ним:

.github - для работы с git;
lib - тут находятся контракты для импорта(например, OpenZeppelin);
noscript - вспомогательные скрипты для деплоя;
src - основная папка для наших контрактов;
test - папка для тестовых контрактов;
.foundry.toml - настройка для Foundry в данном проекте;

Свой проект мы можем писать, создавая новые файлы в папке src, а тесты - в test. Я проговариваю это еще раз, так как видел проекты, в которых разработчики создавали отдельные папки все src для контрактов, а потом жаловались, что что-то не работает.

Старайтесь держать структуру файлов в корневой папке.

Если же вы получили готовый проект для написания тестов и скачали его с GitHub, то подготовить его в своей рабочей среде можно выполнив команду:

forge install

Она установит необходимые зависимости и настроит пути.

Да, несколько слов о путях к файлам.

В некоторых проектах можно встретить отдельный файл remappings.txt. Именно в нем вы можете прописывать уникальные пути к файлам. Например, после установки контрактов от Open Zeppelin, они будут находиться в папке lib и иметь полный путь:

lib/openzeppelin-contracts/contracts/token/ERC20.sol 

Вы можете модифицировать его, сократив до

@openzeppelin/token/ERC20.sol

Для этого, в файле remappings.txt следует прописать:

@openzeppelin/ = lib/openzeppelin-contracts/contracts/

И теперь "под капотом" он будет заменяться на более короткий.

Это бывает удобно, когда у вас большая файловая система в проекте.

Также, если вы видите ошибку в контракте, когда редактор подчеркивает импорты, то одним из первых действий желательно проверять эти пути в этом файле, так как у разработчиков они могут быть прописаны иначе от вашей установки.

#foundry #lesson8
5🔥1
Foundry с 0. Часть 9

На следующей неделе мы понемногу начнем разбирать уже практическую часть написания тестов на Foundry. А перед этим нужно рассказать, какие тесты вообще можно делать для своего проекта.

1. Unit тест

Самый простой вид тестирования. По сути, мы проверяем, что функция выполняет свою роль и выдает результат, который должен считаться валидным.

2. Fuzz тест

Немного сложно перевести основную идею этого вида тестирования, поэтому его чаще всего и называют фаззинг. Это такой вид тестов, когда сам Foundry подставляет аргументы в тестируемую функцию и пытается найти такие, которые могут сломать ее.

Грубый пример, у нас есть функция перевода, которая допускает значения меньше uint112. И вот мы, как бы говорим, Foundry найди такие значения, чтобы функция откатилась. Он в аргументы подставляет разные значения от uint4 до uint256 и выдает результат.

Если сейчас не понятно, то с практикой вы лучше осознаете всю ее пользу.

3. Invariant тесты

Сравнительно недавнее веяние в мире тестов. Активно его стали развивать только в последние полгода.

Инварианты - это такие значение, которые всегда должны быть "правильными"... Я сам долго пытался понять это, но объяснить лучше на примере.

Вот вы запустили свой токен. И вот его totalSupply никогда не должен быть больше общего количества токенов у пользователей. Или, что количество токенов на вывод не должно превышать количество токенов при депозите.

Такие параметры сложно проверить из-за того, что при первых двух видах тестов состояния блокчейна обнуляется каждый раз, а в данном случае оно должно "запоминаться" на изменения. Подробнее об этом в постах с соответствующей темой.

4. Differential тесты

Более сложные по своей основе тесты, которые иногда еще называют differential fuzzing.

Это такой метод тестирования, который включает в себя исполнение различных реализаций одной и той же функции или логики, с последующим сравнение их результатов.

Я сам не писал еще такие тесты, поэтому будем позже разбираться с ними вместе.

5. Fork тесты

Это тесты ваших контрактов на "скопированном" участке реальной сети блокчейна. Такое тестирование бывает полезно, когда вы хотите узнать, будет ли ваш код работать на различных версиях EVM.

6. Дебаггинг

Возможно, не совсем тестирование, но это мощный инструмент для работы со смарт контрактами и отслеживании путей работы ваших функций. Уверен, что каждый хороший разработчик на одном из этапов своего обучения задавался целью как можно лучше освоить эту тему.

В рамках каждого вида тестов мы можем проверять различные условия в коде (if/else, require), математические операции, порождение событий и вообще практически все, что захотим.

В помощь этому был создан прекрасный инструмент forge coverage, который показывает участки кода требующие проведения тестирования и само покрытие тестами всего контракта.

Со следующей неделе мы начнем разбирать практическую часть написание тестов. А впереди у нас еще много работы!

#foundry #lesson9
👍7
Foundry с 0. Часть 10

Мы закончили с введением в Foundry и скоро начнем изучать написание тестов.

Понимаю, что для многих текстовой формат может не подходит, поэтому выкладываю ссылки на несколько крутых ресурсов с Ютуб, которые помогли мне в свое время.

Два видео на русском языке:

1. Foundry: альтернатива Hardhat для разработки/тестов

2. Нечёткое тестирование (fuzzy testing) в Foundry

И несколько на английском:

3. Learn Solidity, Blockchain Development, & Smart Contracts

4. Introduction | Testing with Foundry

5. How to Foundry with Brock Elmore

6. Testing with Foundry

7. Fuzz & Invariant Tests

8. How to Foundry 2.0: Brock Elmore

Этих видео будет достаточно для того, чтобы погрузиться в Foundry и самому начать писать тесты.

Всем приятных выходных и легкого обучения!

#foundry #lesson10
👍3🔥31
Foundry с 0. Часть 11

Сегодня мы начинаем практиковаться с тестами и пойдем также с самого начала. Мы разберем все "по кирпичикам", постепенно усложняя и материал и тесты.

Что нужно знать перед написанием тестов?

Во-первых, все тесты пишутся в папке test. Можно, конечно, создавать новые папки, но придется переписывать пути для Foundry и производить другие не нужные манипуляции. Поэтому проще все делать уже в одной папке, в которой строить нужную нам архитектуру.

Во-вторых, по правилам Foundry название файлов для тестов должно значится как fileName.t.sol. Другими словами окончание t.sol обязательно для каждого файла теста.

В-третьих, практически в каждом файле тестов нам нужно будет импортировать библиотеку Test от Forge и контракт, для которого будут писаться тесты:

import {Test, console2} from "forge-std/Test.sol";

В четвертых, уже наш тестовый контракт обязательно должен иметь наследование от Test:

contract myContract is Test {}

Возьмем, к примеру, файл контракта и файл теста, которые создаются при инициализации проекта: Counter.sol:

Наш контракт:

contract Counter {
uint256 public number;

function setNumber(uint256 newNumber) public {
number = newNumber;
}

function increment() public {
number++;
}
}


И напишем к нему простые тесты. Для начала импортируем библиотеку Test и наш контракт, а также создаем наследование:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract CounterTest is Test {}

Из Test, как вы видите, мы получаем два объекта: сам Test и еще Console. Второй объект поможет нам логировать данные из тестов и выводить их в консоль, очень похоже на console.log из javanoscript.

Далее нам нужно будет создать объект нашего контракта и поместить его в переменную.

Counter public counter;

Но как поместить объект контракта в переменную при том, что в Foundry нет конструктора?

Для этого есть специальная служебная функция setUp(). Если вы переходите на Foundry с Hardhat, то можете представить себе setUp() как нечто подобное loadFixture или beforeEach. Другими словами setUp() будет исполняться перед каждым отдельным тестом. Это нужно для того, чтобы наш локальный блокчейн, на котором и будут проходить все тесты, не хранил изменения в стейте, а очищал его. Так сами тесты будут более достоверными.

Итак, в setUp() мы будем разворачивать наш контракт Counter:

setUp() public {
counter = new Counter();
}

Теперь можно написать наш первый тест.

Самое главное, что нужно запомнить сейчас так это то, что название тестов:

1. Должны начинаться со слова test;
2. Должны кратко описывать то, что они тестируют;

В нашем случае мы можем создать тест с названием:

function test_Increment() public {}

Также следует запомнить, что все функции для тестов должны иметь область видимости public или external, чтобы программа могла работать с ними.

Итак, теперь нам нужно понять логику проведения тестов. По мере постов, вы более подробно поймете, о чем идет речь, поэтому сейчас напишу по простому.

Смотрите, у нас есть функция, которую мы хотим протестировать. Результатом работы этой функции будет какое-то значение или действие: изменение переменной, баланса, голосов, доступа или чего-то еще. Т.е. мы ожидаем определенные результаты.

В тестах нам и нужно сравнить наши ожидания с тем, что явилось результатом исполнения функции. Например, у нас в контракте есть функция increment(). Что она делает? Она прибавляет 1 к значению в number.

Как написать для этого тест?

function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
//console2.log(counter.number());
}

В начале мы вызываем функцию в нашем контракте для изменения переменной number, а затем проверяем, что наше ожидаемое значение будет равно значению, которое сохранилось в переменной. С помощью console2.log мы смотрим значение number.

Теперь нам нужно обратиться к Foundry в терминале и попросить сделать тест. Это делается через команду:
👍1
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.

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:

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
👍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, и создав соответствующую переменную. Более того, для простых тестов добавим еще модификатор и простую функцию:

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. Проверим это в тестах. Напишем простой тест:

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 может получиться такой:

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
👍23🔥41
Foundry с 0. Часть 16

Думал тему из этого поста постепенно поднимать в нескольких других, но понял, что лучше основную идею изложить сейчас, а потом уже дополнять при необходимости.

А поговорим мы о работе с консолью: вызовом тестов и логированием из них.

Из предыдущих постов мы узнали, для того чтобы вызвать процесс тестирования нашего контракта, достаточно в консоли прописать:

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.

Вообще, для того, чтобы посмотреть отчет о покрытии кода тестами, нужно выполнить простую команду:

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 в проекте есть, поэтому далее выполняем команду:

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 команды нам нужно было часто указывать специальную ссылку для подключения к блокчейн узлам, чтобы можно было получать необходимую информацию. Мы писали что-то типа:

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.

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, побитовые операции и базовый дебаггинг.

Достаточно много времени будет уходить на изучение каждой темы и детальное ее понимание для учеников.

И у меня возник вопрос к вам: стоит ли его проводить в этом году?

Я хочу сказать, что он точно будет, только нужно выбрать для этого время.

Много учеников еще до проходят предыдущие модули в своем темпе, разбирая каждый урок и делая практические задания. Новый модуль будет "вместить" в себя достаточно сложно.

Я бы предложил запустить его после новогодних праздников, когда все до пройдут текущие модули.

А что думаете вы?

Прошу поучаствуйте в опросе ниже.