В завершении базовых постов по инвариант тестам стоит поднять тему Actor management.
В прошлом уроке мы написали более продвинутый тест от имени одного контракта, а теперь посмотрим, как организовать процесс теста, когда вызовы в наш WETH контракт идут от нескольких Handler, что имитирует вызовы функций от разных пользователей.
Как вы можете догадаться, что нам потребуется дополнительный контракт:
contract ActorManager is CommonBase, StdCheats, StdUtils {
Handler[] public handlers;
constructor(Handler[] memory _handlers) {
handlers = _handlers;
}
function sendToFallback(uint256 handlerIndex, uint256 amount) public {
uint256 index = bound(handlerIndex, 0, handlers.length - 1);
handlers[index].sendToFallback(amount);
}
function deposit(uint256 handlerIndex, uint256 amount) public {
uint256 index = bound(handlerIndex, 0, handlers.length - 1);
handlers[index].deposit(amount);
}
function withdraw(uint256 handlerIndex, uint256 amount) public {
uint256 index = bound(handlerIndex, 0, handlers.length - 1);
handlers[index].withdraw(amount);
}
}Здесь мы создаем массив из объектов нашего контракта Handler и инициализируем их в конструкторе.
Далее нам также нужно повторить те же функции в Handler, которые мы планируем вызывать.
Обратите внимание на аргументы в этих функциях! Мы позволяем Foundry самому случайным образом выбрать один из наших Handler и послать вызов от его имени. Не забудьте использовать читкод bound(), чтобы Foundry мог использовать индекс контракта, который действительно есть в массиве.
Далее обновим наш контракт с тестом:
contract WETH_Multi_Handler_Invariant_Tests is Test {
WETH public weth;
ActorManager public manager;
Handler[] public handlers;
function setUp() public {
weth = new WETH();
for (uint256 i = 0; i < 3; i++) {
handlers.push(new Handler(weth));
// Send 100 ETH to handler
deal(address(handlers[i]), 100 * 1e18);
}
manager = new ActorManager(handlers);
targetContract(address(manager));
}
function invariant_eth_balance() public {
uint256 total = 0;
for (uint256 i = 0; i < handlers.length; i++) {
total += handlers[i].wethBalance();
console.log("Handler num calls", i, handlers[i].numCalls());
}
console.log("ETH total", total / 1e18);
assertGe(address(weth).balance, total);
}
}В функции setUp() мы создаем объекты наших контрактов и пополняем балансы каждого созданного Handler.
В конце, выбираем контракт manager как целевой для тестов. Напомню, что если этого не сделать, то инвариант тест будет проходить по каждому созданному контракту и вызывать случайным образом функции в них.
В приведенном выше тесте мы хотим удостовериться, что баланс токенов WETH будет равен или больше балансам всех наших Handler контрактов.
Именно поэтому мы создаем новую переменную total, в которую записываем все значения от переменных wethBalance в каждом Handler.
Запускаем наш тест через консольную команду forge test и видим, что все прошло успешно без каких-либо откатов!
Чуть позже мы посмотрим еще на реальные инвариант тесты от действующих протоколов, а сейчас предлагаю вам попрактиковаться самим и пересмотреть прекрасные видео от Smart Contract Programmer.
#foundry #lesson30