921 ETH застрял в эре zkSync: Почему функция Transfer() не работает?
Ознакомьтесь с различиями между функциями transfer(), send() и call()
В прошлом году, The Block исследователь - Иден Ау указал в Twitter, что Gemholic, новый проект на zkSync Era, собрал около 921 ETH за счет продажи токенов GEMS, но собранные средства застряли в смарт-контракте GemstoneIDO из-за проблемы с функцией transfer()
Основная причина этого события кроется в ограничениях функции transfer() в Solidity. В то время как transfer() ограничена 2300 газами, что вполне достаточно для трансферов, функция fallback() или receive() другого контракта не может реализовать слишком сложную логику. Кроме того, если функция transfer() не сработает, транзакция автоматически вернется назад.
Хотя этот метод применим в цепочках, совместимых с EVM, проблема заключается в том, что zkSync Era не полностью совместима с виртуальной машиной Ethereum (EVM). При расчете газа на zkSync Era используется динамический и расходящийся метод измерения газа. Использование функции transfer() на zkSync Era приведет к превышению лимита в 2300 единиц газа, что приведет к автоматическому откату транзакции.
Методы передачи Ether
В Solidity существуют другие методы передачи Ether (или других нативных токенов), помимо transfer(). Ниже перечислены различные методы:
Transfer() - этот метод уже был представлен выше, здесь приведен пример кода:
payable(_address).transfer(1 ether);
Send() - Аналогично, send() также имеет ограничение в 2300 газов, что затрудняет реализацию слишком сложных функций fallback() или receive(). Однако, в отличие от transfer(), send() возвращает значение bool, указывающее на то, была ли передача успешной или нет. Это означает, что даже если передача не удалась, транзакция не будет автоматически возвращена.
bool success= payable(_address).send(1 ether);
Call() - С другой стороны, call() не имеет ограничения по количеству газа, что делает ее способной поддерживать сложную логику для функции fallback() или receive(). Возвращаемое значение call() - это кортеж (bool, data), где bool означает, была ли передача успешной или нет. Это означает, что даже если передача не удалась, транзакция не будет автоматически возвращена.
В настоящее время большинство разработчиков предпочитают использовать call(), поскольку он обеспечивает большую гибкость при разработке. transfer() используется только в тех случаях, когда есть особая необходимость в ограничении газа и автоматическом возврате транзакции при сбое. Кроме того, многие разработчики призывают других избегать использования transfer() в процессе разработки.
С другой стороны, send() имеет ограничения по газу и не обеспечивает автоматического возврата при сбое. Она менее удобна и безопасна по сравнению с call(), поэтому ее редко используют.
Заключение
Ошибка в данном случае возникла из-за недостаточного понимания командой разработчиков различий в средах разработки разных цепочек и отсутствия всестороннего тестирования смарт-контракта. Если бы команда провела тестирование в тестовой сети и заранее обратилась за услугами аудита смарт-контракта, проблему можно было бы обнаружить и устранить.
Смарт-контракты не могут быть изменены после развертывания, поэтому очень важно провести многочисленные тесты и аудиты безопасности до развертывания, чтобы избежать значительных финансовых потерь.