Настало время коснуться темы форков в наших тестах. Почему только коснуться? Потому что, когда я искал какие-либо достойные примеры этого тестирования, то чаще всего встречал либо базовые объяснения, либо копипасту.
И сейчас мы просто поговорим о читкодах и базовых примерах, а уже когда я найду хороший реальный пример реального протокола, то разберем его детальнее.
Итак, поехали.
Для начала, что есть вообще форк-тест?
Форк-тест - это такой вид тестов, когда вы проверяете код своего контракта не на локальном блокчейне, тот же 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