Глава 2 День 2 - Транзакции и скрипты
Привет вам, сумасшедшие люди из Cadence! Мы ВОЗВРАЩАЕМСЯ к еще одному дню, и в этом дне мы будем углубляться в транзакции и скрипты. Если вы еще этого не сделали, обязательно прочитайте [вводную часть по транзакциям и скриптам в Главе 1 День 1] (https://github.com/emerald-dao/beginner-cadence-course/tree/main/chapter1.0/lesson1#transactions–scripts).
Видео
Если вы хотите получить этот (невероятный) контент в видеоформате, вы можете посмотреть это видео: https://www.youtube.com/watch?v=T2QTTFnQa5k
Транзакции и скрипты
Транзакции и скрипты необходимы для любого приложения Блокчейна. Без них мы вообще не смогли бы взаимодействовать с Блокчейном. В Flow они еще более особенные, потому что они обе отделены от контракта. Если вы уже кодили на Ethereum, вы знаете, что транзакции - это просто функции, которые вы вызываете внутри самого контракта (если вы этого не знаете, ничего страшного!). Однако в Flow транзакции и скрипты выступают в качестве своего рода “посредника” между человеком, взаимодействующим с Блокчейном, и смарт-контрактами. Это выглядит примерно так:

Транзакции vs. Скрипты
Итак, в чем же разница между транзакциями и скриптами? Самая большая разница заключается в том, что транзакции изменяют данные в Блокчейне, а скрипты просматривают данные в Блокчейне. Вот полезная схема для понимания различий:

Как вы можете видеть, скрипты также не стоят денег (фу!). С другой стороны, транзакции стоят “газа”, который является формой оплаты, необходимой для изменения данных в Блокчейне.
Скрипты
В течение предыдущего дня мы фактически реализовали наш первый сценарий на игровой площадке Flow. Давайте вернемся к этому примеру:
Загрузите игровую площадку flow (https://play.onflow.org), скопируйте этот контракт в учетную запись 0x01
и нажмите “Deploy”:
pub contract HelloWorld {
pub let greeting: String
init() {
self.greeting = "Hello, World!"
}
}
Затем перейдите на вкладку Script с левой стороны и верните наш вчерашний сценарий:
import HelloWorld from 0x01
pub fun main(): String {
return HelloWorld.greeting
}
Если вы нажмете “Execute”, в консоли должно появиться сообщение “Hello, World!“. Отлично! Вы только что выполнили скрипт. Обратите внимание, что оплата не потребовалась, и мы просмотрели данные в нашем смарт-контракте.
Транзакции
Теперь давайте рассмотрим пример транзакции. С левой стороны, в разделе “Transaction Templates”, нажмите на вкладку “Transaction”. Удалите все на этой вкладке, чтобы она выглядела следующим образом:
Хорошо, отлично. Теперь мы хотим изменить данные в Блокчейне. Чтобы сделать это, давайте создадим нашу транзакцию. Мы можем сделать это, поместив данный код на страницу:
transaction() {
prepare(signer: AuthAccount) {}
execute {}
}
Бум! Это пустая транзакция, которая ничего не делает. Чтобы объяснить, что такое prepare
и execute
, нам нужно сделать небольшой перерыв и поговорить о аккаунтах на Flow.
Аккаунт на Flow
На Flow учетные записи могут хранить свои собственные данные. Что это значит? Если я владею NFT (NonFungibleToken) на Flow, то этот NFT хранится на моем аккаунте. Это очень отличается от других Блокчейнов, таких как Ethereum. В Ethereum ваш NFT хранится в смарт-контракте. В Flow мы фактически позволяем аккаунтам самим хранить свои данные, что очень здорово. Но как нам получить доступ к данным на своём аккаунте? Мы можем сделать это с помощью типа AuthAccount
. Каждый раз, когда пользователь (вроде нас с вами) отправляет транзакцию, вы должны оплатить ее, а затем “подписать”. Это означает, что вы нажали на кнопку, говорящую: “Эй, я хочу одобрить эту транзакцию”. Когда вы подписываете ее, транзакция принимает ваш AuthAccount
и может получить доступ к данным вашего аккаунта.
Вы можете видеть, как это делается в части транзакции prepare
, и в этом весь смысл фазы prepare
: получить доступ к информации/данным вашего аккаунта. С другой стороны, фаза execute
не может этого сделать. Но она может вызывать функции и делать то, что нужно для изменения данных в Блокчейне. ПРИМЕЧАНИЕ: В реальности вам никогда фактически не понадобится фаза execute
. Технически вы можете сделать все в фазе prepare
, но так код будет менее понятным. Лучше разделить логику.
Вернемся к нашему примеру
Итак, мы хотим изменить наше поле greeting
на что-то другое, чем “Hello, World!“. Но есть проблема. Мы не добавили в смарт-контракт способ изменения наших данных! Поэтому мы должны добавить функцию в контракт, чтобы сделать это.
Вернитесь к аккаунту 0x01
и добавьте эту функцию внутрь контракта:
pub fun changeGreeting(newGreeting: String) {
self.greeting = newGreeting
}
Что это значит? Вспомните из предыдущих дней, что мы говорили о функциях. Вы задаете их следующим образом:
[access modifier] fun [function name](parameter1: Type, parameter2: Type, ...): [return type] {}
Чтобы не усложнять ситуацию, мы используем pub
в качестве модификатора доступа. pub
означает, что мы можем вызвать эту функцию из любого места (в нашем контракте или в транзакции). Мы также принимаем параметр newGreeting
, который является строкой, и присваиваем нашему приветствию новое значение.
Но подождите! В контракте есть ошибка. В нем говорится “невозможно присвоить постоянный член: greeting
”. Почему он так говорит? Помните, мы сделали наше приветствие let
. let
означает, что это константа, поэтому если мы хотим изменить наше greeting
, мы должны изменить его на var
. Не забудьте снова нажать “Deploy”. Теперь ваш код должен выглядеть следующим образом:
pub contract HelloWorld {
pub var greeting: String
pub fun changeGreeting(newGreeting: String) {
self.greeting = newGreeting
}
init() {
self.greeting = "Hello, World!"
}
}
Теперь, когда мы настроили наш контракт, давайте вернемся к нашей транзакции. Во-первых, давайте убедимся, что мы импортировали
наш контракт HelloWorld, следующим образом: import HelloWorld from 0x01
. Затем мы должны решить: где мы хотим вызвать changeGreeting
? На этапе prepare
или на этапе execute
? Ответ - в фазе execute
, потому что мы не обращаемся ни к каким данным в учетной записи. Мы просто изменяем некоторые данные в смарт-контракте.
Мы можем сделать это, добавив эту строку в фазу execute
: HelloWorld.changeGreeting(newGreeting: myNewGreeting)
. Когда вы вызываете функцию в Cadence, вы передаете параметры, делая (argumentLabel: value
), где argumentLabel
- это имя аргумента, а value
- фактическое значение. Вы заметите, что мы получаем ошибку, что myNewGreeting
не определен, что вполне логично, поскольку мы ниоткуда его не получаем. Поэтому давайте добавим параметр myNewGreeting
в нашу транзакцию, чтобы мы могли передать значение для нового приветствия. Мы можем сделать это следующим образом:
import HelloWorld from 0x01
transaction(myNewGreeting: String) {
prepare(signer: AuthAccount) {}
execute {
HelloWorld.changeGreeting(newGreeting: myNewGreeting)
}
}
Теперь справа появится подсказка. Мы можем ввести наше новое приветствие в маленькое окошко! Давайте введем “Goodbye, World!“:
Заметьте также, что мы можем “подписать” эту транзакцию с любого аккаунта. Поскольку это не имеет значения (мы не обращаемся к данным на аккаунте), смело выбирайте любой аккаунт по своему усмотрению.
После нажатия кнопки “Send” вернитесь к своему скрипту и нажмите кнопку “Execute”. Теперь вы должны увидеть “Goodbye, World!”, напечатанное в консоли. Бум, вы только что успешно осуществили свою первую транзакцию.
На этом мы закончим на сегодня.
Квесты
Пожалуйста, отвечайте на выбранном вами языке.
Объясните, почему мы не будем вызывать
changeGreeting
в скрипте.Что означает
AuthAccount
на этапеprepare
в транзакции?В чем разница между фазой
prepare
и фазойexecute
в транзакции?Это самый сложный квест на данный момент, так что если он займет у вас некоторое время, не волнуйтесь! Я могу помочь вам в Discord, если у вас есть вопросы.
Добавьте две новые вещи в ваш контракт:
- Переменную с именем
myNumber
, имеющую типInt
(присвойте ей 0 при развертывании контракта) - Функцию
updateMyNumber
, которая принимает в качестве параметра новое числоnewNumber
, имеющую типInt
, и обновляющуюmyNumber
до значенияnewNumber
.
- Переменную с именем
Добавьте скрипт, который считывает
myNumber
из контрактаДобавьте транзакцию, которая принимает параметр с именем
myNewNumber
и передает его в функциюupdateMyNumber
. Убедитесь, что число изменилось, запустив скрипт снова.