Перейти к основному содержимому
Блокчейн68ГЛАВА 68Язык RIDE: выражение, которое отвечает да или нет{-# STDLIB_VERSION 6 #-}{-# CONTENT_TYPE DAPP #-}@Callable(i)func zavesti(id)strict ball = …let3strict184цена известна заранее

Язык RIDE: выражение, которое отвечает да или нет

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Solidity похож на JavaScript: циклы, классы, изменяемые переменные, всё привычное. RIDE — не похож ни на что из того, что было раньше в треке, и это сделано нарочно.

В RIDE нет циклов. Нет своих типов. Нет изменяемых переменных. Нет обращений наружу. Убрано ровно то, из-за чего стоимость выполнения программы нельзя предсказать заранее. Взамен появляется свойство, которого нет ни в Solidity, ни в чейнкоде Fabric: цену любого скрипта считает компилятор, до всякого запуска.

Эта глава — про язык как таковой: из чего состоит скрипт, чем let отличается от strict и как читать сообщения компилятора. Всё ниже — вывод настоящего компилятора RIDE версии 2.3.0.

Три директивы​

Любой скрипт начинается с трёх строк:

{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}
ТРИ СТРОКИ В ШАПКЕ РЕШАЮТ, ЧТО ЭТО ВООБЩЕ ЗА СКРИПТ{-# STDLIB_VERSION 6 #-}какие функции доступныв версии 3 тот же sigVerify стоит 102, в шестой — 182{-# CONTENT_TYPE DAPP #-}выражение или приложениеEXPRESSION возвращает да/нет, DAPP содержит вызываемые функции{-# SCRIPT_TYPE ACCOUNT #-}на что вешаютACCOUNT на аккаунт, ASSET на токендирективы обязательны: без них компилятор не знает, по каким правилам читать текстих пишут первыми тремя строками и почти никогда потом не трогают
Три строки, которые решают, что это вообще за скрипт
Нажми на часть выражения

Самый простой скрипт, который что-то делает:

{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}
sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
сложность: 182 | размер base64: 88

Он воспроизводит поведение обычного аккаунта: «пропускай, если подпись верна». Скрипт-выражение обязан вернуть Boolean — если вернуть что-то другое, компилятор скажет прямо:

Script should return boolean

Значения вместо переменных​

let a = 2
let b = 3
a + b == 5
сложность: 2 | размер: 44

Слово let объявляет значение, а не переменную. Поменять его нельзя:

let a = 1
let a = 2
Compilation failed: [Value 'a' already defined in the scope in 85-111]

Строки складываются тем же плюсом:

let s = "IS" + "-21"
s == "IS-21"

Деление — целочисленное:

(7 / 2) == 3
сложность: 2

А дробного числа просто нет:

let x = 1.5
Parse error: illegal expression in 95-96
Важно

Компилятор не понял 1.5 не как «плохое число», а как непонятное выражение: точки между цифрами в языке не бывает вовсе. Отсюда правило, знакомое по предыдущим главам: деньги считают целыми, а запятую ставят при показе.

Отвечено вопросов: 0 из 3
Что задаёт директива STDLIB_VERSION?
Что должен вернуть скрипт с CONTENT_TYPE EXPRESSION?
Почему let a = 1.5 не компилируется?
Отвечено верно: 0 из 3

let ленив, strict — нет​

Это место стоит запомнить особо: одно слово меняет цену скрипта в шестьдесят раз.

@Callable(i)
func f() = {
let dorogo = sigVerify(base58'', base58'', base58'')
[ IntegerEntry("x", 1) ]
}
сложность: 3
@Callable(i)
func f() = {
strict dorogo = sigVerify(base58'', base58'', base58'')
[ IntegerEntry("x", 1) ]
}
сложность: 184
ОДИН И ТОТ ЖЕ КОД, РАЗНЫЕ СЛОВА — РАЗНАЯ ЦЕНАlet — ленивыйlet dorogo = sigVerify(…)[ IntegerEntry("x", 1) ]сложность 3strict — считается сразуstrict dorogo = sigVerify(…)[ IntegerEntry("x", 1) ]сложность 184значение под let вычисляется только если его кто-то спросил — не спросили, не заплатилиstrict нужен там, где важен сам факт вычисления: проверка, которая должна упастьпроверку под let легко «потерять»: она молча не выполнится, и никто этого не заметит
Один и тот же код: 3 против 184

Значение под let вычисляется только если его кто-то спросил. В обоих примерах dorogo дальше не используется — и в первом случае за него не платят вовсе.

Важно

Отсюда ловушка, на которой теряют деньги. Проверку под let легко «потерять»:

let proverka = if (ball < 2 || ball > 5) then throw("plohoy ball") else true
[ IntegerEntry(k, ball) ]

Проверка не выполнится: её никто не спросил. Всё, что должно упасть, объявляют через strict — или ставят прямо в выражение результата.

if только с else​

let a = 1
if (a == 1) then true
Compilation failed: [expected a false branch in 116-116]

if в RIDE — не оператор ветвления, а выражение, которое обязано дать значение при любом исходе. Ветка else обязательна всегда.

if (1 == 1) then true else false
сложность: 1

match: разбор по типу​

match tx {
case t: TransferTransaction => t.amount > 0
case d: DataTransaction => true
case _ => false
}
сложность: 2 | размер: 172
Нажми на часть выражения

Уберём последнюю ветку:

Compilation failed: [Matching not exhaustive: possibleTypes are Order, ReissueTransaction,
BurnTransaction, MassTransferTransaction, ExchangeTransaction, TransferTransaction,
SetAssetScriptTransaction, InvokeScriptTransaction, … while matched are TransferTransaction]

Компилятор перечислил все типы, которые вы не разобрали. Это не придирка: пропущенная ветка в скрипте на аккаунте означает, что какой-то вид транзакции пройдёт без проверки.

Интересный факт

Matching not exhaustive — редкий случай, когда сообщение компилятора длиннее самой ошибки. Зато читать его не надо: достаточно добавить case _ => и решить, что делать с остальными.

Отвечено вопросов: 0 из 3
Чем let отличается от strict?
Проверка объявлена через let и нигде не используется. Что произойдёт?
Почему if требует else?
Отвечено верно: 0 из 3

Чтение необязательных значений​

Чтение из состояния возвращает «значение или ничего»:

let v = getInteger(this, "ball") // Int|Unit

Достать из него число можно тремя способами:

valueOrElse(getInteger(this, "ball"), 0) // подставить запасное
getIntegerValue(this, "ball") // упасть, если нет
match getInteger(this, "ball") { // разобрать вручную
case n: Int => n
case _ => 0
}
valueOrElse → сложность 13
getIntegerValue → сложность 11
Совет

getIntegerValue короче и дешевле, но падает на пустом ключе. Это ровно то, что нужно, когда отсутствие записи — ошибка: например, оценку ставят в несуществующую зачётку. А valueOrElse берут там, где пусто — законное состояние: например, вклад, которого ещё не было.

Сложность​

Число, которое компилятор печатает рядом с размером, — сложность: счётчик работы скрипта. Каждая встроенная функция стоит фиксированно, цены сложены заранее.

a + b → 2
if (1 == 1) then true else false → 1
getInteger(this, "ball") → 11
sha256(…) → 120
sigVerify(…) → 182

Узел показывает сложность каждой функции dApp отдельно:

curl .../addresses/scriptInfo/<адрес>
"callableComplexities":{"polozhit":20,"zabrat":22,"skolko":14}
Важно

В основной сети сложность ограничена и влияет на комиссию: тяжёлый скрипт стоит дороже, а слишком тяжёлый не примут. На локальном узле ограничения смягчены — у нас установилась и выполнилась функция сложностью 14 483. Поэтому проверять укладываемость надо в той сети, где будет работать приложение, а не на своей машине.

Чего в языке нет​

ЧЕГО В RIDE НЕТ — И ПОЧЕМУ ЭТО СДЕЛАНО НАРОЧНОнет цикловнельзя написать бесконечный скриптнет своих типов и классовструктура известна заранеенет изменяемых переменныхзначение, объявленное один раз, не меняетсянет дробных чиселденьги считают целыми: 7 / 2 даёт 3нет обращений наружускрипт не ходит в сеть и не читает файлыитог: стоимость любого скрипта известна до запуска — её считает компилятор
Пять отсутствующих возможностей — и то, ради чего их убрали
  • Циклов. Бесконечный скрипт написать нельзя в принципе. Списки обрабатывают функциями FOLD, у которых число шагов известно заранее.
  • Своих типов и классов. Структура данных известна компилятору целиком.
  • Изменяемых переменных. Объявленное один раз значение не меняется.
  • Дробных чисел. Деление целое: 7 / 2 даёт 3.
  • Обращений наружу. Скрипт не ходит в сеть и не читает файлы: неоткуда взяться расхождению между узлами.

Итог — то самое свойство, ради которого всё это сделано: цена скрипта известна до запуска.

Типичные ошибки​

Ошибка 1. Проверка под let.

Разобрана выше. Самая дорогая ошибка в языке: код есть, а проверки нет.

Ошибка 2. if без else.

expected a false branch

Ошибка 3. Неполный match.

Matching not exhaustive: possibleTypes are Order, ReissueTransaction, …

Добавить case _ => и решить, что делать с остальными типами.

Ошибка 4. Опечатка в имени функции.

sigVerifu(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
Can't find a function 'sigVerifu'(ByteVector, ByteVector, ByteVector) or it is @Callable in 85-142

Обратите внимание: компилятор перечислил типы аргументов. Это же сообщение появится, если имя верное, а типы не те.

Ошибка 5. Присваивание вместо сравнения.

let x = 5
x = 6
Parse error: expected "let" or "strict" keyword in 95-96

Компилятор увидел строку, которая начинается с имени, и ждал объявления.

Отвечено вопросов: 0 из 3
Чем valueOrElse отличается от getIntegerValue?
Почему в RIDE нет циклов?
Что означает «Can t find a function sigVerifu(ByteVector, ByteVector, ByteVector)»?
Отвечено верно: 0 из 3

Шпаргалка по главе​

Что нужноКак
Шапка скриптатри директивы: STDLIB_VERSION, CONTENT_TYPE, SCRIPT_TYPE
Объявить значениеlet a = 2 — ленивое
Заставить вычислитьstrict a = …
Ветвлениеif (…) then … else … — else обязателен
Разбор по типуmatch x { case t: Type => … case _ => … }
Чтение с запасным значениемvalueOrElse(getInteger(this, k), 0)
Чтение со строгим падениемgetIntegerValue(this, k)
Скомпилироватьcompile(src) из @waves/ride-js
Узнать сложностьполе complexity в результате компиляции

Словарь главы​

ТерминЧто это
ДирективаСтрока {-# … #-} в шапке; меняет правила разбора
EXPRESSIONСкрипт-выражение: обязан вернуть Boolean
DAPPСкрипт-приложение с вызываемыми функциями
letЛенивое значение: считается только при обращении
strictЗначение, которое вычисляется всегда
СложностьСчётчик работы скрипта; считается компилятором заранее
`IntUnit`
Matching not exhaustiveВ match разобраны не все возможные типы

Цель: скорость ≥ 90 зн/мин, точность ≥ 88%

{-# STDLIB_VERSION 6 #-}

Что должен уметь после главы​

  • написать шапку скрипта и объяснить каждую директиву;
  • отличить let от strict и не потерять проверку;
  • написать if и match так, чтобы компилятор не ругался;
  • прочитать значение из состояния тремя способами и выбрать подходящий;
  • объяснить, что такое сложность и откуда она берётся;
  • назвать пять отсутствующих возможностей языка и объяснить, зачем их убрали;
  • разобрать пять типичных сообщений компилятора.

Проверка по главе​

Сводная проверка по всей главе: 10 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоRIDE: функциональный язык смарт-контрактов. Михаил ПотанинFPURE
ВидеоInal Kardanov Как писать скрипты (смарт контракты) Waves ч.1Ярослав Логинов
ВидеоRide Programing Language, Waves TechGg Pro
ВидеоDeveloping smart contracts on Waves Protocol | Waves Hackathon 2022 WorkshopDoraHacks
ВидеоWaves Smart Contracts: Console ExampleWaves Tech

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.