Руководство по Move на Aptos
Tags: tutorial, aptos, move, developer
Published: 2022-08-24 21:22:08

Блокчейн Aptos состоит из нод валидаторов, на которых работает протокол консенсуса. Протокол консенсуса согласовывает порядок транзакций и их вывод при выполнении на виртуальной машине Move Virtual Machine (MoveVM). Каждая нода валидатора переводит транзакции вместе с текущим состоянием книги блокчейна в виртуальную машину. MoveVM обрабатывает эти входные данные для получения на выходе набора изменений или дельты хранения. Как только консенсус согласует и зафиксирует выходные данные, они становятся общедоступными. В этом руководстве мы познакомим вас с основными концепциями Move и тем, как они применимы к разработке на Aptos.

## Что такое Move?

Move - это безопасный и надежный язык программирования для Web3, в котором особое внимание уделяется ограниченности и контролю доступа. Активы в Move могут быть представлены ресурсом или храниться в нем. Ограниченность обеспечивается по умолчанию, так как структуры не могут быть дублированы. Дублировать можно только те структуры, которые явно определены на уровне байткода как копия.

Контроль доступа происходит как из понятия учетных записей, так и из привилегий доступа к модулю. Модуль в Move может быть либо библиотекой, либо программой, которая может создавать, хранить или передавать активы. Move гарантирует, что только публичные функции модуля могут быть доступны другим модулям. Если структура не имеет публичного конструктора, она может быть создана только в модуле, который ее определяет. Аналогично, поля внутри struct могут быть доступны и изменены только в пределах своего модуля, то есть через публичные функции доступа и настройки.

## Сравнение с другими виртуальными машинами

**Aptos / Move**

- Хранение данных - Данные хранятся в пределах учетной записи владельца
- Распараллеливание - Возможность распараллеливания во время выполнения в Aptos 
- Безопасность транзакций - Последовательный номер
- Тип безопасности - Модульные structs и generics
- Вызов функций - Статическая передача не на generics

**Solana / SeaLevel**

- Хранение данных - Хранятся в учетной записи владельца, связанной с программой
- Распараллеливание - Требуется указать в транзакции все учетные записи и программы, к которым осуществляется доступ
- Безопасность транзакций - Уникальность транзакций + запоминание транзакций
- Тип безопасности - Программные structs
- Вызов функций - Статическая передача

**EVM**

- Хранение данных - Хранится на учетной записи, связанной со smart contract
- Распараллеливание - В настоящее время серийно ничего не производится
- Безопасность транзакций - nonces, аналогичные порядковым номерам
- Тип безопасности - Типы контрактов
- Вызов функций - Динамическая передача

## Особенности Aptos Move

Каждое развертывание MoveVM имеет возможность расширить ядро MoveVM дополнительными функциями с помощью уровня адаптера. Кроме того, MoveVM имеет структуру для поддержки стандартных операций, подобно тому, как компьютер имеет операционную систему.

Особенности адаптера Aptos Move включают:

- Мелкозернистое хранение, которое устраняет зависимость между объемом данных, хранящихся на учетной записи, и газом за транзакции, связанные с учетной записью
- [Таблицы](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=with%20the%20account-,Tables,-for%20storing%20key) для хранения ключевых, ценностных данных в рамках учетной записи в масштабе
- Параллелизм с помощью [Block-STM](https://medium.com/aptoslabs/block-stm-how-we-execute-over-160k-transactions-per-second-on-the-aptos-blockchain-3b003657e4ba), позволяющий параллельно выполнять транзакции без участия пользователя

Фреймворк Aptos поставляется с множеством полезных библиотек:

[Стандарт Token](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=A-,Token%20standard,-that%20makes%20it), позволяющий создавать NFT и другие богатые токены без публикации smart contract
[Стандарт Coin](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=A-,Coin%20standard,-that%20makes%20it), позволяющий создавать безопасные по типу Coin, публикуя тривиальный модуль.
[Итерабельная таблица](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=An-,iterable%20Table,-that%20allows%20for), позволяющая просматривать все записи в таблице
Фреймворк стейкинг и делегирования
Сервис [`type_of`](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=A-,type_of,-service%20to%20identify) для определения во время выполнения адреса, модуля и имени структуры данного типа
Система multi `signer`, позволяющая использовать несколько подписывающих субъектов
Сервис [временных меток](https://aptos.dev/guides/move-guides/move-on-aptos#:~:text=A-,timestamp%20service,-that%20provides%20a), который предоставляет монотонно увеличивающиеся часы, сопоставленные с реальным текущим временем unixtime

И многое другое в скором времени...

## Ключевые понятия в Move

- Данные должны храниться в учетной записи, которая ими владеет, а не в учетной записи, которая опубликовала модуль.
- Поток данных должен иметь минимальные ограничения с акцентом на удобство использования экосистемы
- Предпочтение статической безопасности типов перед безопасностью во время выполнения через generics.
- `signer` должен быть обязан ограничивать доступ к добавлению или удалению активов в учетной записи, если это явно не указано

**Владение данными**

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

В Solidity данные хранятся в namespace учетной записи, создавшей контракт. Обычно это представлено в виде карты адреса к значению или идентификатора экземпляра к адресу владельца.

В Solana данные хранятся в отдельной учетной записи, связанной с контрактом.

В Move данные могут храниться в учетной записи владельца модуля, но это создает проблему неоднозначности владения и подразумевает две проблемы:

Это делает право собственности неоднозначным, так как у актива нет ресурса, связанного с владельцем.
Создатель модуля берет на себя ответственность за срок службы этих ресурсов, например, аренду, восстановление и т.д.
Что касается первого пункта, то, размещая активы в доверенных ресурсах внутри учётной записи, владелец может гарантировать, что даже злонамеренно запрограммированный модуль не сможет модифицировать эти активы. В Move мы можем запрограммировать стандартную структуру и интерфейс orderbook, что позволит приложениям, созданным на его основе, не получить черный доступ к учетной записи или ее записям в orderbook.

Сравните две следующие стратегии хранения coin:

В следующем случае coin размещаются на одной учетной записи с указанием владельца в виде индекса:

```javascript 

struct CoinStore has key {
    coins: table<address, Coin>,
}
```
Вместо этого предпочтителен подход, при котором coins хранятся на учетной записи:

```javascript
struct CoinStore has key {
    coin: Coin,
}
```
Это делает право собственности очевидным.

**Поток данных**

Поток данных должен иметь минимальные ограничения с акцентом на удобство использования экосистемы

Активы можно запрограммировать так, чтобы они были полностью ограничены в пределах модуля, сделав это таким образом, чтобы ни один интерфейс никогда не представлял struct в форме значения, а вместо этого предоставлял только функции для манипулирования данными, определенными в модуле. Это ограничивает доступность данных только в пределах модуля и делает их невозможными для экспорта, что, в свою очередь, препятствует взаимодействию с другими модулями. В частности, можно представить контракт на покупку, который принимает в качестве входных данных некоторые `Coin<T>` и возвращает `Ticket`. Если `Coin<T>` определен только внутри модуля и не экспортируется за его пределы, то приложения для этого `Coin<T>` ограничены тем, что определено в модуле.

Сравните следующие две функции реализации перевода Coin с помощью `deposit` и `withdraw`:

```javascript
public fun transfer<T>(sender: &signer, recipient: address, amount: u64) {
    let coin = withdraw(&signer, amount);
    deposit(recipient, coin);
}
```
Ниже приведены ограничения на то, где Coin можно использовать за пределами модуля:

```javascript
fun withdraw<T>(account: &signer, amount: u64): Coin<T>
fun deposit<T>(account: address, coin: Coin<T>)
```
Добавив публичные доступные устройства для `withdraw` и `deposit` средств, монету можно вывести за пределы модуля, использовать другими модулями и вернуть в модуль:

```javascript
public fun withdraw<T>(account: &signer, amount: u64): Coin<T>
public fun deposit<T>(account: address, coin: Coin<T>)
```

**Типы безопасности**

В Move, учитывая конкретную структуру, скажем, A, различные варианты можно различать двумя способами:

Внутренние идентификаторы, такие как [GUID](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/guid.move).
Generics, такие как `A<T>`, где `T` - это другая структура

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

Generics позволяют создавать совершенно отдельные типы и ресурсы, а также интерфейсы, которые ожидают эти типы. Например, orderbook может заявить, что он ожидает две валюты для всех заказов, но одна из них должна быть фиксированной, например, `buy<T>(coin: Coin<Aptos>)`: Coin<T>. Это явно указывает, что пользователь может купить любой coin `<T>`, но должен заплатить за нее `coin<Aptos>`.

Сложность с generic возникает, когда желательно хранить данные на `T`. Move не поддерживает статическую передачу на generics, поэтому в функции типа `create<T>(...) : Coin<T>`, `T` должен быть либо фантомным типом, т.е. использоваться только как параметр типа в `Coin`, либо должен быть указан как вход в `Create`. На `T` не могут быть вызваны функции, такие как `T::function`, даже если каждый `T` реализует эту функцию.

Кроме того, для структур, которые могут создаваться массово, generics приводит к созданию множества новых хранилищ и ресурсов, связанных с отслеживанием данных и эмиссией, что, возможно, является меньшей проблемой.

В связи с этим мы сделали трудный выбор и создали два стандарта "`Token`": один для токенов, связанных с валютой, под названием `Coin`, а другой для токенов, связанных с активами или NFT, под названием `Token`. `Coin` использует статическую безопасность типов через generics, но является гораздо более простым контрактом. В то время как `Token` использует динамическую безопасность типов через свой собственный универсальный идентификатор и отказывается от generics из-за сложности, которая влияет на эргономику его использования.

**Доступ к данным**

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

В Move модуль может определить, как ресурсы могут быть доступны и их содержимое может быть изменено независимо от присутствия подписавшего их владельца учетной записи. Это означает, что программист может случайно создать ресурс, позволяющий другим пользователям произвольно вставлять или удалять активы из учетной записи другого пользователя.

В нашей разработке есть несколько примеров того, где мы разрешили доступ, а где предотвратили его:

- Токен не может быть напрямую вставлен в учетную запись другого пользователя, если только у него уже не имеется часть этого токена.
- TokenTransfers позволяет пользователю напрямую клеймить токен, хранящийся в ресурсе другого пользователя, эффективно используя список контроля доступа для получения такого доступа.
- В Coin пользователь может напрямую переводить на учетную запись другого пользователя, если у него есть ресурс для хранения этих coin

Менее тщательная работа над нашими токенами могла позволить пользователям давать airdrop токены  непосредственно в учетную запись другого пользователя, что добавило бы дополнительное хранилище в их учетные записи, а также сделало бы их владельцами контента, который они сначала не одобрили.

В качестве конкретного примера вернемся к предыдущему случаю Coin с функцией withdraw. Если бы функция withdraw вместо этого была определена следующим образом:

```javascript
public fun withdraw<T>(account: address, amount: u64): Coin<T>
```
любой сможет удалить coins с `account`

## Дополнительные источники

- [The Move Book
](https://move-language.github.io/move/)
- [Документация Aptos Framework](https://aptos.dev/guides/move-guides/move-on-aptos#key-concepts-in-move:~:text=The%20Aptos%20Framework%20documentation)
- [Начало работы](https://aptos.dev/guides/move-guides/move-on-aptos#key-concepts-in-move:~:text=Aptos%20Framework%20documentation-,Getting%20Started,-Edit%20this%20page)