Създайте интелигентни договори за Tezos blockchain с помощта на езика на ликвидността (част 1)

След като прекарах две години в изучаване на Solidity за разработване на dapps на Ethereum, открих Tezos. Веднага бях съблазнен от възможността, която блокчейнът на Tezos носи със себе си, неговата скалируемост, децентрализация и Michelson, което прави интелигентните договори по-сигурни.

Когато реших да се науча как да пиша интелигентни договори за блокчейн Tezos, се натъкнах на суровата реалност: екосистемата Tezos далеч не е толкова развита, колкото тази на Ethereum.
Докато Ganache е най-добрият ви приятел, когато разработвате интелигентни договори за Ethereum, няма еквивалент на Tezos. По време на проучването си онлайн намерих Granary, но не успях да го накарам да работи на моя MacBook и изглежда, че все още е в процес на разработка (последният комит в тяхното репо в Github беше преди 5 месеца към февруари 2020 г. ).

В допълнение към оскъдните инструменти, ще разберете също, че уроците също са оскъдни. Документацията за изучаване на езици от високо ниво за Tezos blockchain през повечето време е непълна (като за Fi) или изисква добро познаване на функционалното програмиране (като Ligo или Liquidity). Има и SmartPy, който, трябва да призная, свърши чудесна работа както в документацията, така и в онлайн редактора. Но като начало не съм голям фен на Python и исках да приема предизвикателството да науча функционален език.

Избрах „Ликвидност“. Въпреки че все още съм начинаещ в Liquidity, реших, че би било интересно да споделя опита си и да напиша основно ръководство стъпка по стъпка, за да помогна на други хора, идващи от Solidity или език за програмиране като JavaScript. Следващият урок ще разкрие основните понятия за ликвидност и функционално програмиране. Ще използвам синтаксиса ReasonML, тъй като смятам, че е по-близо до JavaScript, който е език за програмиране, който много програмисти познават и се чувстват удобно. Ако греша някъде в урока, моля не се колебайте да ме поправите :)

Структура на договор

Според официалната документация на Liquidity, договор в Liquidity винаги представя следната структура:

[%%version 2.0]

<... local declarations ...>

type storage = TYPE

let%init storage
    (x : TYPE)
    (y : TYPE)
    ... =
    BODY

let%entry entrypoint1
    (p1 : TYPE)
    (s1 : TYPE) =
    BODY

let%entry entrypoint2
    (p2 : TYPE)
    (s2 : TYPE) =
    BODY

let%entry default
    (parameter : TYPE)
    (storage : TYPE) =
    BODY

 ...

Първото изречение е версията на компилатора Liquidity, който използвате. Не е абсолютно необходимо (примерите, предоставени в онлайн редактора, се компилират с изявление за версия).

Вторият ред не е задължителен и се използва за деклариране на различни типове и функции, които ще използвате в договора си по-късно.

Третият ред е декларацията за съхранение. Договорът съхранява състоянието си в една „променлива“, като създава нов „тип“. След това различните части на хранилището могат да бъдат достъпни чрез нотация с точка (например storage.users или storage.address). ТРЯБВА да дефинирате тип хранилище във всеки договор.

След това ще намерите функцията за инициализация. Тази стъпка не е задължителна, ако вашето хранилище е твърдо кодирано, в противен случай можете да използвате, за да зададете вашите променливи за съхранение, когато внедрявате договора.

Следвайки тези стъпки, намирате входните точки. Всеки договор за ликвидност се състои от различни входни точки, които съответстват на извикванията, направени към интелигентния договор. Всички те трябва да имат различни имена. Всички те получават два аргумента: параметрите, изпратени при извикване на входната точка и текущото хранилище. След това всички те връщат два аргумента: списък с операции и новото хранилище.
Можете също така да дефинирате входна точка по подразбиране, която ще бъде изпълнена, когато договорът бъде извикан и няма входна точка посочени. Например, можете да го използвате, за да обработвате всеки tez, изпратен към договора.

Нека започнем сега с нашия договор. Това ще бъде много прост договор, който ще получава стойност и ще я съхранява.

Договорът Simple Storage

Ще започнем с дефиниране на версията, която искаме да използваме като добра практика:

[%%version 2.0]

Съхранението ще бъде много просто, ще започнем с цяло число и ще го увеличим:

[%%version 2.0]
type storage = int;

Сега можем да инициализираме нашето хранилище, когато договорът ще бъде разгърнат.

[%%version 2.0]
type storage = int;
let%init storage = 0;

След тази стъпка нашето хранилище се инициализира до нула.

Сега нека напишем входната точка, за да увеличим стойността:

[%%version 2.0]
type storage = int;
let%init storage = 0;
let%entry increment = (num: int, storage) => ([], storage + num);

Нека го разбием:

  1. let%entry = тази ключова дума показва, че декларирате входна точка, която може да бъде извикана отвън.
  2. инкремент = това е името на вашия запис, то трябва да е уникално за договора.
  3. (num: int, storage) = както е обяснено по-горе, всяка входна точка приема два аргумента: параметрите, предадени на входната точка и паметта. Тук параметърът е цяло число с име „num“.
  4. ([], storage + num) = всяка входна точка връща две стойности: списък с вътрешни операции за изпълнение след изпълнение на договора и окончателното съхранение.

Ако всичко върви добре, трябва да получите следния код на Michelson в онлайн компилатора на Liquidity:

Съхраняване на множество стойности

Увеличаването на цяло число в хранилището е страхотно, но какво да кажем за съхраняването на „Hello world“ в договора?? Нека модифицираме предишния договор, за да запазим цяло число и низ.

Първо, трябва да променим типа на хранилището. Не забравяйте, че съхранението е една единствена стойност, така че ако искаме да съхраняваме множество стойности, ще трябва да използваме тип, наречен „запис“:

[%%version 2.0]
type storage = {
  count: int,
  greeting: string
}

Записът е много подобен на JavaScript обект, една от основните разлики е, че трябва да го декларирате изрично. Отворете набор от фигурни скоби и напишете името на полето, последвано от типа. В нашия случай се нуждаем от име на поле, наречено „count” от тип integer и име на поле, наречено „greeting” от тип string.

Сега е време да напишем функцията за инициализация:

[%%version 2.0]
type storage = {
  count: int,
  greeting: string
}
let%init storage = (count: int, greeting: string) => {count, greeting};

Както в първата версия на договора, започваме с let%init, последвано от името на хранилището. Ще дадем възможност на потребителите да задават свои собствени стойности, които да се предават като параметри. Ще принудим тези стойности да бъдат едно цяло число и един низ. Благодарение на ReasonML можем да използваме punning и да пишем {count, greeting} вместо {count: count, greeting: greeting}, за да върнем нашето инициализирано хранилище.

След това ще пренапишем функцията за нарастване. Този път, тъй като хранилището съдържа 2 стойности, ще трябва да бъдем внимателни и да актуализираме само полето „count“:

[%%version 2.0]
type storage = {
  count: int,
  greeting: string
}
let%init storage = (count: int, greeting: string) => 
  {count, greeting};
let%entry increment = (num: int, storage) => {
  let storage = storage.count = storage.count + num;
  ([], storage);
}

Вече започвате да се запознавате с входните точки, параметъра(ите) и паметта като аргументи и списъка с операции и новата памет като върнати стойности. В средата увеличаваме полето „count“ на хранилището с цялото число, предоставено от потребителя.

Входната точка за добавяне на нов поздрав ще изглежда много подобно:

[%%version 2.0]
type storage = {
  count: int,
  greeting: string
}
let%init storage = (count: int, greeting: string) => {count, greeting};
let%entry increment = (num: int, storage) => {
  let storage = storage.count = storage.count + num;
  ([], storage);
}
let%entry greet = (greeting: string, storage) => {
  let storage = storage.greeting = greeting;
  ([], storage);
}

Вместо цяло число ще очакваме низ и вместо да увеличаваме полето в хранилището, просто ще го заменим с новата стойност.

Ако всичко върви добре, когато компилирате кода, трябва да бъдете възнаградени със следния код на Michelson:

Направи го! Написахте първия си код за ликвидност!

В част 2 ще отделим малко време, за да научим как да използваме интерфейса на онлайн редактора за ликвидност, за да тестваме нашия договор.
В част 3 ще разгледаме нови концепции и ще продължим нашето пътуване в ликвидността.

Тази статия е достъпна и в IPFS: QmaF9Thhd57c8ECUps7R1mFrDs8vXTuEUyuG7deDrKNNzh