Как разрабатывать, тестировать и развертывать смарт-контракты с помощью Ganache

Читать первым в Telegram

Разработка, тестирование и развертывание смарт-контрактов – это фундаментальные понятия блокчейн-разработки. В этом руководстве вы узнаете, как начать работать со смарт-контрактами в блокчейне Ethereum.

Прежде чем перейти к делу, важно ознакомиться с основными концепциями криптографии и блокчейна. Если вы еще не знакомы с ними, рекомендуем посмотреть это короткое видео.

Что такое смарт-контракт?

Смарт-контракты – это неизменяемые программы, хранящиеся в блокчейне. Они автоматизируют выполнение транзакций по заранее определенным условиям, что позволяет им широко использоваться для децентрализованного исполнения контрактов без посредников.

Смарт-контракты способны возвращать лишь чётко определенный набор результатов, регулируемый их неизменяемым кодом, благодаря которому участники контракта могут быть уверены в согласованности его исполнения. Никакого участия третьих лиц и потерь времени – контракты исполняются сразу после выполнения условий, заложенных в их исходном коде.

Смарт-контракты могут быть развёрнуты только внутри блокчейна. Ethereum поддерживает смарт-контракты, написанные на языке программирования Solidity.

Предварительные требования

В данном руководстве по работе со смарт-контрактами в блокчейне Ethereum используются JavaScript и Solidity, поэтому вам понадобятся базовые знания этих языков программирования, а также Node.js.

Solidity похож на JavaScript, поэтому описываемые концепции будут довольно просты для понимания.

Создание проекта

Мы будем использовать следующие инструменты разработки и тестирования смарт-контрактов:

  • Ganache
  • Truffle (устанавливается командой npm i truffle -g)
  • JavaScript для тестирования
  • Solidity для программирования смарт-контрактов

Инициализация Truffle

Truffle предоставляет все необходимые инструменты для разработки и тестирования смарт-контрактов. Вы можете инициализировать проект Truffle с помощью команды truffle init. Она создаст новый проект с тремя папками (/contracts, /migrations и /tests) и одним конфигурационным файлом – truffle-config.js.

$ truffle init

✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test

$ ~

Создание экземпляра блокчейна Ganache Ethereum

После загрузки и установки Ganache запустите экземпляр блокчейна.

Запущенный экземпляр блокчейна

Проверьте порт в конфигурации RPC-сервера (обычно 7545) и внесите при необходимости изменения в truffle-config.js.

module.exports = {
  networks: {
    development: {
     host: "127.0.0.1",     // Localhost (по-умолчанию: none)
     port: 7545,            // Стандартный порт Ethereum (по-умолчанию: none)
     network_id: "*"        // Маска для любой сети (по-умолчанию: none)
    }
  }
}

Теперь введите команду truffle migrate. Если она не выдаст никаких ошибок, значит Truffle готов к работе с созданным экземпляром блокчейна.

Создание смарт-контракта

Смарт-контракты хранятся в папке /contracts. Обратите внимание, что в ней уже есть контракт migrations.sol.

Создайте в этой папке новый файл с именем TruffleTutorial.sol.

pragma solidity >=0.4.22 <0.9.0;

contract TruffleTutorial {
  address public owner = msg.sender;
  string public message;

  // конструктор вызывается при развертывании контракта
  constructor() public {
    // установка начального значения сообщения
    message = "Hello World!";
  }

  modifier ownerOnly() {
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  // функция для установки значения сообщения, которую может вызывать только владелец смарт-контракта
  function setMessage(string memory _message) 
    public 
    ownerOnly 
    returns(string memory) 
  {
    // устанавливаемое значение не должно быть пустым
    require(bytes(_message).length > 0);

    // установка нового значения сообщения
    message = _message;
    return message;
  }
}

Вы можете посмотреть комментарии в исходном коде, чтобы понять, что делает каждая функция. Также объясним всю суть здесь.

Этот смарт-контракт хранит текстовое сообщение и позволяет менять его только владельцу контракта. Контракт также позволяет читать это сообщение всем участникам блокчейна. При развёртывании контракта в блокчейн запишется сообщение “Hello World!”.

* Вы можете быстро протестировать смарт-контракт в Remix IDE.

Развертывание смарт-контракта в локальной тестовой сети Ganache Ethereum

Теперь давайте развернем этот смарт-контракт в локальной тестовой сети экземпляра блокчейна Ganache.

Если вы раньше работали с базами данных, например, такими как MySQL или PostgreSQL, то, скорее всего, уже сталкивались с миграциями.

В папке migrations вы можете увидеть пример миграции, созданный при инициализации Truffle. Для нашего смарт-контракта потребуется создать отдельный файл миграции, назовём его 2_TruffleTutorial_migration.js.

// Находим смарт-контракт `TruffleTutorial.sol` в папке `/contracts`
const TruffleTutorial = artifacts.require("TruffleTutorial");

module.exports = function(deployer) {
  // Развёртываем смарт-контракт при помощи Truffle
  deployer.deploy(TruffleTutorial);
};

Вам может быть интересно, как работают функции artifacts и deployer. За них отвечает Truffle. При запуске команды truffle migrate программа просматривает папку /migrations и развёртывает смарт-контракты согласно файлам миграций.

После того как вы создадите файл миграции с приведённым выше кодом, введите команду truffle migrate --reset, она развернёт контракты в тестовой сети. Также будут сгенерированы файлы с ABI и байт-кодом, которые способна интерпретировать виртуальная машина Ethereum.

Взаимодействие с развёрнутым смарт-контрактом

Теперь, когда смарт-контракт находится в блокчейне, с ним можно взаимодействовать при помощи консоли Truffle.

* Более простой способ взаимодействия – графический интерфейс Remix IDE.

Введите команду truffle console, чтобы открыть консоль для взаимодействия со смарт-контрактом.

$ truffle console
truffle(development)> const truffleTutorial = await TruffleTutorial.deployed()
truffle(development)> const address = await truffleTutorial.address
truffle(development)> address
'0x46C00D73bF785000B3c3F93569E84415AB2381f2'

Попробуйте ввести все эти строки, чтобы увидеть адрес смарт-контракта. Если вам удастся это сделать, значит смарт-контракт успешно развёрнут, и с ним смогут взаимодействовать другие ваши проекты.

Теперь попробуйте получить сообщение, хранящееся в смарт-контракте.

truffle(development)> const message = await truffleTutorial.message()
truffle(development)> message
'Hello World!'

А теперь попробуйте сохранить сообщение в смарт-контракт.

truffle(development)> await truffleTutorial.setMessage('Hi there!')
truffle(development)> await truffleTutorial.message()
'Hi there!'

Несмотря на то, что мы проверяли работу смарт-контракта вручную, всё то же самое можно было бы сделать автоматически при помощи тестов.

Тестирование смарт-контракта

Тесты хранятся в папке /tests. Создайте в ней новый файл с именем TruffleTutorial.js.

const { assert } = require("chai")

const TruffleTutorial = artifacts.require("./TruffleTutorial.sol")

require("chai")
  .use(require("chai-as-promised"))
  .should()

contract('TruffleTutorial', ([contractOwner, secondAddress, thirdAddress]) => {
  let truffleTutorial

  // сохраняем развёрнутый смарт-контракт вместе с его методами в переменную
  // `truffleTutorial` до того, как будут запущены тесты
  before(async () => {
    truffleTutorial = await TruffleTutorial.deployed()
  })

  // проверяем успешность развёртывания
  describe('deployment', () => {
    // проверяем адрес смарт-контракта
    it('deploys successfully', async () => {
      const address = await truffleTutorial.address

      assert.notEqual(address, '')
      assert.notEqual(address, undefined)
      assert.notEqual(address, null)
      assert.notEqual(address, 0x0)
    })

    // проверяем начальное значение сообщения
    it('has a message', async () => {
      const message = await truffleTutorial.message()
      assert.equal(message, 'Hello World!')
    })
  })

  describe('message', () => {
    // проверяем возможность установки нового значения
    // сообщения владельцем смарт контракта
    it('contract owner sets a message', async () => {
      // установка нового значения сообщения
      await truffleTutorial.setMessage('Hi there!', { from: contractOwner }) 
      // `from` помогает вызвать метод смарт-контракта от имени заданного участника
      // (в данном случае владельца смарт-контракта)

      // проверка нового значения сообщения
      const message = await truffleTutorial.message()
      assert.equal(message, 'Hi there!')
    })

    // проверяем, что только владелец смарт-контракта может устанавливать
    // значение сообщения и никто другой
    it('address that is not the owner fails to set a message', async () => {
      await truffleTutorial.setMessage('Hi there!', { from: secondAddress })
        .should.be.rejected
      // сообщаем Chai, что тест считается пройденным успешно,
      // если не удалось вызвать setMessage

      await truffleTutorial.setMessage('Hi there!', { from: thirdAddress })
        .should.be.rejected
    })
  })
})

Здесь для тестирования используется фреймворк Chai. С его помощью производится сопоставление переменных с их эталонными значениями. Если обратить внимание на код теста, то можно обнаружить некоторые сходства с тем, что до этого делалось вручную при помощи консоли Truffle, за исключением того, что здесь используется фреймворк Chai.

* assert.equal принимает два аргумента, и если они совпадают, тест проходит успешно.

Теперь введите команду truffle test и убедитесь в том, что все тесты проходят успешно.

$ truffle test
Using network 'development'.

  Contract: TruffleTutorial
    deployment
      ✓ deploys successfully
      ✓ has a message (236ms)
    message
      ✓ contract owner sets a message (165ms)
      ✓ address that is not the owner fails to set a message (250ms)

  4 passing (735ms)

Бонус: Развёртывание смарт-контракта в основной сети Ethereum

* Обратите внимание, что развёртывание смарт-контрактов в основной сети Ethereum требует платы за газ (говоря простым языком комиссию). Кроме того, каждый раз, когда вы что-то записываете в блокчейн (например, вызываете функцию setMessage), с вас так же будет взята плата за газ (комиссия). Чтение данных при этом по-прежнему останется бесплатным.

Сначала установите расширение браузера Metamask. Затем перейдите в Remix IDE.

Внести изменение в автоматически сгенерированный проект – создайте в папке /contracts файл TruffleTutorial.sol и вставьте в него весь исходный код смарт-контракта.

На боковой панели редактора расположена вкладка SOLIDITY COMPILER, переключитесь на неё и скомпилируйте Solidity-код смарт-контракта. Компилятор создаст файлы с ABI и байт-кодом.

После компиляции переключитесь на вкладку DEPLOY & RUN TRANSACTIONS, расположенную так же на боковой панели редактора. Здесь можно попробовать развернуть смарт-контракт в тестовой сети Remix VM. На этой же вкладке вы можете вручную протестировать развёрнутый смарт-контракт, используя инструменты Remix IDE.

Теперь давайте развернём смарт-контракт в основной сети Ethereum. Перед этим убедитесь, что у вас есть Ethereum на балансе кошелька Metamask, чтобы оплатить комиссию (за газ).

Переключите окружение на вкладке DEPLOY & RUN TRANSACTIONS с Remix VM на Injected Provider, чтобы подключиться к кошельку Metamask, который в свою очередь должен быть подключен к основной сети Ethereum. После нажатия кнопки Deploy вам будет предложено подписать транзакцию в Metamask. Как только вы это сделаете, смарт-контракт будет развёрнут и готов к работе.

* После развёртывания смарт-контракта не забудьте запомнить, а лучше записать присвоенный ему адрес, чтобы взаимодействовать с ним через клиентские приложения.


Материал подготовлен при поддержке Atlantida Community:

Поддержать проект

Социальные сети проекта:

Подпишись, чтобы ничего не пропустить!