Реализация невзаимозаменяемого токена, написанного с помощью Ink
ink !: язык смарт-контрактов Substrate
Блокчейн-фреймворк Parity Substrate, на котором построен Polkadot, находится в активной разработке и быстро продвигается к финальной версии. Ink (или ink!, как это название обычно называют в документации) - это решение Parity для написания смарт-контрактов для блокчейна на основе Substrate.
Как и Substrate, Ink построен на основе Rust и, следовательно, соответствует правилам и синтаксису языка Rust. В этом выступлении будет рассмотрен пример смарт-контракта, воспроизводящего невзаимозаменяемый токен, обычно называемый токенами ERC721 в блокчейне Ethereum. Сам контракт можно найти на Github здесь.
Это первая часть серии, которая будет охватывать процесс создания и развертывания этого смарт-контракта Ink, в частности, мы рассмотрим:
- Как установить субстрат и чернила с их зависимостями
- Написание незаменимого контракта с невзаимозаменяемыми токенами, который будет поддерживать 3 основные функции: чеканка токенов, передача токенов и утверждение другой учетной записи для отправки токенов от вашего имени.
- Как создать и развернуть смарт-контракт на блокчейне Substrate, а также протестировать функции в цепочке с помощью приложения Polkadot JS
Примечание. Приложение Polkadot JS было разработано для управления не только самим Polkadot, но и любой цепочкой субстратов. Интерфейс является динамическим в том смысле, что доступные параметры управления зависят от который поддерживает цепочку подложек.
В этой статье мы рассмотрим процесс установки Ink и его необходимые зависимости, а также представим пример контракта с невзаимозаменяемым токеном, сопровождающий эту серию, поговорим о структуре контракта Ink и его отличиях от контракта на основе Solidity. как сходство между ними.
Примечание о невзаимозаменяемых токенах
Невзаимозаменяемые токены, или NFT, отличаются от токенов в стиле ERC20, при этом каждый токен уникален. В нашем контракте каждый токен будет иметь уникальный идентификатор, используемый для представления токена. Отсюда каждый токен может иметь собственное значение, собственные метаданные или иметь конкретное использование в приложении или системе ценностей.
Утверждение учетных записей, не являющихся владельцами, для передачи или управления токеном также отличается и должно выполняться для каждого токена с использованием невзаимозаменяемых токенов. Cryptokitties - самый известный пример реализации невзаимозаменяемых токенов - каждый токен представляет котенка на платформе.
NFT представляют различные проблемы для стандартного токена и, следовательно, дают нам больше возможностей для изучения синтаксиса и возможностей Ink. Теперь давайте установим Substrate, Ink и необходимые пакеты для написания нашего смарт-контракта.
Установка
Чтобы начать писать контракты Ink, необходимо несколько пакетов. После их установки мы можем запустить цепочку субстратов в окне терминала - мы будем использовать эту цепочку как средство развертывания нашего смарт-контракта.
Ржавчина
Субстрат и Чернила основаны на Ржавчине. Установите Rust с помощью следующих команд или перейдите на страницу установки. Две другие команды включены ниже, чтобы убедиться, что ваша nightly
версия Rust (последняя доступная версия Rust) актуальна, а также поддержка сборки WebAssembly для nightly
Rust: контракты Ink компилируются в файлы .wasm
и, следовательно, развертываются в цепочки субстратов как WebAssembly .
# install rust and update environment curl https://sh.rustup.rs -sSf | sh source ~/.cargo/env # run rust updates and add WebAssembly target support rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly
Примечание. Cargo - это менеджер пакетов Rust, пакеты которого называются ящиками. Общедоступный реестр грузов можно просмотреть по адресу https://crates.io.
Примечание 2: в последней команде цель относится к целевой папке в каталоге вашего проекта Rust, которая содержит сборки ваших программ. Здесь будут создаваться файлы наших смарт-контрактов.
Субстрат
Установите Substrate с помощью следующей команды:
curl https://getsubstrate.io -sSf | bash
Утилиты wasm
Смарт-контракты Ink компилируются в WebAssembly перед загрузкой в цепочку Substrate. Для этого нам потребуется установить некоторые утилиты:
# We will install:
#
# Binaryen
# Compiler infrastructure and toolchain library for WebAssembly
#
# Wabt
# The WebAssembly Binary Toolkit
#
# Parity Wasm Utils
# Parity specific WebAssemply utilities */
# MacOS
brew install binaryen
brew install wabt
cargo install pwasm-utils-cli --bin wasm-prune
# Ubuntu
apt install binaryen
apt install wabt
cargo install pwasm-utils-cli --bin wasm-prune
Чернила
Отсюда мы можем установить Ink через ящик. Для этого выполните следующее:
cargo install --force --git https://github.com/paritytech/ink cargo-contract
Запуск локальной цепочки субстратов
Наш смарт-контракт будет протестирован на локальной цепочке блоков Substrate, запущенной на вашем компьютере. После установки Substrate выполните следующее для инициализации цепочки:
substrate --dev
Флаг —-dev
инициализирует для вас блокчейн разработки, локальный только для вашей машины, и включает некоторые учетные записи пользователей с самого начала.
Эта установка предназначена исключительно для целей тестирования; вы можете думать об этом как об эквиваленте программы Truffle’s Ganache, которая запускает локальную цепочку на основе Ethereum, настроенную для целей тестирования.
Теперь вы заметите, что узел работает с проверяемыми новыми блоками. Нажатие CTRL+C
или закрытие окна терминала остановит выполнение узла, хотя состояние цепочки сохраняется до следующего запуска цепочки. В этой заметке, если вы хотите запустить новую цепочку, выполните следующую команду, чтобы очистить цепочку:
substrate purge-chain --dev
Когда наша цепочка запущена и чернила готовы к использованию, мы можем приступить к кодированию контракта. Прежде чем сделать это, также стоит упомянуть о поддержке редактора. Я лично считаю Visual Studio Code лучшим редактором для работы не только на Ink, но и на Rust в целом. Чтобы быстро настроить:
- Установите VS Code отсюда, если у вас еще не установлен редактор
- Установите расширение RLS (Rust Language Support) и расширения WebAssembly. Это можно сделать прямо в редакторе на вкладке Расширения или нажать
Shift+CMD+X
, чтобы перейти прямо к нему.
Настройка Ink Project
Установив все зависимости, приступим к настройке проекта.
Самый простой способ сделать это в настоящее время - установить контракт Ink Hello World под названием Flipper. Установив Flipper, мы можем опираться на то, что уже включено, и не беспокоиться о настройке и компиляции скриптов - они предусмотрены в Flipper.
Примечание. Как субстрат, так и чернила находятся в быстрой разработке и еще не завершены, поэтому среда смарт-контракта и сам код смарт-контракта, скорее всего, изменятся по мере приближения Parity к финальный выпуск фреймворка.
Чтобы быстро запустить наш проект Ink, загрузите Flipper с помощью Cargo:
# fetch the Flipper Ink contract cargo contract new flipper
Flipper
предоставляет нам шаблон проекта, необходимый для начала написания смарт-контракта. Включено:
- Структура папок и метаданные конфигурации проекта
- Простой контракт Flipper в
src/lib.rs
, который просто «переворачивает» логическое значение междуtrue
иfalse
с помощью методаflip()
и получает это значение в цепочке с помощью методаget()
. Мы заменим этот файл контрактом NFT. - Специальный
Cargo.toml
файл Rust, описывающий зависимости проекта и метаданные модуля,.gitignore
файл иbuild.sh
файл. Файлbuild.sh
- это то, что мы запускаем для компиляции нашего смарт-контракта, в результате чего получается скомпилированный файл.wasm
контракта, абстракция контракта JSON и многое другое. Мы рассмотрим построенный контракт ниже.
Примечание. Сейчас самое время проверить src/lib.rs
, чтобы почувствовать синтаксис контракта.
Давайте изменим имя flipper
на более подходящее: nftoken
. Измените следующее:
/flipper
имя папки на/nftoken
Cargo.toml
: изменить[package] name
и[lib] name
наnftoken
build.sh
: изменитьPROJNAME=nftoken
Также убедитесь, что у нас есть разрешения на запуск nftoken/build.sh
:
cd nftoken chmod +x build.sh
Наконец, добавьте папку /nftoken
в рабочую область VS Code, и мы готовы начать писать.
О чернилах
Ink имеет несколько уровней абстракции, где более высокие уровни абстрагируются над более низкими уровнями. Мы будем использовать самый высокий уровень, который называется уровнем языка, или lang
уровнем. Эти уровни также были разделены на модули, которые можно изучить здесь.
Под модулем lang
находятся модули model
и core
, которые ориентированы на абстракции среднего уровня и основные утилиты соответственно. Под модулем core
мы также можем ожидать интерфейс командной строки, специально предназначенный для создания контрактов Ink и управления ими.
Хотя на момент написания статьи о том, как использовать эти модули, мало что известно, у нас действительно есть необработанные документы API для просмотра, как для модуля core
, так и для модуля model
. Если вы читаете эту статью, эти документы можно просмотреть сейчас, хотя в нашем контракте ниже будут использоваться некоторые из этих API, предназначенных для демонстрации того, как они используются в контексте уровня lang
через контракт невзаимозаменяемых токенов.
Имея это в виду, давайте теперь посмотрим, как выглядит структура нашего lang
производного контракта, и сравним ее с тем, что мы ожидаем от смарт-контракта на основе Solidity.
Напоминание: заполненный контракт можно найти здесь на Github.
Структура контракта
Структурирование контракта Ink аналогично контракту Solidity, где основные компоненты, которые мы ожидаем от Solidity, также согласованы в Ink: переменные контракта, события, публичные функции и частные функции, а также переменные среды для захвата вызывающего. адрес и многое другое.
Ниже представлена абстракция структуры NFToken
контракта:
// declare modules use parity::<module> ... //wrap entire contract inside the contract! macro contract! { // contract variables as a struct struct NFToken { owner: storage::Value<AccountId>, ... } // compulsory deploy method that is run upon the initial contract instantiation impl Deploy for NFToken { fn deploy(&mut self, init_value: u64){} } // define events event EventMint { owner: AccountId, value: u64 } ... // public contract methods in an impl{} block impl NFToken { pub(external) fn total_minted(&self) -> u64 {} ... } // private contract methods in a separate impl{} block imp NFToken { fn is_token_owner( &self, of: &AccountId, token_id: u64) -> bool {} ... } } // test functions mod tests { fn it_works() {} ... }
Давайте кратко рассмотрим эти разделы и узнаем, чем они отличаются от того, что мы ожидаем от контракта на Solidity. Ink построен на Rust, поэтому весь синтаксис здесь является допустимым синтаксисом Rust.
- В разделе объявления модуля мы привносим в контракт внешние функции, и он аналогичен по своему характеру объявлениям
using
в Solidity.
// Ink use ink_core::{ env::{self, AccountId}, storage, }; use ink_lang::contract; // Solidity interface ContractName { using SafeMath for uint256; using AddressUtils for address; }
- События объявляются внутри макроса
!contract
, тогда как с Solidity мы определяем наши события в интерфейсе контракта, вводя каждое какevent
:
// Ink event Transfer { from: AccountId, to: AccountId, token_id: u64 } // Solidity event Transfer( address indexed from, address indexed to, uint256 indexed _tokenId );
- Если контракт Solidity встроен в блок
interface
, контракт Ink встроен в макросcontract!
. Наши события объявляются внутри этого макроса, тогда как события объявляются в интерфейсе Solidity. Это описано ниже.
Примечание. Макрос в Rust - это объявление, представляющее блок синтаксиса, которым будут окружены обернутые выражения. Макросы абстрактны на синтаксическом уровне, поэтому макрос contract!
добавляет в свое содержимое дополнительный синтаксис.
// Ink contract! { // contract } // Solidity interface ContractName { // contract }
- В Ink переменные контракта записываются в структуру имени контракта. Карты хеширования, производные от типа
HashMap
в Rust, заменяют типmapping
в Solidity, обеспечиваяkey => value
списков.
Как Substrate хранит ценности
Любая часть данных, хранящаяся в цепочке субстратов, называется внешними, и Ink предоставляет нам средства для хранения внешних данных в цепочке через модуль storage
, который находится в модуле core
языка. Другими словами, все переменные контракта, которые вы планируете сохранить в цепочке, будут использовать тип из storage
. И наоборот, модуль memory
также доступен для структур данных для работы с памятью.
Solidity, с другой стороны, использует другой подход к этому. Начиная с версии Solidity 0.5 ссылочные типы storage
и memory
были введены в аргументы функции или переменные функции, поэтому контракт знает, где ссылаться на эти переменные. Однако это не обязательно для переменных контракта в Solidity.
Примитивные типы также доступны и согласованы на обоих языках; где Rust использует u64
, Solidity принимает более подробное объявление типа uint64
. В целом довольно просто добиться одинаковой структуры переменных контракта между двумя языками.
// Ink struct NFToken { owner: storage::Value<AccountId>, approvals: storage::HashMap<u64, AccountId>, } // Solidity address owner; mapping (uint64 => address) approvals;
В приведенном выше примере тип значений, обрабатываемых storage
объектами, передается через угловые скобки вместо общих типов.
- Концепция функции инициализации присутствует как в Ink, так и в Solidity, хотя и реализована по-разному. Используя Ink, мы явно определяем метод
deploy()
в блоке реализацииDeploy{}
. Параметры, которые мы определяем для этого метода, представляют, какие данные мы отправляем при инициализации контракта. Например. для нашего невзаимозаменяемого токена мы предоставим начальное количество токенов для чеканки:
// Inks initialisation function, deploy() impl Deploy for NFToken { fn deploy(&mut self, init_value: u64) { ... } }
- Общедоступные и частные методы также определены в блоках
impl
, где открытые методы явно определены с помощьюpub(external)
. Опять же, при сравнении этого синтаксиса с синтаксисом Solidity,internal
иexternal
используются для определения частного или общедоступного ресурса.
Примечание. В Rust функции, модули, трейты и структуры по умолчанию являются закрытыми и должны быть определены с помощью ключевого слова pub
, чтобы они были доступны извне. Расширение (external)
для pub
зависит от рукописного ввода и обязательно для включения в общедоступные функции рукописного ввода.
// public functions impl NFToken { pub(external) fn total_minted(&self) -> u64 {} } // private functions impl NFToken { fn mint_impl( &mut self, receiver: AccountId, value: u64) -> bool { } }
Опять же, мы разделили наши частные и общедоступные функции на отдельные impl
блоки и включили pub(external)
для определенных общедоступных функций.
- В качестве последнего строительного блока нашего контракта определен модуль
tests
, который устанавливает различные условия при тестировании наших функций. В модулеtests
мы можем протестировать логику нашего контракта без необходимости компилировать и развертывать ее в цепочке субстратов, что позволяет быстро устранять ошибки и проверять, работает ли контракт должным образом.
Следующий
Теперь мы обсудили строительные блоки смарт-контракта Ink. В следующей части этой серии мы более подробно рассмотрим функции контракта невзаимозаменяемых токенов, разберемся с используемыми шаблонами проектирования Rust и как они связаны с логикой нашего контракта. Затем мы рассмотрим процесс создания и развертывания смарт-контракта в локальной цепочке субстратов и протестируем его функции с помощью Polkadot JS.
Читайте следующую часть этой серии здесь: