В данной статье мы познакомимся с тем, как задеплоить очень простой смарт-контракт на локальный блокчейн Ganache. После развёртывания смарт-контракта, мы научимся взаимодействовать с ним путём отправки транзакций в его адрес. Для простоты я буду использовать фреймворк Truffle, так как он упрощает деплой и взаимодействие со смарт-контрактом. В качестве контракта мы создадим самый простой Faucet. В реальных условиях Faucet используется как хранилище криптовалюты, с которого любой желающий может перевести некоторое количество средств на свой баланс. Используются Faucet в тестовых сетях. В одной из своих прошлых статей я показывал как использовать реальный Faucet. Наш контракт будет иметь всего две функции: Одна для пополнения баланса самого смарт-контракта Другая для перевода средств с баланса смарт-контракта на аккаунт, который вызвал эту функцию. В аргументе функции будет передаваться требуемое количество Ether. Во второй функции будет стоять ограничение - не более 0.01 ETH за одну транзакцию. Если будет запрошено больше, то транзакция завершится с ошибкой. Примечание: даже в случае завершения транзакции с ошибкой, с отправителя всё равно будет списано некоторе количество Ether за отправку транзакции. Это плата за Gas, который потребовался для обработки транзакции на нодах. Таким образом исключается недобросовестная нагрузка на сеть, когда злоумышленник нагружает сеть заведомо ошибочными транзакциями, и попусту использует её вычислительную мощность. За такие транзакции он всё равно будет платить, и рано или поздно израсходует все свои Ether. Поэтому, например, контракт с бесконечным циклом не нанесёт особого вреда ресурсам блокчейна. Создадим контракт Пополним баланс контракта с аккаунта A Обратимся с аккаунта Б к контракту с просьбой перечислить средства на баланс  в желаемом количестве Подразумевается, что у вас уже установлен Ganache и Truffle. Если нет, вы можете ознакомиться с установкой в моей предыдущей статье. Если такие понятия как EOA, Contract account, транзакция, Gas для вас незнакомы, то вы можете обратится к моим предыдущим статьям, ссылку на которые я оставлю в конце руководства. Итак, приступим и создадим контракт. Создадим рабочую директорию и инициализируем truffle-проект: После инициализации проекта, наш рабочий каталог будет выглядеть следующим образом: С назначением этих каталогов мы разберёмся по ходу дела, а пока в папке contracts создадим наш контракт и назовём его Faucet.sol: Вторая строка pragma solidity  определяет совместимость контракта с разными версиями компиляторов. В моём случае я указал все версии 0.8.X. Solidity очень динамично развивающийся язык, и как правило версии  0.X и 0.Y несовместимы друг с другом. Если вы попытаетесь скомпилировать наш контракт, скажем версией компилятора 0.7.6, то компилятор после прочтения строки pragma solidity  сразу выдаст ошибку совместимости, и не приступит к дальнейшей компиляции. Я компилировал контракт на версии Solidity 0.8.20, это была последняя версия на момент написания статьи. Вы можете указать версию компилятора в файле truffle-config.js, и тогда при компиляции фреймворк Truffle сам подтянет нужную версию. Фрагмент файла конфигурации truffle-config.js с версией компилятора 0.8.20: Скомпилируем наш контракт: После успешной компиляции в папке build/contracts должен появиться файл Faucet.json - это файл для внутренних нужд фреймворка, который нужен для деплоя смарт-контракта в блокчейн, а также для упрощения взаимодействия с контрактом из кода приложения. Чтобы фреймворк Truffle понял какой контракт ему задеплоить, мы должны указать ему на наш контракт. Для этого в папке migrations создадим файл 1_faucet_migration.js и добавим в него следующий код: После проделанных шагов, ваше окружение должно выглядеть так: У нас почти всё готово для деплоя контракта. Осталось запустить Ganache и добавить одну настройку. Запустим Ganache: Для того чтобы Ganache смог визуализировать смарт-контракт, мы должны указать ему на файл конфигурации проекта с контрактом. Имя файла конфигурации: truffle-config.js. Для этого зайдём на вкладку Contracts, и нажмём на LINK TRUFFLE PROJECTS: В появившемся окне нажимаем ADD PROJECT, находим папку с нашим проектом и указываем на файл конфигурации truffle-config.js: Затем нажимаем на SAVE AND RESTART. После рестарта во вкладке Contracts должна появиться информация о нашем контракте. Как видим, он ещё не задеплоен: Отлично. Теперь у нас всё готово для деплоя контракта. Для этого в консоли набираем: Вывод: Отлично. Контракт задеплоен. В самом низу, в разделе 'Summary' видим поле 'Final cost', а рядом количество в Ether, равное 0.000426427875 ETH. Это пошлина в виде Gas за создание контракта. А теперь вопрос. Кто заплатил эту пошлину?  Если посмотреть выше, то мы увидим поле account: 0x5591B981a1133b044B36d82502e838f597b0af6D, а под ним есть поле balance: 99.999573572125. Зайдём в Ganache и найдём этот аккаунт. Это будет первый аккаунт. Почему именно первый аккаунт, ведь мы не указывали никаких дополнительных данных в Truffle при деплое контракта? Ответ заключается в том, что у Ganache есть аккаунт по-умолчанию, и по дефолту это первый аккаунт. Ниже я покажу как осуществлять транзакции от имени других аккаунтов. Раз контракт задеплоен, и мы разобрались с кого была списана пошлина за создание контракта, тогда давайте зайдём в Ganache и убедимся, что баланс первого аккаунта уменьшился: Кажется, что-то тут не так. Как видим, ни один баланс не изменился. Дело в том, что Ganache при отрисовке баланса округляет его в большую сторону, и поэтому мы не видим списания мелких сумм. Для того чтобы точно узнать баланс, мы можем посмотреть его из консоли простым вызовом web3.js библиотеки, которая входит в состав фреймворка Truffle. Для этого войдём в консоль Truffle: Должна появится строка: В примерах кода ниже, если речь будет идти именно о вызове команд в консоли Truffle, я буду писать символ > перед вызовом команды. Проверяем баланс: Отлично, видим, что баланс изменился ровно на то количество Ether, которое было в поле 'Final cost'. Для выхода из консоли Truffle можно использовать Ctrl + D. Но она нам ещё пригодится на следующих шагах, поэтому можно её пока не закрывать. Давайте ещё раз зайдём в Ganache и посмотрим на статус нашего контракта: Статус сменился на DEPLOYED. Провалимся в сам контракт. Видим адрес контракта и его баланс: Можем зайти во вкладку Transactions и посмотреть на саму транзакцию, создавшую контракт: Контракт задеплоен, а это значит, что пора переходить к пополнению его баланса. Пополним баланс нашего контракта, скажем, на 70 Ether. Переведём средства с первого аккаунта. Адрес отправителя и адрес контракта можно взять из Ganache GUI. Транзакция на пополнение баланса контракта: Квитанция о проведённой транзакции: Транзакции прошла успешно. Проверим балансы EOA и контракта. Баланс первого аккаунта: Баланс контракта: В Ganache мы тоже видим, что с отправителя списалось 70 Ether: А баланс контракта пополнился на 70 Ether: Кстати, зачисление средств на контракт мы произвели обычной транзакцией, которая ничем не отличается от перевода средств с EOA аккаунта на другой EOA аккаунт, и мы явно не вызывали никаких функций контракта. Это вовсе не означает, что контракт так же как и EOA, всегда может принимать Ether на свой счёт. Здесь в дело вступила разновидность fallback функций - функция receive(), которая была специально добавлена в Solidity именно для целей явного перечисления Ether на контракт. Существует ещё одна разновидность fallback функции, и она существовала до введения функции receive(). C помощью неё можно точно так же перевести средства на контракт: Функция fallback() отрабатывает тогда, когда мы вызываем конкретный метод, но его нет в вызываемом контракте, а если в транзакции в поле value были ещё и Ether, то они зачисляются на баланс контракта. Средства будут зачислены на контракт, только в случае наличия ключевого слова payable. Если же мы решили отправить Ether на контракт в котором нет ни одной fallback функции, то данный контракт не может принимать Ether через обычные транзакции, а транзакция завершится с ошибкой. Контракт может содержать и две fallback функции одновременно. Логику работы этих функций можно кратко изобразить схемой: msg.data - это поле data в теле транзакции, в которой обычно передаётся информация о вызываемом методе и его аргументах, а в случае создания контракта, то код самого контракта. Таким образом функция receive() была специально создана именно для случаев намеренного пополнения баланса контракта, чтобы более старая функция fallback() не выполняла несколько задач одновременно (принцип единой ответственности). Примечание: при изучении, вы можете воспользоваться Web IDE, такой например как Remix IDE, чтобы быстро изучить поведение контракта, и вручную протестировать интересующие вас кейсы. Такие инструменты как Truffle и Ganache уже нужны для процесса разработки и автоматического тестирования. Отлично, наш Faucet теперь имеет средства на балансе, и может выполнять своё назначение, а именно - позволить остальным воспользоваться этими средствами. На этом шаге мы обратимся со второго аккаунта к контракту, и вызовем его метод withdraw(), чтобы снять с контракта 0.01 Ether. Для взаимодействия с контрактом я воспользуюсь абстракцией над контрактом, которую получу при помощи фреймворка Truffle, так как это упростит отправку транзакции. Для получения абстракции нашего контракта, в консоли Truffle выполним следующий код: Теперь можно обратиться к методу контракта withdraw() простым вызовом: Обратим внимание на дополнительный объект с полем from, который мы передали вторым аргументом в вызов метода. Его нет в сигнатуре оригинального метода в контракте. При помощи этого объекта мы указали фреймворку, что хотим выполнить транзакцию с конкретного аккаунта, в нашем случае со второго. Если бы мы не передали этот объект, то транзакция ушла бы с аккаунта по-умолчанию, то есть с первого. Квитанция транзакции: Проверяем балансы. EOA аккаунт после зачисления 0.01 Ether с контракта: Видим, что зачислилось чуть меньше 0.01 Ether, так как при вызове метода контракта нужно платить за Gas. Контракт после снятия с него 0.01 Ether: Транзакция с вызовом метода withdraw(): Баланс контракта после снятия 0.01 Ether: Баланс второго аккаунта после зачисления с контракта 0.01 Ether: Все наши транзакции снизу вверх. Создание контракта, перевод средств на контракт и вызов метода контракта для снятия средств: Кстати, если мы зедеплоим наш смарт-контракт в тестовой сети, то любой желающий может списать с него Ether, ну или пополнить баланс контракта. Как подключаться к тестовой сети я рассказывал здесь. На этом всё. Наше введение во взаимодействие с контрактом подошло к концу. Мы научились создавать контракт в локальном блокчейне Ganache, узнали как взаимодействовать с контрактом, и познакомились с fallback функциями. Локальное окружение для изучения web3.js и Ethereum Создаём, подписываем и отправляем Ethereum транзакцию при помощи web3.js и Ganache Подключаемся к Ethereum Testnet используя только web3.js и консоль Адреса в Ethereum