В прошлом посте мы впервые встретились с читкодами, помните 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().