Тестирование, компиляция и развертывание контрактов Ink и способ вызова функций с помощью Polkadot JS

Разработка платформы блокчейна Substrate от Parity идет полным ходом, и теперь доступна возможность развертывания смарт-контрактов на основе Ink, хотя и на ранней стадии. В этой статье мы рассмотрим процесс компиляции и развертывания смарт-контракта Ink, прежде чем изучать средства вызова функций контракта через клиент Polkadot JS.

Это последняя часть из трех частей, посвященных процессу создания смарт-контрактов Ink в сочетании с блокчейнами Substrate от установки до развертывания. Предыдущую статью, посвященную процессу написания контракта Ink, можно найти здесь:



Смарт-контракт невзаимозаменяемого токена, с которым мы работаем, полностью доступен здесь на Github.

Тестирование контрактов на чернила

Тестирование смарт-контракта Ink может (и должно) проводиться как вне сети, так и в сети. Предыдущее может быть выполнено с помощью модуля tests в самом контракте, а последнее - в локальной цепочке разработчиков Substrate.

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

В первую очередь вы можете проверить контракт Ink с помощью модуля tests под вашим макросом contract!. Шаблон выглядит следующим образом:

// test function boilerplate
#[cfg(all(test, feature = "test-env"))]
mod tests {
   
   use super::*;
   use std::convert::TryFrom;
   #[test]
   fn it_works() {
      // test function...
   }
}

Тестовые функции заключены в отдельный tests модуль, который импортирует все из родительского модуля и, таким образом, знает все о рассматриваемом смарт-контракте. Давайте разберем некоторые из наиболее неоднозначных строк кода, начав с верхнего флага cfg.

// config flag to only compile in a test environment
#[cfg(all(test, feature = "test-env"))]

Тесты не скомпилированы со смарт-контрактом - они займут ненужное место в цепочке. Мы также включили в модуль tests два use оператора:

// use everything from super module (the smart contract)
use super::*;
// use the TryFrom trait - allowing safe type conversions
use std::convert::TryFrom;

Первая строка, super::*, не требует пояснений; модулю tests необходимо знать смарт-контракт, который он тестирует, поэтому все закупается в рамках * из родительского модуля - самого смарт-контракта. Второй аргумент вводит черту TryFrom в область видимости.

Типаж TryFrom реализует простое и безопасное преобразование типов, которое при некоторых обстоятельствах может завершиться сбоем.

Мы используем метод try_from(), производный от признака TryFrom, чтобы попытаться получить AccountId адресов для использования в нашем тестировании. Фактически, это первое, что мы делаем в it_works() тесте после инициализации экземпляра контракта:

#[test]
fn it_works() {
   // initialise a contract instance to test
   let mut _nftoken = NFToken::deploy_mock(100);
   // try to obtain alice's account
   let alice = AccountId::try_from([0x0; 32]).unwrap();
   ...
}

AccountId в Substrate состоит из 32 символов, поэтому адрес Алисы просто объявлен как 32 нуля. Учетная запись разворачивается для получения фактического адреса из перечисления Result или Error.

Примечание. Мы рассмотрели концепцию разворачивания Rust в предыдущей статье.

Оператор #[test] существует до определения функции; это синтаксис Rust, который сообщает компилятору, что мы намерены использовать эту функцию как тестовую. VS Code будет встраивать кнопку тестирования под каждую функцию, помеченную таким образом как тест, но нажатие этой кнопки для вызова cargo test завершится ошибкой, так как нам нужна слегка измененная тестовая команда для проверки контрактов Ink. Мы посетим эту команду ниже.

В it_works() мы инициализируем изменяемый экземпляр контракта, используя deploy_mock(), фиктивную функцию развертывания, предоставляемую фреймворком Ink. Теперь контракт можно вызывать и управлять им с помощью переменной _nftoken. deploy_mock() вызовет deploy() метод контракта, который ожидает init_value аргумент, поэтому было предоставлено значение 100, что приводит к созданию 100 токенов во время выполнения теста.

Использование утверждений в тестах

Отсюда остальная часть it_works() проста. Мы воспользовались макросами утверждений Rust, чтобы гарантировать, что состояние нашего контракта меняется так, как мы ожидаем, при передаче токенов и утверждении других учетных записей для отправки токенов.

Rust включает три макроса утверждения, доступные для использования в стандартной библиотеке:

// assert! - true or false
assert!(some_expression());
// assert_eq! - asserts that 2 expressions are equal
assert_eq!(a, b, "Testing if {} and {} match", a, b);
// assert_ne! - asserts that 2 expressions are not equal
assert_ne!(a, b, "Testing that a and b are not equal");

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

cargo test --features test-env -- --nocapture

Использование --no-capture обеспечит более подробный вывод, включая вывод println(), где он использовался в модуле tests. test-env функция гарантирует, что мы тестируем только среду Ink, как определено в Cargo.toml:

[features]
default = []
test-env = [
    "ink_core/test-env",
    "ink_model/test-env",
    "ink_lang/test-env",
]
...

Успешный тест приведет к следующему результату:

Составление договора

После прохождения тестов мы можем скомпилировать контракт. Для этого запустите build.sh в каталоге проекта:

./build.sh

Полученные файлы будут находиться в вашей target/ папке:

nftoken.wat
nftoken-fixed.wat
nftoken.wasm
nftoken-opt.wasm
nftoken-pruned.wasm
NFToken.json

Контракты Ink компилируются в двоичный веб-стандарт WebAssembly, или .wasm. Нас интересуют два файла из приведенного выше скомпилированного вывода, которые мы загрузим в Substrate:

  • nftoken-pruned.wasm: оптимизированный .wasm файл, который мы загрузим в нашу цепочку субстратов.
  • NFToken.json: ABI-код контракта в формате JSON

Примечание. Хотя мы не ориентированы на WebAssembly, стоит упомянуть, что этот формат активно используется в пространстве цепочки блоков для более эффективного выполнения. Ethereum 2.0 будет полагаться на подмножество WebAssembly, которое они назвали eWasm, и, конечно же, цепочки субстратов также принимают стандарт. Хотя WebAssembly в первую очередь предназначен для Интернета, он никоим образом не ограничивается браузером. Спецификация WebAssembly находится в стадии разработки, и мы можем ожидать, что в ближайшие годы будет выпущено больше функций, что сделает эту технологию очень интересной для работы.

Возможно, вы знакомы с контрактным ABI из контрактов на основе Ethereum, которые предоставляют интерфейсным Dapps средства для связи с контрактом в цепочке. По сути, они описывают структуру контракта, включая его функции и переменные в объекте JSON, что делает интеграцию приложений на основе Javascript особенно простой.

Теперь, чтобы развернуть контракт, разверните свою локальную цепочку субстратов, если вы еще этого не сделали, и давайте обратимся к приложению Polkadot JS для управления нашим развертыванием.

# run your local Substrate chain
substrate --dev

Развертывание контрактов на чернила

Развертывание и создание экземпляра контракта в цепочке Substrate включает в себя сначала развертывание контракта, а затем его создание. Этот двухэтапный процесс позволяет разработчикам развернуть определенный стандарт - возможно, стандарт токенов - где другие заинтересованные стороны могут затем создать экземпляр того же контракта со своими собственными данными токена. Это устраняет необходимость загружать дубликаты одного и того же контракта для по существу идентичной функциональности и идентичного кода ABI.

Повторяю, этот двухэтапный процесс включает в себя:

  1. Загрузка контракта в цепочку субстратов
  2. Создание экземпляра контракта, с которым затем можно взаимодействовать с

Обе эти задачи могут быть выполнены с помощью приложения Polkadot JS, проекта на основе TypeScript и React с открытым исходным кодом, доступного на Github.

Polkadot JS

Теперь мы загрузим наши скомпилированные .wasm и .json ABI в цепочку разработчиков Substrate. Для этого необходим клиент Polkadot JS.

Вы можете либо клонировать проект для запуска на вашем локальном компьютере, либо просто посетить https://polkadot.js.org/apps, чтобы получить к нему доступ в Интернете. Загрузите клиент, чтобы начать последующий процесс развертывания.

Шаг 1. Убедитесь, что клиент подключен к вашему локальному узлу Substrate

Во-первых, нам нужно убедиться, что клиент подключен к правильной цепочке. Перейдите на вкладку Settings и убедитесь, что для remote node/endpoint to connect to установлено значение Local Node (127.0.0.1:9944). Нажмите Save and Reload, если необходимо изменение.

Примечание. Другие цепочки, Alexander и Emberic Elm, представляют собой цепочки на основе субстрата, которые управляются Parity. Работа с другими цепочками субстратов, такими как Polkadot, выходит за рамки этой статьи, однако клиент Pokakdot JS на самом деле разработан для работы с любым блокчейном на основе субстрата и поэтому чрезвычайно динамичен в том, что представлено во всем приложении.

Шаг 2. Разверните скомпилированный контракт на свой узел

Чтобы развернуть наш контракт, перейдите на страницу Contracts из боковой панели и убедитесь, что вы находитесь на вкладке Code. Если вы еще не развернули контракт на своем узле, вкладка Code будет единственной доступной. Пользовательский интерфейс будет похож на следующий:

Теперь на вкладке Код:

  • Убедитесь, что deployment account установлен на ALICE. У Алисы будет достаточный баланс для развертывания, создания и тестирования контракта.
  • Перетащите nftoken-pruned.wasm в поле compiled contract WASM
  • Необязательно: измените значение code bundle name на более понятное для человека имя
  • Перетащите NFToken.json в поле contract ABI
  • Установите maximum gas allowed на 500 000, чтобы гарантировать, что мы предоставим достаточно газа для обработки транзакции.

После настройки нажмите Deploy, а затем подтвердите еще раз. Сделки состоятся, и контракт будет развернут.

Шаг 3. Создание контракта

Теперь вы заметите, что доступны две дополнительные вкладки: Instance и Call. Сначала мы будем использовать вкладку Instance для создания экземпляра контракта, а затем вкладку Call для тестирования наших функций. Вкладка Instance будет выглядеть примерно так:

На вкладке "Экземпляр":

  • Убедитесь, что значение code for this contract указывает на ваш развернутый контракт
  • Установите начальное количество токенов для чеканки как значение initValue.

Примечание. Пользовательский интерфейс Polkadot теперь учитывает нашу структуру контракта и, в частности, аргументы, которые необходимо предоставить для функции развертывания, которую мы определили для контракта, включая ожидаемый тип данных. Это один из примеров динамического характера пользовательского интерфейса Polkadot и того, как он разработан для обслуживания широкого спектра типов контрактов.

  • Установите для endowment значение 1000, чтобы гарантировать, что новая учетная запись контракта будет отчеканена с некоторым значением. Это рекомендованное значение из официальных документов Ink. Как и контракты Ethereum, контракты Ink размещаются на отдельном адресе с их собственным уникальным AccountId и балансом.
  • Опять же, установите maximum gas allowed на 500 000, чтобы гарантировать, что мы предоставим достаточно газа для транзакции.
  • Нажмите Initiate и подтвердите выполнение транзакции.

После успешной транзакции будет создан экземпляр контракта, и функции будут доступны для вызова.

Шаг 4. Вызов функций из созданного контракта

Наша последняя задача - убедиться, что функции работают должным образом. Вы заметите, что все pub(external) функции, которые мы определили в контракте, теперь доступны для вызова и тестирования на вкладке Call:

Polkadot JS еще не обеспечивает обратную связь от вызовов в самом клиенте, но ваш фид узлов в вашем терминале должен отражать транзакции по мере проверки новых блоков. То, что у нас есть с точки зрения UX, - это уведомления об успехах или сбоях, которые появляются в правом верхнем углу окна вашего браузера при обработке вызова функции.

Примечание. Механизмы обратной связи будут публиковаться здесь по мере их появления в командной строке или в клиенте Polkadot JS.

Резюме

Мы завершили процесс развертывания смарт-контрактов Ink, от установки до создания в цепочке. Вкратце, давайте рассмотрим различные элементы, которые сделали этот процесс возможным:

  • Установка базового языка Rust, менеджера пакетов Cargo и фреймворка Substrate
  • Установка Ink, доступная через собственный ящик Cargo, вместе с инструментами WebAssembly, необходимыми для компиляции Ink в .wasm
  • Начальная загрузка базового Flipper контракта Ink для получения шаблона Ink, включая конфигурацию среды и build.sh файл
  • Написание контракта NFToken в соответствии с концепциями и соглашениями Rust, с функциями чеканки, передачи и утверждения, а также с отправкой событий
  • Тестирование с помощью модуля tests перед составлением контракта с build.sh
  • Развертывание, создание экземпляров и тестирование вызовов функций через клиент Polkadot JS, подключенный к вашей локальной цепочке разработчиков Substrate.

Мои мысли

Я лично думаю, что за проектами Substrate и Polkadot будет очень интересно следить до их финального релиза; они пытаются решить самые острые проблемы в пространстве блокчейнов, внедряя новые и появляющиеся технологии, такие как Rust, WebAssembly, и протоколы, которые делают возможной блокчейн Substrate.

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

Я надеюсь, что эта серия пролила некоторый свет на экосистему инструментов, связанных с разработкой смарт-контрактов для Substrate.