Перейти к основному содержимому
Блокчейн69ГЛАВА 69Смарт-аккаунт: скрипт вместо одной подписиproofs[0]Алисаproofs[1]Борисproofs[2]Соняa + b + s >= 2две подписи из трёх

Смарт-аккаунт: скрипт вместо одной подписи

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

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

Обычный аккаунт работает по одному правилу: «подпись верна — пропускаем». В предыдущей главе мы написали это правило на RIDE и увидели, что оно стоит 182 единицы сложности.

А теперь главное: это правило можно заменить. Навесить на обычный аккаунт скрипт — и он начнёт пропускать транзакции по своему собственному условию. Две подписи из трёх. Никаких переводов до определённой даты. Только записи данных, но не деньги. Что угодно, что выражается в RIDE.

Такой аккаунт называется смарт-аккаунтом, и это самая недооценённая возможность Waves: в Ethereum ровно то же самое требует отдельного контракта-кошелька и куда больше кода.

Эта глава — про то, как это делается, сколько стоит и где подводные камни. Все ответы узла сняты на локальном узле Waves 1.6.4.

Скрипт-турникет​

СКРИПТ НА АККАУНТЕ — ТУРНИКЕТ НА ВЫХОДЕТранзакцияTransferDataSetScriptотправляется со счётаСкрипт-проверяющийmatch tx { case t: TransferTransaction => false case _ => sigVerify(…)}true → уходитfalse → отказскрипт решает про исходящие: входящий перевод он не остановитотказ звучит одинаково всегда: Transaction is not allowed by account-script
Скрипт решает про исходящие транзакции: true пропускает, false отказывает

Начнём с очевидного правила — «переводы запрещены»:

{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}
match tx {
case t: TransferTransaction => false
case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}
сложность: 183

Ставим:

const { setScript } = require('@waves/waves-transactions');
const ss = setScript({ script: compiled.base64, chainId: 'R' }, seed);
в блоке: 30

Проверим, что было до и что стало после. До установки тот же перевод проходил:

ответ: 200

После:

код: 400
{"error":307,"message":"Transaction is not allowed by account-script"}
Нажми на часть выражения

А запись данных по-прежнему проходит — её пропускает ветка case _:

код: 200 | в блоке: 30
Важно

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

Что видит скрипт​

Внутри скрипта доступно одно значение — tx, сама транзакция. Что в нём есть, зависит от типа, поэтому почти каждый скрипт начинается с match.

ПолеЧто в нём
tx.bodyBytesтело транзакции в байтах — то, что подписано
tx.proofsмассив подписей, до восьми мест
tx.senderPublicKeyпубличный ключ отправителя
tx.feeкомиссия
t.amount, t.recipientу перевода — сумма и получатель
d.dataу транзакции данных — список записей
Совет

sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey) — это ровно поведение обычного аккаунта, записанное вручную. Полезно держать эту строку в голове как «ничего не изменилось»: любой скрипт-проверяющий отличается от неё только добавленными условиями.

Отвечено вопросов: 0 из 3
Что делает скрипт на аккаунте?
Можно ли скриптом запретить приём переводов?
Чем error 307 отличается от 112?
Отвечено верно: 0 из 3

Две подписи из трёх​

Вот ради чего всё затевалось. Счёт, которым распоряжаются трое, и любые двое из них могут отправить транзакцию:

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

let alice = base58'FmEEYNK6ZxyC…'
let boris = base58'8cbuFR33cJ9x…'
let sonya = base58'6ZwCUSYEc5CA…'

let a = if sigVerify(tx.bodyBytes, tx.proofs[0], alice) then 1 else 0
let b = if sigVerify(tx.bodyBytes, tx.proofs[1], boris) then 1 else 0
let s = if sigVerify(tx.bodyBytes, tx.proofs[2], sonya) then 1 else 0

a + b + s >= 2
сложность: 549
Нажми на часть выражения
МЕСТО В МАССИВЕ ЗАКРЕПЛЕНО ЗА КЛЮЧОМproofs[0]Алисаподписалproofs[1]Борисподписалproofs[2]Соняпусто2 из 3 → проходитa + b + s >= 21 из 3 → отказперепутали места → отказподпись Алисы, положенная в proofs[1], не засчитывается: там ждут Борисапустая строка на своём месте — законное значение: так отмечают «этот не подписывал»именно поэтому proofs — массив на восемь мест, а не одно поле подписи
Каждое место в массиве закреплено за своим ключом

Одна подпись из трёх:

код: 400 | Transaction is not allowed by account-script

Две подписи:

код: 200 | принято | в блоке: 149

Две подписи, но в перепутанных местах — подпись Бориса положили в нулевое место, Алисы в первое:

код: 400 | Transaction is not allowed by account-script
Важно

Порядок в proofs — не формальность. Скрипт проверяет конкретное место конкретным ключом. Если подписант не участвует, на его место кладут пустую строку — это законное значение, означающее «этот не подписывал».

Доплата за сложность​

При первой же попытке отправить транзакцию с такого счёта узел сказал неожиданное:

State check failed. Reason: Transaction sent from smart account. Requires 400000 extra fee.
Fee for TransferTransaction (100000 in WAVES) does not exceed minimal value of 500000 WAVES.
СЛОЖНОСТЬ ПРОВЕРЯЮЩЕГО РЕШАЕТ, БУДЕТ ЛИ ДОПЛАТА183доплата0итого за перевод0,001 WAVESпростая проверка подписи549доплата400 000итого за перевод0,005 WAVESдве подписи из трёхграница — 200: пока проверяющий укладывается, аккаунт платит как обычныйузел сам называет доплату в поле extraFee ответа scriptInfo
Граница — сложность 200: до неё смарт-аккаунт платит как обычный

Правило такое: если сложность проверяющего больше 200, к комиссии каждой исходящей транзакции добавляется 0,004 WAVES. Проверяется это одним запросом:

curl .../addresses/scriptInfo/<адрес>
сложность проверяющего: 183 | доплата: 0
сложность проверяющего: 549 | доплата: 400000

Первый скрипт — «переводы запрещены», сложность 183, доплаты нет. Второй — две подписи из трёх, сложность 549, доплата есть.

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

Отсюда практический приём: если проверяющий слегка не укладывается в 200, его стоит подсократить — уложиться выгоднее, чем платить впятеро за каждую транзакцию. Например, valueOrElse стоит дороже, чем getIntegerValue, а лишний sigVerify — 182 единицы сам по себе.

Отвечено вопросов: 0 из 3
Положили подпись Алисы в proofs[1]. Что будет?
Как отметить, что подписант не участвует?
Когда появляется доплата 0,004 WAVES?
Отвечено верно: 0 из 3

Снять скрипт​

Скрипт можно убрать — если он сам это разрешает:

const off = setScript({ script: null, chainId: 'R' }, seed);
код: 200
скрипт после снятия: {"script":null,"complexity":0,"extraFee":0,"publicKey":null}

Аккаунт снова стал обычным.

Важно

Скрипт может запретить сам себя снимать — достаточно ветки case s: SetScriptTransaction => false. Такой аккаунт останется со своим правилом навсегда. Это мощный приём и одновременно способ заблокировать счёт насмерть: проверяйте, что при отладке такая ветка не попала в код случайно.

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

Ошибка 1. Забыть про доплату.

Transaction sent from smart account. Requires 400000 extra fee.

Библиотека считает комиссию по типу транзакции и про скрипт на счёте не знает. Ставить надо вручную.

Ошибка 2. Сдвигать подписи вместо пустых мест.

Подписант не участвует — на его месте пустая строка, а не отсутствие элемента. Сдвиг ломает соответствие.

Ошибка 3. Запретить всё, включая снятие скрипта.

match tx {
case _ => false // счёт заблокирован навсегда
}

Потерять так учебный счёт не страшно, рабочий — очень.

Ошибка 4. Ожидать, что скрипт остановит входящие.

Не остановит. Скрипт — про исходящие.

Ошибка 5. Думать, что отказ объяснится.

Transaction is not allowed by account-script — это всё, что будет сказано. Какое именно условие не сошлось, узел не сообщает: отлаживать приходится, упрощая скрипт по частям.

Отвечено вопросов: 0 из 3
Скрипт вернул false. Что скажет узел?
Можно ли запретить снятие скрипта?
Библиотека посчитала комиссию 0,001, а узел требует 0,005. Почему?
Отвечено верно: 0 из 3

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

Что нужноКак
Поставить скриптsetScript({ script: base64, chainId }, seed)
Снять скриптsetScript({ script: null, chainId }, seed)
Узнать сложность и доплатуGET /addresses/scriptInfo/<адрес>
Поведение обычного аккаунтаsigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
Запретить вид транзакцииcase t: TransferTransaction => false
Подписать несколькими ключамиsignTx(tx, [{privateKey: a}, {privateKey: b}, null])
Отметить «не подписывал»пустая строка на своём месте в proofs
Доплата0,004 WAVES, если сложность проверяющего больше 200

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

ТерминЧто это
Смарт-аккаунтОбычный аккаунт со скриптом-проверяющим
ПроверяющийСкрипт-выражение, решающий про исходящие транзакции
error: 307Скрипт аккаунта не разрешил транзакцию
extraFeeДоплата за сложный проверяющий: 0,004 WAVES
proofs[n]Место в массиве подписей, закреплённое за конкретным ключом
Многоподписный счётСмарт-аккаунт, требующий N подписей из M
SetScriptTransactionТип 13; им ставят и снимают скрипт

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

sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)

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

  • поставить скрипт на аккаунт и снять его;
  • объяснить, что скрипт решает только про исходящие транзакции;
  • написать проверяющего, запрещающего определённый вид транзакций;
  • собрать счёт с двумя подписями из трёх и объяснить роль мест в proofs;
  • посчитать доплату по сложности и узнать её через scriptInfo;
  • назвать способ заблокировать счёт навсегда — и не сделать этого случайно.

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

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

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

Видео по теме

ВидеоWaves Smart Contracts: Multisignature ExampleWaves Tech
ВидеоWaves Smart Contracts: Create Multi Sig Account via Waves IDE toolsWaves Tech
ВидеоWaves Smart Contracts: Escrow ExampleWaves Tech
ВидеоWaves Smart Contracts: Create MultiSig Account via Waves ClientWaves Tech
ВидеоWaves smart-contract - RIDEAnonymous Man

Комментарии

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