Смарт-контракты — это серия операций, запрограммированных на выполнение определенной логики. Хотя это типичная функция любого программного обеспечения — выполнять операции, смарт-контракты отличаются тем, что все взаимодействия с логикой смарт-контрактов стоят денег, за исключением функции просмотра, не вызываемой смарт-контрактом. А поскольку запрограммированный смарт-контракт является счетом, он подвержен атакам, и средства могут быть украдены.
В связи с возможностью наличия ошибки в смарт-контракте важно проводить тестирование логики контракта, чтобы выявить возможные ошибки. В этой статье вы узнаете, что такое тестирование, поймете различные виды тестов, а также пройдете через некоторые операции тестирования с помощью фреймворка.
Почему вы должны тестировать свои смарт-контракты?
Тесты помогают программистам проверить логику в их кодах. Эти тесты представляют собой запрограммированные сценарии, помогающие убедиться в эффективности алгоритма. Они могут быть запрограммированы для проверки того, что функция, выполняющая сложение при передаче двух параметров, например, sum(2,2), должна быть равна 4.
Этот набор операций важен для выявления ошибок. Тесты, являясь указателем ошибок, крайне важны для ваших смарт-контрактов, чтобы проверить их на наличие возможных уязвимостей. Правильность логики обнаруживается при проведении нескольких тщательных тестов различных сценариев.
Различные виды тестов
Существуют различные виды тестов, которые зависят от количества выполняемых операций. Безусловно, несмотря на проведение тестов и продвижение вашей кодовой базы для аудита с целью уменьшения количества ошибок, существует вероятность обнаружения новых уязвимостей. Возможные сценарии тестирования следующие:
UNIT-TESTING
Юнит-тестирование — это форма тестирования части или небольших блоков вашего приложения на локальном уровне. При этом проверяется правильность выполнения операций в программном обеспечении. В экосистеме, управляемой тестами, отдельные части программного обеспечения тестируются, чтобы убедиться, что они соответствуют закодированной логике.
ТЕСТЫ ДЛЯ ИНТЕГРАЦИИ
Когда функции и библиотеки интегрируются в смарт-контракты, интеграционный тест ожидает проверки правильности логики. Вы можете проверить, что утилиты Openzeppelin, будь то Reentrancy Guard или библиотека SafeMath, работают должным образом, как ожидается, при интеграции в ваш проект.
FUZZ-ТЕСТИРОВАНИЕ
Фазз-тестирование — это введение неправильных или некорректных входных данных для обнаружения дефектов в программном обеспечении. Его часто называют fuzzing, потому что это процесс выполнения или взаимодействия с приложениями в неожиданных сценариях с целью выявления дефектов в процессе разработки.
СКВОЗНОЕ ФУНКЦИОНАЛЬНОЕ ТЕСТИРОВАНИЕ
Эта форма тестирования представляет собой полный тест для проверки всех функциональных возможностей приложения. При этом приложение тестируется так, как будто оно работает в основной сети. Это включает в себя форкирование mainnet, выдачу адресов за свои, установку баланса адреса, получение времени хранения и деформирования и многие другие функции.
Тестирование смарт-контракта с помощью Hardhat
Hardhat — это быстрый, расширяемый и гибкий инструмент разработки ethereum для создания, тестирования и развертывания смарт-контрактов. Это фреймворк на Javascript, созданный для помощи в составлении контрактов, тестирования на возможные ошибки, отладки ошибок и развертывания в различных сетях. Фреймворк Hardhat поставляется с большим количеством пакетов, позволяющих написать свой контракт, а также позволяет использовать эфиры для тестирования смарт-контракта и запуска скриптов для развертывания.
Мы будем тестировать контракт по умолчанию, который поставляется с фреймворком Hardhat. Вы можете ознакомиться с этим для установки.
Когда вы успешно установите hardhat, вы найдете контракт ниже в contracts/Greeter.sol.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Greeter {
string private greeting;
constructor(string memory _greeting) {
console.log("Deploying a Greeter with greeting:", _greeting);
greeting = _greeting;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
greeting = _greeting;
}
}
Контракт Greeter — это простой контракт, который имеет приватную строковую переменную состояния, называемую greeting. У него есть конструктор, который получает в качестве параметра строковую переменную памяти. Этот параметр устанавливает в конструкторе консольный журнал с импортированной консолью hardhat, а следующая строка устанавливает строковую переменную state на вход из конструктора.
Две функции, та, что с видимостью представления, и та, что без нее, читают из state и записывают в state соответственно. Функция вида greet считывает текущее значение приветствия. Другая, setGreeting, получает параметр для изменения или установки приветствия.
Тест по умолчанию, который поставляется с приведенным выше контрактом, выглядит следующим образом:
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
expect(await greeter.greet()).to.equal("Hello, world!");
const setGreetingTx = await greeter.setGreeting("Hola, mundo!");
// wait until the transaction is mined
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal("Hola, mundo!");
});
});
Это тест Chai. Chai — это фреймворк тестирования Javascript для проверки на наличие ошибок. Другими популярными являются Mocha и Jest. Важно добавить, что с появлением Foundry появилась возможность запускать тесты полностью на языке Solidity.
Согласно коду, сначала импортируется expect из chai и импортируется ethers. Метод description служит для идентификации тестируемой функции. В описании it отмечается основная цель теста.
Чтобы протестировать функцию в контракте, важно сначала развернуть контракт, а затем сделать его функции доступными для тестирования. Именно здесь в развертывании помогают эфиры. Greeter — это константа, которая получает контракт для развертывания, константа greeter передает значение в конструктор, а следующая строка развертывает контракт.
Первое ожидание проверяет, что при вызове функции greeter, которая является функцией чтения, выходные данные будут равны переданному значению «Hello World! Следующая строка — это вызов функции setGreeting и переданное ей значение. Следующая за ней строка проверяет, что транзакция успешно завершена, а затем ожидает, что новое значение приветствия действительно является новым значением.
Существуют и другие возможные проверки, которые делает доступными эта структура тестов. К ним относятся expectRevert, beforeEach, greaterThan и многие другие.
Все они предназначены для проверки правильности выполнения операции, соответствует ли она своему назначению. Тестирование гарантирует вам, что ваш контракт менее подвержен атакам.
Я надеюсь, что это введение в тестирование смарт-контрактов поможет вам выработать привычку тестировать свою логику, чтобы ваш контракт не стал следующей целью хакеров.