Course Overview

Глава 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 транзакции и скрипты выступают в качестве своего рода “посредника” между человеком, взаимодействующим с Блокчейном, и смарт-контрактами. Это выглядит примерно так:

drawing

Транзакции vs. Скрипты

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

drawing

Как вы можете видеть, скрипты также не стоят денег (фу!). С другой стороны, транзакции стоят “газа”, который является формой оплаты, необходимой для изменения данных в Блокчейне.

Скрипты

В течение предыдущего дня мы фактически реализовали наш первый сценарий на игровой площадке Flow. Давайте вернемся к этому примеру:

Загрузите игровую площадку flow (https://play.onflow.org), скопируйте этот контракт в учетную запись 0x01 и нажмите “Deploy”:

cadence
		
			pub contract HelloWorld {

    pub let greeting: String

    init() {
        self.greeting = "Hello, World!"
    }
}
		 
	

Затем перейдите на вкладку Script с левой стороны и верните наш вчерашний сценарий:

cadence
		
			import HelloWorld from 0x01

pub fun main(): String {
    return HelloWorld.greeting
}
		 
	

Если вы нажмете “Execute”, в консоли должно появиться сообщение “Hello, World!“. Отлично! Вы только что выполнили скрипт. Обратите внимание, что оплата не потребовалась, и мы просмотрели данные в нашем смарт-контракте.

Транзакции

Теперь давайте рассмотрим пример транзакции. С левой стороны, в разделе “Transaction Templates”, нажмите на вкладку “Transaction”. Удалите все на этой вкладке, чтобы она выглядела следующим образом:

drawing

Хорошо, отлично. Теперь мы хотим изменить данные в Блокчейне. Чтобы сделать это, давайте создадим нашу транзакцию. Мы можем сделать это, поместив данный код на страницу:

cadence
		
			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 и добавьте эту функцию внутрь контракта:

cadence
		
			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”. Теперь ваш код должен выглядеть следующим образом:

cadence
		
			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 в нашу транзакцию, чтобы мы могли передать значение для нового приветствия. Мы можем сделать это следующим образом:

cadence
		
			import HelloWorld from 0x01

transaction(myNewGreeting: String) {

  prepare(signer: AuthAccount) {}

  execute {
    HelloWorld.changeGreeting(newGreeting: myNewGreeting)
  }
}
		 
	

Теперь справа появится подсказка. Мы можем ввести наше новое приветствие в маленькое окошко! Давайте введем “Goodbye, World!“:

drawing

Заметьте также, что мы можем “подписать” эту транзакцию с любого аккаунта. Поскольку это не имеет значения (мы не обращаемся к данным на аккаунте), смело выбирайте любой аккаунт по своему усмотрению.

После нажатия кнопки “Send” вернитесь к своему скрипту и нажмите кнопку “Execute”. Теперь вы должны увидеть “Goodbye, World!”, напечатанное в консоли. Бум, вы только что успешно осуществили свою первую транзакцию.

На этом мы закончим на сегодня.

Квесты

Пожалуйста, отвечайте на выбранном вами языке.

  1. Объясните, почему мы не будем вызывать changeGreeting в скрипте.

  2. Что означает AuthAccount на этапе prepare в транзакции?

  3. В чем разница между фазой prepare и фазой execute в транзакции?

  4. Это самый сложный квест на данный момент, так что если он займет у вас некоторое время, не волнуйтесь! Я могу помочь вам в Discord, если у вас есть вопросы.

  • Добавьте две новые вещи в ваш контракт:

    • Переменную с именем myNumber, имеющую тип Int (присвойте ей 0 при развертывании контракта)
    • Функцию updateMyNumber, которая принимает в качестве параметра новое число newNumber, имеющую тип Int, и обновляющую myNumber до значения newNumber.
  • Добавьте скрипт, который считывает myNumber из контракта

  • Добавьте транзакцию, которая принимает параметр с именем myNewNumber и передает его в функцию updateMyNumber. Убедитесь, что число изменилось, запустив скрипт снова.