Перейти к основному содержимому
Блокчейн70ГЛАВА 70dApp на RIDE: зачётка, которая живёт в сетиi.caller3MGu…thisdApp@Callable(i)func zavesti(id)[ StringEntry(…) ]возвращает список действий

dApp на RIDE: зачётка, которая живёт в сети

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

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

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

Чтобы появилось приложение, скрипт должен уметь две вещи: принимать вызовы снаружи и менять состояние. Именно это и добавляет второй вид скрипта — DAPP.

Мы напишем на нём ту же зачётку, что уже писали на Solidity и чейнкодом Fabric. Сравнение получится честным: одна задача, третья платформа. Всё ниже — прогон на локальном узле Waves 1.6.4.

Устройство вызываемой функции​

{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

@Callable(i)
func zavesti(id: String, fio: String) = {
let kluch = "z_" + id
match getString(this, kluch) {
case s: String => throw("Zachetka " + id + " uzhe zavedena")
case _ => [ StringEntry(kluch, fio), IntegerEntry(kluch + "_ball", 0) ]
}
}
ВЫЗЫВАЕМАЯ ФУНКЦИЯ ВОЗВРАЩАЕТ СПИСОК ДЕЙСТВИЙ@Callable(i)func zavesti(id: String, fio: String) = { let kluch = "z_" + id …проверки… [ StringEntry(kluch, fio) ]}i — кто и с чем пришёлi.callerадрес вызвавшегоi.paymentsчто приложено к вызовуi.transactionIdфункция ничего не пишет сама: она возвращает список того, что сеть должна сделатьвернуть одно действие вместо списка нельзя — компилятор перечислит все допустимые типы
Функция не пишет сама — она возвращает список того, что сеть должна сделать
Нажми на часть выражения

Попробуем вернуть одно действие вместо списка:

@Callable(i)
func f() = { IntegerEntry("x", 1) }
Compilation failed: [CallableFunction needs to return
(List[BinaryEntry|BooleanEntry|Burn|DeleteEntry|IntegerEntry|Issue|Lease|LeaseCancel|
Reissue|ScriptTransfer|SponsorFee|StringEntry], Any) | List[…]

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

Что можно положить в список​

ЧТО МОЖНО ПОЛОЖИТЬ В СПИСОК ДЕЙСТВИЙIntegerEntryзаписать число в своё состояниеStringEntryзаписать строкуBooleanEntry / BinaryEntryзаписать да-нет или байтыDeleteEntryудалить ключScriptTransferотправить деньги со счёта dAppIssue / Reissue / Burnвыпустить, допечатать, сжечь токенвсё, что делает dApp, — в этом списке: другого способа изменить мир у него нет
Всё, что делает dApp, — в этом списке; другого способа менять мир у него нет
ДействиеЧто делает
IntegerEntry, StringEntryзаписать число или строку в своё состояние
BooleanEntry, BinaryEntryзаписать да-нет или байты
DeleteEntryудалить ключ
ScriptTransferотправить деньги со счёта dApp
Issue, Reissue, Burnвыпустить, допечатать, сжечь токен
Lease, LeaseCancel, SponsorFeeаренда и спонсирование комиссий
Важно

Функция ничего не делает сама — она возвращает список. Пока функция считает, ничего не записано; если где-то по дороге сработает throw, не запишется вообще ничего. Это и есть встроенная «всё или ничего», которую в Solidity обеспечивает revert.

Отвечено вопросов: 0 из 3
Что возвращает вызываемая функция?
Что означает @Callable(i)?
Что произойдёт, если по дороге сработает throw?
Отвечено верно: 0 из 3

Кто вызывает и чьё состояние меняется​

ВЫЗЫВАЕТ ОДИН, МЕНЯЕТСЯ СОСТОЯНИЕ ДРУГОГОВызвавший3MGuDcHHKqHaU1CDt5a98…подписал транзакцию 16заплатил 0,005 WAVESinvokeАккаунт dApp3M52rTnLTitKusqJ9JCtJs4JCiptN9Yq4RXthis — это он: запись идёт в ЕГО состояниеи деньги ScriptTransfer уходят с его счётаi.caller — вызвавший, this — сам dApp: перепутать их значит написать не ту программуключи, привязанные к пользователю, поэтому и делают составными: "vklad_" + i.caller.toString()состояние вызвавшего dApp изменить не может — только своё
i.caller — вызвавший, this — сам dApp; перепутать их значит написать не ту программу

Внутри функции доступны два разных адреса, и путать их нельзя:

  • this — аккаунт, на котором стоит скрипт. Записи идут в его состояние, деньги ScriptTransfer уходят с его счёта.
  • i.caller — тот, кто вызвал функцию. Он подписал транзакцию типа 16 и заплатил комиссию.

Отсюда типичный приём — привязать данные к вызвавшему составным ключом:

func kluch(a: Address) = "vklad_" + a.toString()
Важно

Состояние вызвавшего dApp изменить не может. Он пишет только в своё. Поэтому все данные пользователей хранятся в состоянии самого приложения, и ключи делают составными — иначе двум пользователям не разойтись.

Вместе с i приезжает ещё несколько полезных вещей:

ПолеЧто в нём
i.callerадрес вызвавшего
i.callerPublicKeyего публичный ключ
i.paymentsчто приложено к вызову (следующая глава)
i.transactionIdидентификатор этой транзакции
i.feeзаплаченная комиссия

Устанавливаем и вызываем​

Установка — та же транзакция SetScript, что и для смарт-аккаунта:

сложность: 182 | размер: 752
в блоке: 24

Вызов — транзакция типа 16:

const { invokeScript } = require('@waves/waves-transactions');

const tx = invokeScript({
dApp: '3M52rTnLTitKusqJ9JCtJs4JCiptN9Yq4RX',
call: { function: 'zavesti', args: [
{ type: 'string', value: 'z-1' },
{ type: 'string', value: 'Kovalik Oleg' },
] },
chainId: 'R',
}, seedПользователя);
тип: 16 | комиссия: 500000
в блоке: 24 | статус: succeeded
изменения состояния: {"data":[{"key":"z_z-1","type":"string","value":"Kovalik Oleg"},
{"key":"z_z-1_ball","type":"integer","value":0}],
"transfers":[],"issues":[],"burns":[],"invokes":[]}
Нажми на часть выражения

Проверяем состояние dApp:

[{"key":"z_z-1","type":"string","value":"Kovalik Oleg"},
{"key":"z_z-1_ball","type":"integer","value":0}]

Отказы​

Вторая функция — с проверкой диапазона:

@Callable(i)
func postavit(id: String, ball: Int) = {
let kluch = "z_" + id
strict fio = getStringValue(this, kluch)
if (ball < 2 || ball > 5) then throw("Ball vne diapazona 2..5")
else [ IntegerEntry(kluch + "_ball", ball) ]
}

Обратите внимание на strict: чтение нужно ради побочного эффекта — упасть, если зачётки нет. Под let эта проверка бы не выполнилась.

Три отказа подряд, все с живого узла:

повторное заведение:
{"error":306,"message":"Error while executing dApp: Zachetka z-1 uzhe zavedena"}

балл вне диапазона:
{"error":306,"message":"Error while executing dApp: Ball vne diapazona 2..5"}

несуществующая функция:
{"error":306,"message":"Error while executing dApp: @Callable function 'netTakoy' doesn't exist in the script"}
Интересный факт

error: 306 — «скрипт dApp упал». В отличие от смарт-аккаунта, здесь узел передаёт ваш текст из throw. Поэтому сообщения стоит писать осмысленно: они и есть интерфейс ошибок вашего приложения.

Отвечено вопросов: 0 из 3
Чем this отличается от i.caller?
Почему чтение зачётки объявлено через strict?
Что означает error 306?
Отвечено верно: 0 из 3

Та же задача на трёх платформах​

Стоит сравнить прямо сейчас, пока все три реализации свежи.

SolidityЧейнкод FabricdApp на RIDE
Где данныеполя контрактаобщее хранилищесостояние аккаунта
Проверкаrequirethrowthrow
Возврат результатаизменяет поляputStateсписок действий
«Всё или ничего»revertнабор измененийсписок не вернулся
Кто вызвалmsg.senderctx.clientIdentityi.caller
Стоимость вызова~96 000 газанет платы0,005 WAVES

Логика во всех трёх одинаковая, а вот способ вернуть результат — разный. В RIDE он самый непривычный и самый строгий: функция описывает изменения, а применяет их сеть.

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

Ошибка 1. Вернуть действие вместо списка.

CallableFunction needs to return (List[BinaryEntry|BooleanEntry|…], Any) | List[…]

Даже одно действие оборачивают в квадратные скобки.

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

Та же ловушка, что и в прошлой главе, но здесь дороже: проверка в dApp защищает чужие деньги.

Ошибка 3. Писать в состояние вызвавшего.

Нельзя. Данные пользователей живут в состоянии dApp под составными ключами.

Ошибка 4. Две функции с одинаковым именем.

Compilation failed: [Value 'f' can't be defined because function with this name is already defined]

Перегрузки функций в RIDE нет.

Ошибка 5. Не проверить applicationStatus.

Вызов может попасть в блок и не примениться. Проверять надо не код ответа, а состояние или stateChanges.

Отвечено вопросов: 0 из 3
Функция возвращает одно действие без скобок. Что скажет компилятор?
Можно ли объявить две @Callable-функции с одним именем?
Где хранят данные пользователей dApp?
Отвечено верно: 0 из 3

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

Что нужноКак
Объявить вызываемую функцию@Callable(i) + func имя(арг: Тип) = [ … ]
Записать в состояниеIntegerEntry(k, v), StringEntry(k, v)
Удалить ключDeleteEntry(k)
Отправить деньгиScriptTransfer(adres, summa, unit)
Отказатьthrow("текст")
Прочитать своё состояниеgetInteger(this, k), getStringValue(this, k)
Узнать вызвавшегоi.caller
Вызвать снаружиinvokeScript({ dApp, call: { function, args }, chainId }, seed)
Посмотреть результатполе stateChanges в /transactions/info/<id>

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

ТерминЧто это
dAppАккаунт со скриптом CONTENT_TYPE DAPP
@CallableПометка функции, вызываемой снаружи
iСведения о вызове: кто, с чем, какой транзакцией
thisСам dApp: его состояние и его счёт
Список действийТо, что возвращает функция; применяет его сеть
stateChangesЧто именно изменил вызов; видно в ответе узла
error: 306Скрипт dApp упал; текст — ваш из throw
Составной ключ"vklad_" + i.caller.toString() — способ разойтись пользователям

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

{-# CONTENT_TYPE DAPP #-}

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

  • написать dApp с вызываемыми функциями и установить его на аккаунт;
  • объяснить, почему функция возвращает список, а не пишет сама;
  • перечислить основные действия и сказать, что делает каждое;
  • отличить this от i.caller и хранить данные пользователей правильно;
  • вызвать функцию снаружи и прочитать stateChanges;
  • разобрать три отказа dApp и объяснить, откуда берётся текст ошибки.

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

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

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

Видео по теме

ВидеоКак создать децентрализованное приложение? Воркшоп от Waves Platform в КиевеCRYPTODEALERS
ВидеоWaves Platform воркшоп для веб-разработчиков | Kyiv BlockchainHubCRYPTODEALERS
ВидеоWaves Smart Contracts: Console ExampleWaves Tech
ВидеоЧто такое dApps? Обзор и примеры с анимациейWhiteboard Crypto на русском
ВидеоИнал Карданов, Waves Platform DevOps практики в Blockchain разработкеЛогрокон

Комментарии

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