10 сентября Waves Platform выпустила новую версию узла, в первой реализации которого есть поддержка смарт-контрактов. В первой части мы сосредоточились на идее смарт-счетов Waves и на том, что отличает их от других существующих решений. В этой части мы сосредоточимся больше на языке смарт-контрактов и инструментах для разработчиков. Эта статья носит технический характер, поэтому, если вы чувствуете проблемы с пониманием, обязательно прочтите Часть 1. Особая благодарность инженеру-исследователю Waves Назиму Фаур за помощь в написании этих статей.
Самая важная часть предыдущей статьи:
По сути, смарт-учетная запись - это учетная запись с прикрепленным скриптом проверки транзакций. Другими словами, сценарий, который прикреплен к учетной записи, чтобы учетная запись могла проверять каждую транзакцию перед ее подтверждением.
Контракт Waves для Smart Account всегда должен возвращать True
или False
, как показано ниже.
О ГАЗе и сборах
Прежде чем перейти к более техническим вещам. Давайте поговорим о газе и сборах. Одна из основных задач, которую мы ставим перед собой, - избавиться от газа для простых операций. Это не значит, что нет никаких комиссий, потому что майнеры также должны быть заинтересованы в выполнении контрактов. Мы решили проблему газа с практической точки зрения. Мы провели тесты производительности и рассчитали скорость выполнения различных операций. Тесты были построены с использованием JMH, результаты доступны здесь. В результате нашего подхода нет газа для не-Turing Complete Smart Account, только фиксированная плата. Кроме того, тесты привели к некоторым ограничениям:
- Размер скрипта не может превышать 8 КБ и должен выполняться быстрее, чем 20 операций проверки подписи. Второе правило означает, что проверка для смарт-аккаунта будет не более чем в 20 раз медленнее, чем для обычного аккаунта.
- Смарт-аккаунты платят дополнительную комиссию 0,004 WAVES за каждую транзакцию. Минимальная комиссия за транзакцию в сети Waves составляет 0,001 WAVES для обычных аккаунтов и 0,005 для смарт-аккаунтов.
Язык RIDE
Скрипт (контракт) должен быть написан на нашем языке RIDE. RIDE - это не полный по Тьюрингу ленивый, строго типизированный, статически типизированный язык, основанный на выражениях. Эти функции делают его простым, выразительным и свободным от ошибок.
Scala (на нем написана нода Waves) вместе с RIDE под влиянием F #. В документации вы можете найти полное описание стандартной библиотеки (встроенных функций) и типов данных.
Одна из самых интересных функций - сопоставление с образцом, что позволяет очень удобно описывать разные условия для разных типов транзакций:
Если вам интересно, как работает RIDE, вы можете найти более подробную информацию в white paper. Приведены описания всех этапов: парсинг, компиляция, десериализация, расчет стоимости, оценка. Теперь хорошо понимать, что первые два этапа - вне цепочки, десериализация, расчет стоимости и оценка - в цепочке.
Начало работы со смарт-аккаунтами Waves
Приступим к самому интересному и увлекательному - напишем наш первый контракт на смарт-аккаунт. Мы реализуем очень простой пример с учетной записью с несколькими подписями 2 из 3. Мультиподпись - это требование, чтобы транзакции имели две или более подписей, прежде чем они могут быть выполнены.
Если вы начнете писать смарт-контракт волн, вам нужно будет перейти в нашу IDE. Щелкните New
→ Empty Contract
, чтобы создать новый файл.
В качестве первого шага давайте определим открытые ключи Алисы, Боба и Купера. Они контролируют счет, и только двое из них могут отправить транзакцию.
В документации есть функция sigVerify
, позволяющая проверить подпись:
sigVerify
принимает в качестве аргументов тело транзакции, подпись и открытый ключ. Есть объект tx
, в котором хранятся данные транзакции. Есть поле tx.bodyBytes
с байтами проверяющей транзакции. Также есть поле tx.proofs
, которое представляет собой массив с особенностями (до 8).
На втором этапе давайте проверим подписи в правильном порядке:
И последний шаг - проверяем, что у нас больше двух подписей:
Вот и все! Наш контракт с мультиподписью 2 из 3 на RIDE готов. Вы можете получить скомпилированную версию из IDE или развернуть ее прямо в консоли. Последняя версия консольных команд находится в документации, вот пример использования:
Давайте завершим наш пример с несколькими подписями. Полный список контрактов:
let alicePubKey = base58'B1Yz7fH1bJ2gVDjyJnuyKNTdMFARkKEpV' let bobPubKey = base58'7hghYeWtiekfebgAcuCg9ai2NXbRreNzc' let cooperPubKey = base58'BVqYXrapgJP9atQccdBPAgJPwHDKkh6A8' let aliceSigned = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey )) then 1 else 0 let bobSigned = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey )) then 1 else 0 let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey )) then 1 else 0 aliceSigned + bobSigned + cooperSigned >= 2
Примечание: оператора возврата нет. Последняя строка контракта - результат.
Для сравнения, одна из распространенных мультисигнатур на Ethereum выглядит так:
pragma solidity ^0.4.22; contract SimpleMultiSig { uint public nonce; // (only) mutable state uint public threshold; // immutable state mapping (address => bool) isOwner; // immutable state address[] public ownersArr; // immutable state // Note that owners_ must be strictly increasing, in order to prevent duplicates constructor(uint threshold_, address[] owners_) public { require(owners_.length <= 10 && threshold_ <= owners_.length && threshold_ >= 0); address lastAdd = address(0); for (uint i = 0; i < owners_.length; i++) { require(owners_[i] > lastAdd); isOwner[owners_[i]] = true; lastAdd = owners_[i]; } ownersArr = owners_; threshold = threshold_; } function execute(uint8[] sigV, bytes32[] sigR, bytes32[] sigS, address destination, uint value, bytes data) public { require(sigR.length == threshold); require(sigR.length == sigS.length && sigR.length == sigV.length); bytes32 txHash = keccak256(byte(0x19), byte(0), this, destination, value, data, nonce); address lastAdd = address(0); // cannot have address(0) as an owner for (uint i = 0; i < threshold; i++) { address recovered = ecrecover(txHash, sigV[i], sigR[i], sigS[i]); require(recovered > lastAdd && isOwner[recovered]); lastAdd = recovered; } // If we make it here all signatures are accounted for. // The address.call() syntax is no longer recommended, see: // https://github.com/ethereum/solidity/issues/2884 nonce = nonce + 1; bool success = false; assembly { success := call(gas, destination, value, add(data, 0x20), mload(data), 0, 0) } require(success); } function () payable public {} }
Важность транзакций с данными в смарт-аккаунтах
Транзакции с данными предназначены для хранения произвольных данных в виде пар ключ-значение. Они также связаны с аккаунтом (адресом). Каждое значение имеет связанный с ним тип данных. Поддерживаются четыре типа данных: логический, целочисленный, строковый и байтовый массив. Можно получить данные из транзакций данных в вашем контракте.
Этот небольшой пример показывает, как читать данные из транзакций данных и проверять внешние транзакции в зависимости от значения. Это очень полезно для оракулов и реальных приложений для обработки данных. У этой функции огромный потенциал, но это тема для отдельной статьи.
Дальнейшее обучение
Документация
- Полную информацию о смарт-счетах Waves можно найти здесь.
- Язык RIDE поддерживает разные типы данных, и все они описаны здесь.
- Есть много встроенных функций, таких как addressFromPublicKey и sigVerify, вы можете найти полный список встроенных функций RIDE здесь.
Учебники
- Мультиподпись с развертыванием и пением разными аккаунтами (с использованием библиотеки JS)
- Вариант использования условного депонирования (с использованием библиотеки Java).
Куда обратиться, если вам понадобится наша помощь?
Сообщество Waves всегда готово помочь вам и ответить на все ваши вопросы в кратчайшие сроки, поэтому не стесняйтесь писать нам сюда:
- Раздор
- "Форум"
- Telegram канал
- Github
Что дальше?
Дальнейшее развитие смарт-контрактов Waves - это полный по Тьюрингу язык (или система), который позволит решать все типы задач. Еще одна интересная идея, связанная со смарт-контрактами в экосистеме Waves, - это Smart Assets. Они работают аналогичным образом - полные контракты не по Тьюрингу, которые могут быть прикреплены к токену. Например, с помощью Smart Assets можно будет удерживать токены до определенной высоты блокчейна или запрещать сделки P2P. Подробнее о смарт-активах - в этой статье.
В последний выпуск смарт-ассеты не были включены, но они уже реализованы в кодовой базе Waves. Если у вас есть идеи или варианты использования, пишите их в комментариях, мы будем рады обсудить.