Move в сети Aptos

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

Что такое Move?

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

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

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

Сравнение с другими VM

Aptos / MoveSolana / SeaLevelEVM

Хранение данных

В учетной записи владельца

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

В учетной записи, связанной со смарт-контрактом

Параллелизация

Способен динамически делать вывод типов параллелизации во время выполнения в сети Aptos.

Требует указать в транзакции все учетные записи и программы, к которым осуществляется доступ

В настоящее время серийно ничего не производится

Безопасность транзакций

Порядковый номер

Уникальность транзакций + запоминание транзакций

Одноразовые номера, схожие с порядковыми номерами

Безопасность типов

Структуры модулей и дженерики

Структуры программы

Типы контрактов

Вызов функции

Статическая диспетчеризация не на дженериках

Статическая диспетчеризация

Динамическая диспетчеризация

Функциональные возможности Aptos Move

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

Характеристики адаптера Aptos Move включают в себя:

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

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

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

Платформа Aptos снабжена многими полезными библиотеками:

  • Token standard, позволяющая создавать NFT и другие расширенные токены без публикации смарт-контракта.

  • Coin standard, которая позволяет создавать безопасные по типу монеты путем публикации тривиального модуля.

  • Итерируемая таблица, которая позволяет просматривать все записи в таблице iterable Table.

  • Среда стейкинга и делегирования

  • Сервис type_of , чтобы во время выполнения идентифицировать адрес, модуль и имя структуры данного типа.

  • Система мультиподписи, позволяющая использовать несколько субъектов подписания signer

  • Сервис временных меток timestamp service, который предоставляет монотонно увеличивающиеся часы, сопоставленные с реальным текущим Unix-временем.

И скоро будет намного больше...

Ключевые принципы Move

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

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

  • Предпочтение статической безопасности типов над безопасностью во время выполнения через дженерики.

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

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

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

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

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

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

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

  2. Создатель модуля берет на себя ответственность за срок службы этих ресурсов, например, за аренду, рекламацию и т.д.

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

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

Следующая команда помещает монеты на один аккаунт, право собственности на который указывается индексом:

struct CoinStore has key {
    coins: table<address, Coin>,
}

Вместо этого предпочтителен подход, при котором монеты хранятся в аккаунте:

struct CoinStore has key {
    coins: Coin,
}

Это делает право собственности явно определенным.

Поток данных

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

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

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

public fun transfer<T>(sender: &signer, reciepient: address, amount: u64) {
    let coin = withdraw(&signer, amount);
    deposit(reciepient, coin);
}

Следующие лимиты, где Coin можно использовать вне модуля:

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

Добавляя общедоступные средства доступа для вывода и депозита, монета может быть выведена за пределы модуля, использована другими модулями и возвращена в модуль:

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

Безопасность типов

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

  • Внутренние идентификаторы, такие как GUIDs

  • Дженерики такие как A<T>, где T - другая структура

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

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

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

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

По этой причине мы сделали сложный выбор, создав два стандарта «токенов», один для токенов, связанных с валютой, называемой Coin а другой для токенов, связанных с активами или NFT с названием Token. Coin lиспользует статическую безопасность типов через дженерики, но это гораздо более простой контракт. В то время какToken lиспользует безопасность динамического типа с помощью собственного универсального идентификатора и избегает дженериков из-за сложности, которая влияет на эргономику его использования.

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

  • Подписант обязан ограничить доступ к добавлению или удалению активов в учетной записи, если это явно не ясно.

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

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

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

  • TokenTransfers позволяет пользователю прямо запрашивать токен, хранящийся в ресурсе другого пользователя, эффективно используя список управления доступом для получения этого доступа.

  • В Coin пользователь может напрямую совершать трансфер на счет другого пользователя, если у него есть ресурс для хранения этой монеты.

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

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

public fun withdraw<T>(account: address, amount: u64): Coin<T>

любой смог бы снять монеты с account.

Дополнительные ресурсы

Last updated