Сегодня хотел поделиться кейсом, который встретился в одном из недавних аудитов, а также обсудить тему правильного тестирования.
В конкурсе протокола Diva, который проходил на платформе CodeHawks, был занятный баг, который пропускался даже в тестах, не смотря на то, что был в конструкторе контракта.
Суть его была в том, что в одном контракте в конструкторе также создавался и другой:
constructor(address _aaveV3Pool, address _diva, address _owner) AaveDIVAWrapperCore(_aaveV3Pool, _diva, _owner) {}
constructor(address diva_, address aaveV3Pool_, address owner_) Ownable(owner_) {}
Обратите внимание, что аргументы передаются в не совсем верном порядке: адреса aave и diva перепутаны местами.
Хоть его и засчитали как Medium (по предварительным результатам), мне кажется, что правильнее было бы установить как Low. Контракты сразу после деплоя были бы не рабочими и, вроде как, ни к каким другим последствиям, кроме как ре-деплой, это бы не привело. Но примечательно другое.
Разработчики написали тесты, которые успешно проходили!
В общем, они были написаны правильно: тестировались необходимые функции и ветки, но что-то все же пропустилось... И это случилось у хороших разработчиков.
Разработчикам, которые только начинают писать тесты для своих контрактов, бывает сложно понять саму суть проводимых тестов. Они пишут unit тесты и стараются получить 100% coverage в итоге, не представляя, что тестирование может быть вне рамок кода.
Например, когда мы пишем тест для проверки реверта в require, в итоге нам нужно получить не сообщение об ошибке, которое порождается этой проверкой, а такие состояния кода, при которых она возникает. А это может быть не совсем директивное поведение пользователя.
Или также дело обстоит с проверками if/else. Тут тесты пишутся не только на два условия, но и для всех состояний, ролей и временных отрезков, которые могут повлиять на эту проверку.
Именно поэтому, на 1000 строк кода контракта мы можем встречать 5000+ строк тестов.
Если вы хотите стать лучше в этом деле, то уже с момента написания самого контракта, начните вести файл, куда будете записывать проверки. Пишем "pragma ..." - думаем на каких сетях это будет работать, какие проблемы есть в текущей версии языка. Пишем "MyContract is..." - думаем над порядком наследований и передачей параметров, пишем переменную - думаем на размерностью и слотом в памяти.
Это сложно, но так вы и сами сможете написать более безопасный код и более подробную документацию, которая поможем аудиторам в скором будущем.
Пишите правильные тесты!
#testing