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

1. Введение

В последние недели и месяцы было много шума вокруг Aptos и Sui, новых высокопроизводительных L1 и смарт-контрактов Move. язык программирования, который является неотъемлемой частью этих новых цепочек. Некоторые разработчики уже агрессивно переходят на Move, клянясь, что это будущее разработки смарт-контрактов, в то время как другие немного осторожны, полагая, что Move — это просто еще один язык программирования смарт-контрактов, который принципиально не предлагает многого по сравнению с существующими моделями программирования. Криптоинвесторы также задаются вопросом, что такого особенного в этих новых L1 и как они сочетаются с Solana, в настоящее время основным игроком в высокопроизводительной категории L1, которая, в частности, использует Rust в качестве основы для программирования смарт-контрактов.

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

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

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

Чтобы выделить особенности Move, я буду сравнивать Solana/Rust с Sui/Move. Основная причина, по которой я это делаю, заключается в том, что гораздо легче что-то понять, когда вы сравниваете это с чем-то, с чем вы уже знакомы, а не пытаетесь понять это самостоятельно. Важно отметить, что существуют и другие варианты Move, такие как Aptos Move, которые делают некоторые вещи немного по-другому. Цель этой статьи не в том, чтобы обсуждать нюансы между различными вариантами Move, а в том, чтобы показать общие преимущества Move и его сравнение с моделью программирования Solana, поэтому для простоты я решил использовать только один вариант (Sui Move) в этой статье. Из-за этого некоторые концепции Move, которые я представляю в этой статье (а именно, объекты и связанные с ними функции), применимы только к варианту Sui Move, а не к другим. Хотя другие варианты Move не обязательно имеют эти концепции, они достигают той же функциональности, используя другие механизмы (например, глобальное хранилище). Но даже в этом случае все основные преимущества Move, обсуждаемые в этой статье, применимы ко всем интеграциям Move (которые изначально поддерживают байт-код Move), включая Aptos. Причина, по которой я выбрал именно Sui, заключается в том, что я лучше знаком с ним. с ним, а также я чувствую, что это немного более интуитивно понятно и легче представить в форме статьи.

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

2. Модель программирования Солана

Чтобы в полной мере оценить то, о чем говорится в этой статье, необходимо некоторое знакомство с моделью программирования Solana. Если вы не знакомы с моделью программирования Соланы, я рекомендую прочитать мою статью о программировании смарт-контрактов Соланы, которая охватывает все концепции, необходимые для следования этой статье.

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

В Solana программы (смарт-контракты) не имеют состояния в том смысле, что они не могут самостоятельно получить доступ (чтение или запись) к какому-либо состоянию, сохраняющемуся во время транзакций. Чтобы получить доступ к состоянию или сохранить его, программам необходимо использовать учетные записи. Каждая учетная запись имеет уникальный адрес (открытый ключ из пары ключей Ed25519) и может хранить произвольные данные.

Мы можем думать о пространстве учетной записи Solana как о глобальном хранилище ключей и значений, где ключи — это адреса учетных записей (pubkeys), а значения — данные учетных записей. Затем программы работают с этим хранилищем ключей и значений, читая и изменяя его значения.

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

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

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

Программы могут вызывать другие программы с помощью вызовов CPI. Эти вызовы работают почти так же, как и вызовы, поступающие со стороны клиента: вызывающая программа должна указать учетные записи, к которым будет обращаться вызываемая программа, а вызываемая программа будет выполнять все те же проверки ввода, как если бы он был вызван из клиента (он не доверяет вызывающей программе).

Учетные записи PDA — это особый вид учетных записей, который позволяет программам предоставлять подписи учетных записей, не владея закрытыми ключами и не храня их. КПК гарантируют, что только программа, для которой был создан КПК, может создать для него подпись (и никакие другие пользователи и программы). Это полезно, когда программе необходимо взаимодействовать с другой программой через вызовы CPI и предоставлять полномочия (например, для реализации хранилища). КПК гарантируют, что никто, кроме программы, не имеет прямого доступа к ресурсам программы. КПК также можно использовать для создания учетных записей по детерминированным адресам.

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

В некотором смысле вы можете думать о программах Solana как о программах в операционной системе, а об учетных записях как о файлах, где каждый может свободно запускать любую программу и даже развертывать свои собственные программы. Когда программы (смарт-контракты) запущены, они будут читать и записывать в файлы (учетные записи). Все файлы доступны всем программам для чтения, но только те программы, которые имеют разрешение на владение файлом, могут записывать в него. Программы также могут выполнять другие программы, но они никоим образом не доверяют друг другу — независимо от того, кто запускал программу, она должна предполагать, что входные данные являются потенциально враждебными. Поскольку эта ОС доступна для всех по всему миру, в программы добавлена ​​встроенная поддержка проверки подписи, чтобы включить функции полномочий и владения для пользователей… Это не идеальная аналогия, но забавная.

3. Модель программирования перемещения

В Move смарт-контракты публикуются в виде модулей. Модули состоят из функций и пользовательских типов (структур). Структуры состоят из полей, которые могут быть примитивными типами (u8, u64, bool…) или другими структурами. Функции могут вызывать другие функции — либо в том же модуле, либо в других модулях, если они сделаны общедоступными.

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

3.1. Объекты

Прежде чем мы продолжим, важно отметить, что следующее понятие объектов специфично для Sui варианта Move, и все может немного отличаться в других интеграциях Move (например, Aptos или Diem/core Move). Тем не менее, в других вариантах Move есть похожие решения, которые достигают того же (сохранение состояния), которые не слишком отличаются. Основная причина, по которой я представляю здесь объекты Sui, заключается в том, что примеры кода, приведенные далее в статье, основаны на Sui-варианте Move, а также потому, что объекты немного более интуитивно понятны, чем, например. механизм глобального хранения в ядре Move. Но важно то, что все основные преимущества Move, обсуждаемые в этой статье, применимы ко всем интеграциям Move (которые изначально поддерживают байт-код Move), включая Aptos. Чтобы узнать, чем отличаются варианты Sui и Aptos, см. статья Почему мы создали Sui Move и страница документации Move на Aptos — хорошее начало.

Объекты — это экземпляры структур, которые сохраняются средой выполнения и сохраняют свое состояние во всех транзакциях.

Есть три разных типа объектов (в Sui):

  • принадлежащие объекты
  • общие объекты
  • неизменяемые объекты

Собственные объекты — это объекты, принадлежащие пользователям. Только пользователь, которому принадлежит объект, может использовать его в транзакции. Метаданные владельца полностью прозрачны и обрабатываются средой выполнения. Он реализован с использованием криптографии с открытым ключом — каждый принадлежащий объект связан с открытым ключом (хранящимся в метаданных объекта во время выполнения), и каждый раз, когда вы хотите использовать объект в транзакции, вам необходимо предоставить соответствующую подпись (сейчас поддерживается Ed25519). с поддержкой мультиподписи ECDSA и K-of-N в ближайшее время).

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

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

Модель программирования Move очень интуитивно понятна и проста. Каждый смарт-контракт представляет собой модуль и состоит из определений функций и структур. Структуры создаются в функциях и могут передаваться в другие модули через вызовы функций. Чтобы структура сохранялась между транзакциями, мы превращаем ее в объект, которым можно владеть, совместно использовать или неизменяемый (конкретно для Sui, это немного отличается в других вариантах Move). Вот и все!

4. Безопасное перемещение

Итак, мы видели это в Move:

  • вы можете передать любой объект, которым вы владеете (или которым вы поделились), в любую функцию в любом модуле
  • любому разрешено публиковать (потенциально враждебный) модуль
  • нет представления о модулях, владеющих структурами, которые давали бы модулю-владельцу единоличное право изменять его, как в случае с учетными записями Соланы — структуры могут перетекать в другие модули и также быть встроены в другие структуры.

Теперь вопрос в том, что делает это безопасным? Что мешает кому-то опубликовать враждебный модуль, взять общий объект (например, пул AMM) и отправить его в враждебный модуль, который затем начнет истощать его средства?

В Solana существует понятие владения учетной записью, при котором только программа, которой принадлежит учетная запись, может ее изменять. Но в Move нет понятия модулей, владеющих объектами, и вы можете отправлять объекты в произвольные модули — не только ссылку на объект, но и весь объект целиком по значению. И никаких специальных проверок не делается. средой выполнения, чтобы убедиться, что этот объект не был незаконно изменен во время прохождения через ненадежный модуль. Так что же обеспечивает безопасность этого объекта? Где гарантия, что объект не будет использован ненадежным кодом?

Что ж, в этом и заключается новизна Move… Поговорим о ресурсах.

4.1. Структуры

Определение типа структуры в значительной степени соответствует вашим ожиданиям:

struct Foo {
  x: u64,
  y: bool
}

Пока все хорошо — именно так вы бы определили структуру и в Rust. Но есть что-то уникальное в структурах в Move, а именно то, что модули в Move имеют гораздо больший контроль над тем, как их типы могут и не могут использоваться, чем обычные языки программирования. Структура, определенная в приведенном выше фрагменте кода, будет иметь следующие ограничения:

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

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

Теперь кажется, что с этими ограничениями мы потеряли большую гибкость. И это правда — работа с такими структурами была бы очень громоздкой в ​​обычном программировании, но на самом деле это именно то, чего мы хотим в смарт-контрактах. Разработка смарт-контрактов — это, в конце концов, программирование цифровых активов (ресурсов). И если вы посмотрите на структуру, описанную выше, это именно то, чем она является — это ресурс. Его нельзя произвольно создать из воздуха, его нельзя воспроизвести и нельзя случайно разрушить. Так что это правда, что мы потеряли некоторую гибкость здесь, но гибкость, которую мы потеряли, — это именно та гибкость, которую мы хотим потерять. Это делает работу с ресурсами интуитивно понятной и безопасной.

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

struct Foo has key, store, copy, drop {
  id: UID,
  x: u64,
  y: bool
}

Вот что они делают:

  • ключ — позволяет структуре стать объектом (конкретно для Sui, ядро ​​Move немного отличается). Как объяснялось ранее, объекты сохраняются и, в случае принадлежащих им объектов, требуют подписи пользователя для использования в вызове смарт-контракта. При использовании ключевой возможности первым полем структуры должен быть идентификатор объекта с типом UID. Это даст ему глобально уникальный идентификатор, который можно использовать для ссылки на него.
  • хранить — это позволяет встроить структуру как поле в другую структуру.
  • копировать — позволяет произвольно копировать/клонировать структуру из любого места.
  • drop — позволяет произвольно уничтожать структуру из любого места.

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

4.2. Монета

Чтобы проиллюстрировать это немного лучше, давайте рассмотрим в качестве примера тип Coin. Монета реализует функциональность, подобную токену ERC20 / SPL, в Sui и является частью библиотеки Sui Move. Вот как это определяется:

// coin.move
struct Coin<phantom T> has key, store {
    id: UID,
    balance: Balance<T>
}
// balance.move
struct Balance<phantom T> has store {
    value: u64
}

Вы можете найти полную реализацию модуля в кодовой базе Sui (ссылка).

Тип Coin имеет ключ и возможности хранения. Ключ означает, что его можно использовать как объект. Это позволяет пользователям владеть монетами напрямую (как объект верхнего уровня). Когда у вас есть монета, никто, кроме вас, не может даже ссылаться на нее в транзакции (не говоря уже о том, чтобы использовать ее). Сохранить означает, что монета может быть встроена как поле в другую структуру. Это полезно для компоновки.

Поскольку возможности выбросить нет, монета не может быть случайно сброшена (уничтожена) в функции. Это очень приятная функция — она означает, что вы не можете случайно потерять монету. Если вы реализуете функцию, которая получает Coin в качестве аргумента, то к концу функции вам нужно что-то сделать с ней явно — либо передать ее пользователю, либо встроить в другой объект, либо отправить в другую функцию через вызов (который опять же должен что-то с ним делать). Конечно, монету можно уничтожить, вызвав функцию coin::burn в модуле монеты, но делать это нужно целенаправленно (случайно не получится).

Отсутствие возможности клонирования означает, что никто не может продублировать монету и, таким образом, создать новую поставку из воздуха. Создание нового предложения может быть выполнено с помощью функции coin::mint, и ее может вызвать только владелец объекта возможностей казначейства для этой монеты (этот объект изначально передается создателю валюты).

Также обратите внимание, что благодаря дженерикам каждая отдельная монета будет иметь свой собственный тип. А так как две монеты можно сложить вместе только через функцию coin::join (а не путем прямого доступа к их полям), то это означает суммировать значения монет разных типов (монета A + монета B) просто невозможно — нет функции этой подписи. Система типов защищает нас от неправильного учета.

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

4.3. Проверка байт-кода

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

Так что же гарантирует, что эти правила соблюдаются произвольными модулями? Что мешает кому-то загрузить модуль со специально созданным байт-кодом, который, например, получить объект Coin, а затем перейти к обходу этих правил, напрямую изменив его внутренние поля? Делая это, вы можете незаконно увеличить количество монет, которые у вас есть. Один только синтаксис байт-кода позволяет это.

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

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

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

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

5. Солана против движения

Теперь, когда мы увидели, как работает программирование Move и что делает его принципиально безопасным, давайте более подробно рассмотрим, какие последствия это имеет для программирования смарт-контрактов с точки зрения компонуемости, эргономики и безопасности. Здесь я сравню разработку Move/Sui с EVM и Rust/Solana/Anchor, чтобы лучше понять, что предлагает модель программирования Move.

5.1. Быстрое кредитование

Начнем с примера флэш-кредитования. Флэш-кредиты — это тип кредита в DeFi, при котором сумма кредита должна быть погашена в рамках той же транзакции, в которой она заимствована. Основное преимущество этого заключается в том, что, поскольку транзакция является атомарной, кредит может быть полностью необеспеченным. Это можно использовать, например. для арбитража между активами без необходимости иметь основную сумму для выполнения арбитража.

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

EVM имеет динамическую диспетчеризацию, поэтому ее можно реализовать с помощью повторного входа следующим образом:

  • Пользователь флэш-кредита создает и загружает собственный смарт-контракт, который при вызове передает управление смарт-контракту флэш-кредита, вызывая его.
  • Затем смарт-контракт мгновенного кредита отправит запрошенные суммы кредита в пользовательский смарт-контракт и вызовет функцию обратного вызова executeOperation() в пользовательском смарт-контракте.
  • Затем пользовательский смарт-контракт будет использовать полученные кредитные суммы для выполнения желаемых операций (например, арбитража).
  • После того, как пользовательский смарт-контракт завершит свои операции, ему необходимо вернуть заемную сумму обратно в смарт-контракт мгновенного кредита.
  • При этом executionOperation() пользовательского смарт-контракта завершится, и управление будет возвращено смарт-контракту мгновенного кредита, который проверит, правильно ли возвращена сумма кредита.
  • Если пользовательский смарт-контракт не вернет заемные суммы правильно, вся транзакция завершится неудачно.

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

Solana работает лучше, так как не допускает повторного входа. Но без возможности повторного входа и смарт-контракта мгновенного займа, вызывающего обратный вызов пользовательского смарт-контракта, как реализовать быстрые займы на Solana? Что ж, это возможно благодаря инструктивному самоанализу. В Solana каждая транзакция состоит из нескольких инструкций (вызовов смарт-контрактов), и из любой инструкции вы можете проверить другие инструкции (их идентификатор программы, данные инструкции и учетные записи), присутствующие в той же транзакции. Это позволяет реализовать флэш-кредит следующим образом:

  • Смарт-контракт мгновенного займа реализует инструкции borrow и repay
  • Пользователи создают транзакцию мгновенного кредита, объединяя вызовы инструкций borrow и repay в одной транзакции. Инструкция borrow при выполнении проверяет, запланирована ли инструкция repay позже в той же транзакции, используя самоанализ инструкций. Если вызов инструкции repay отсутствует или недействителен, транзакция завершится ошибкой на этом этапе.
  • Между вызовами borrow и repay заемные средства могут быть использованы произвольно любыми другими инструкциями, находящимися между ними.
  • В конце транзакции вызов инструкции repay вернет средства в смарт-контракт флэш-кредитора (наличие этой инструкции проверяется с помощью самоанализа в инструкции borrow)

Для любопытных есть прототип реализации этого (ссылка).

Решение работает достаточно хорошо, но все же не идеально. Интроспекция инструкций — это своего рода особый случай, а не то, что обычно используется в Солане, поэтому ее использование имеет накладные расходы как с точки зрения количества концепций, которые необходимо освоить разработчику, так и с точки зрения технических особенностей самой реализации, поскольку их несколько. нюансы, которые необходимо тщательно продумать. Существует также техническое ограничение — поскольку инструкция repay должна присутствовать в транзакции статически, невозможно вызвать repay динамически во время выполнения транзакции через вызов CPI. Вряд ли это нарушает условия сделки, но несколько ограничивает гибкость кода при интеграции с другими смарт-контрактами, а также увеличивает сложность на стороне клиента.

Редактировать: Есть еще один подход к внедрению флэш-кредитования без самоанализа инструкций — вы можете сделать так, чтобы инструкция по флэш-кредитованию в смарт-контракте кредитования выполняла вызов CPI в ваш произвольный смарт-контракт со средствами, а затем после возврата вызова CPI он проверит, правильно ли возвращены средства. Такой подход реализован программой SPL Lending (ссылка). Однако здесь есть другая проблема: не существует универсального способа агрегировать (объединить) несколько кредитов в одном вызове.

Move также запрещает динамическую отправку и повторный вход, но, в отличие от Solana, имеет очень простое и естественное решение для флэш-кредитования. Система линейных типов Move позволяет создавать структуры, которые гарантированно будут использоваться ровно один раз во время выполнения транзакции. Это так называемый шаблон «Горячая картошка» — структура, не имеющая возможностей key, store, drop или clone. Модуль, реализующий такой шаблон, обычно имеет одну функцию, которая создает экземпляр структуры, и другую, которая ее уничтожает. Поскольку структура горячая картошка не имеет возможности удаления, ключа или сохранения, гарантируется, что ее функция уничтожить будет вызвана для ее использования. Несмотря на то, что мы можем передать его любому количеству других функций в любом модуле, в конечном итоге он должен оказаться в функции уничтожить. Это просто потому, что нет другого способа избавиться от него, и верификатор требует, чтобы с ним что-то было сделано к концу транзакции (его нельзя просто произвольно отбросить, так как нет возможности отбрасывания).

Давайте посмотрим, как это можно использовать для реализации мгновенного кредитования:

  • Смарт-контракт флэш-кредитования реализует структуру Receipt «горячая картошка»
  • Когда ссуда предоставляется путем вызова функции loan, она отправляет вызывающей стороне два объекта — запрошенные средства (монету) и квитанцию, которая представляет собой запись суммы ссуды, подлежащей погашению.
  • Затем заемщик может использовать полученные средства для желаемых операций (например, арбитража).
  • После того, как заемщик завершит свои предполагаемые операции, ему необходимо вызвать функцию repay, которая получит заемные средства и квитанцию ​​​​в качестве аргументов. Эта функция гарантированно будет вызываться в той же транзакции, поскольку у вызывающей стороны нет другого способа избавиться от экземпляра Receipt (его нельзя удалить или внедрить в другой объект, это подтверждается верификатором).
  • Функция repay проверяет, была ли возвращена правильная сумма, считывая информацию о кредите, встроенную в квитанцию.

Пример реализации этого можно найти здесь.

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

Функциональность полностью реализована с использованием основных языковых примитивов, а реализация Move не страдает от накладных расходов на интеграцию, как реализация Solana с транзакциями, которые необходимо специально создавать. Кроме того, никакая сложность не передается на сторону клиента.

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

5.2. Блокировка управления монетным двором

Чтобы еще больше подчеркнуть преимущества модели программирования Move, я реализовал смарт-контракт «Mint Authority Lock» как в Solana (Anchor), так и в Sui Move, чтобы провести сравнение.

Что делает смарт-контракт «Mint Authority Lock», так это то, что он расширяет функциональность чеканки токенов, а не только одну, позволяя нескольким сторонам (органам власти) из белого списка чеканить токены. Необходимая функциональность смарт-контракта следующая (применимо как к реализациям Solana, так и к Sui):

  • Первоначальный орган по чеканке токенов создает «блокировку чеканки», которая позволит нашему смарт-контракту регулировать чеканку токенов. Вызывающий становится администратором блокировки монетного двора.
  • Затем администратор может создать дополнительные полномочия для чеканки блокировки, которые могут быть предоставлены другим сторонам, и позволит им использовать блокировку для чеканки токенов, когда им заблагорассудится.
  • Каждый орган монетного двора имеет дневной лимит на количество токенов, которые он может чеканить.
  • Администратор может заблокировать (и разблокировать) любой орган монетного двора в любое время.
  • Возможности администратора могут быть переданы другому лицу.

Этот смарт-контракт можно использовать, например. предоставить возможности монетного двора для токена какому-либо другому пользователю или смарт-контракту, в то время как первоначальный орган монетного двора (администратор) по-прежнему сохраняет контроль над монетным двором. Без этого нам пришлось бы передать полный контроль над монетным двором другой стороне, что не идеально, поскольку нам пришлось бы доверять ей, чтобы не злоупотреблять ею. И дать разрешение нескольким сторонам было бы невозможно.

Полную реализацию этих смарт-контрактов можно найти здесь (Солана) и здесь (Суи).

Примечание. Не используйте этот код в рабочей среде! Это пример кода, предназначенный только для образовательных целей. Хотя я проверил его функциональность, я не проводил тщательного аудита или тестирования безопасности.

Теперь давайте посмотрим на код и посмотрим, чем отличаются реализации. Вот параллельные скриншоты кода для полных реализаций Solana и Sui этого смарт-контракта:

Что сразу бросается в глаза, так это то, что при той же функциональности реализация Solana более чем в два раза больше по размеру по сравнению с Sui (230 LOC против 104). Это уже большое дело, поскольку меньшее количество кода обычно означает меньшее количество ошибок и более короткое время разработки.

Откуда же берутся эти лишние строки в Solana? Если мы внимательнее посмотрим на код Solana, то сможем разделить его на две части — реализация инструкций (логика смарт-контракта) и проверки аккаунта. Реализация инструкций в некоторой степени соответствует тому, что мы имеем на Sui — 136 LOC против 104 на Sui. Дополнительные строки могут быть отнесены к шаблону двух вызовов CPI (~ 10 LOC каждый). Однако наиболее существенное отличие связано с проверкой учетной записи (раздел, отмеченный красным на снимке экрана выше), который требуется (фактически критично) в Solana, но не требуется в Move. Проверки аккаунта составляют ~40% этого смарт-контракта (91 LOC).

Перемещение не требует проверки аккаунта. И это не только выгодно из-за сокращения LOC. Устранение необходимости выполнять проверки учетной записи очень важно, потому что правильно реализовать эти проверки оказалось очень сложно, и если вы сделаете хотя бы одну ошибку, это часто приведет к критическим уязвимостям и потере средств пользователей.На самом деле некоторые из крупнейших (с точки зрения потерянных средств пользователей) эксплойтов смарт-контрактов Solana были атаки с подменой аккаунта, вызванные ненадлежащей проверкой аккаунта:

Ясно, что избавиться от этих чеков было бы большим делом.

Как же Move может обходиться без этих проверок, сохраняя при этом такую ​​же безопасность? Давайте подробнее рассмотрим, что на самом деле делают проверки. Вот проверки аккаунта, необходимые для инструкции mint_to (уполномоченный вызывает это для чеканки токенов через блокировку):

Всего 6 проверок (выделено красным):

  1. Проверяет, что предоставленная учетная запись блокировки принадлежит этому смарт-контракту и имеет тип MintLock. Необходимо передать блокировку, потому что она используется для вызова CPI к программе токенов для чеканки (она хранит полномочия).
  2. Проверяет, принадлежит ли предоставленная учетная запись управления монетным двором предоставленной блокировке. Учетная запись авторитета монетного двора содержит состояние авторитета (его открытый ключ, был ли он забанен и т. д.)
  3. Проверяет, что инициатор инструкции владеет необходимыми ключами для этого органа (требуемый орган подписал транзакцию).
  4. Необходимо передать учетную запись назначения токена, потому что программа токена изменит ее в вызове CPI (добавит баланс). Проверка монетного двора здесь не является строго необходимой, потому что, если в вызове CPI будет передана неправильная учетная запись, вызов CPI завершится ошибкой, но, тем не менее, проверка является хорошей практикой.
  5. Похож на 4.
  6. Проверяет правильность передачи учетной записи Token Program.

Мы видим, что проверки аккаунта (в этом примере) попадают в следующие пять категорий:

  • проверки владения аккаунтом (1, 2, 4, 5)
  • проверки типа аккаунта (1, 2, 4, 5)
  • проверка экземпляра учетной записи (был ли передан правильный экземпляр определенного типа учетной записи) (2, 5)
  • проверка подписи аккаунта (3)
  • проверка адреса учетной записи программы (6)

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

Однако в Move не требуется никаких проверок аккаунта или чего-то подобного. У нас есть только сигнатура функции:

Функция mint_balance требует всего четыре аргумента. Из этих четырех только lock и cap представляют объекты (что-то вроде счетов).

Так как же возможно, что в Solana нам нужно объявить 6 учетных записей и также вручную реализовать для них различные проверки, а в Move нам нужно передать только 2 объекта и никаких явных проверок не нужно?

Ну, в Move некоторые из этих проверок выполняются средой выполнения прозрачно, некоторые из них выполняются статически во время компиляции верификатором, а некоторые из них просто не нужны по конструкции. Давайте посмотрим:

  • проверка владения учетной записью — не требуется из-за дизайна системы типов Move. Структура Move может быть изменена только с помощью функций, определенных в ее модуле, и никогда напрямую. Проверка байт-кода гарантирует, что экземпляры структуры могут свободно перетекать в ненадежный код (другие модули) без незаконных изменений.
  • проверка типа учетной записи — не требуется, поскольку типы перемещения существуют в смарт-контрактах. Определения типов встроены в двоичные файлы модулей (это то, что публикуется в блокчейне и выполняется виртуальной машиной). Верификатор проверит, что правильные типы передаются, когда наши функции вызываются во время компиляции/публикации.
  • проверки экземпляра учетной записи — в Move (а иногда и в Solana) вы делаете это в теле функции. В этом конкретном примере в этом нет необходимости, поскольку параметр универсального типа T для типов аргументов lock и cap обеспечивает правильное соответствие передаваемого объекта cap (возможность/полномочия чеканки) его блокировке (может быть только одна блокировка для каждого типа монеты T).
  • проверки подписи аккаунта — мы не занимаемся подписями напрямую в Sui. Объекты могут принадлежать пользователям. Полномочия на чеканку предоставляются путем владения объектом возможностей чеканки (созданным администратором). Передача ссылки на этот объект в функцию mint_balance позволит нам чеканить. Принадлежащие объекты могут использоваться в транзакции только их владельцами. Другими словами, проверка подписи объекта выполняется средой выполнения прозрачно.

По сути, Move использует проверку байт-кода, чтобы сделать модель программирования более естественной для программирования цифровых активов. Модель Соланы вращается вокруг владения учетной записью, подписей, вызовов CPI, КПК и т. д. Но если мы сделаем шаг назад и подумаем об этом, мы увидим, что мы на самом деле не хотим быть дело с тех. Они не имеют ничего общего с цифровыми активами сами по себе — скорее мы должны их использовать, потому что это позволяет нам реализовать желаемую функциональность в рамках модели программирования Соланы.

В Solana, поскольку нет проверки байт-кода, чтобы гарантировать безопасность типа или ресурса на более детальном уровне, вы не можете позволить какой-либо программе изменять любую учетную запись, поэтому необходимо ввести понятие владения учетной записью. По тем же причинам (отсутствие безопасности типов/ресурсов при вызовах программ) не существует понятия принадлежащих пользователям объектов, которые могут входить и выходить из программ, вместо этого мы имеем дело с подписями учетных записей для подтверждения полномочий. А так как иногда программам тоже нужно уметь ставить подписи аккаунтов, у нас есть КПК…

Несмотря на то, что вы можете иметь тот же тип кросс-программы и безопасность ресурсов в Solana, что и в Move, вам нужно реализовать это вручную, используя строительные блоки низкого уровня (подписи учетных записей, КПК…). В конечном итоге мы используем низкоуровневые примитивы для моделирования программируемых ресурсов (линейных типов). Вот что такое проверки учетных записей — они связаны с ручным внедрением безопасности типов и моделированием ресурсов.

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

5.3. Ограничения компонуемости Solana

Я хочу привести еще один пример, который выделит некоторые болевые точки с возможностью компоновки смарт-контрактов в Solana.

На примере Mint Authority Lock мы видели, что нам требуется объявить намного больше входных данных в Solana по сравнению с Sui (6 учетных записей в Solana против 2 объектов в Sui для вызова mint_to). Очевидно, что иметь дело с 6 учетными записями более обременительно, чем с 2 объектами, особенно если учесть, что нам также необходимо реализовать проверку учетных записей для учетных записей. Возможно, это все еще управляемо, но что произойдет, когда мы начнем составлять несколько разных смарт-контрактов вместе в одном вызове?

Предположим, мы хотим создать смарт-контракт, который делает следующее:

  • он владеет полномочиями монетного двора от программы Token Mint Lock (как описано в предыдущем разделе) для определенного монетного двора токена
  • когда он вызывается, он будет использовать свои полномочия для создания указанного пользователем количества токенов, обменивать их с помощью AMM на другой токен и отправлять их пользователю в одной и той же инструкции.

Цель этого примера — проиллюстрировать, как смарт-контракт Mint Authority Lock и смарт-контракт AMM будут составлены вместе. Это чисто гипотетический пример, который, вероятно, не имеет никакого применения в реальной жизни, но послужит для иллюстрации сути — реальные примеры составления смарт-контрактов не слишком отличаются от этого.

Учетная запись проверяет вызов инструкций, который делает это, может выглядеть примерно так:

Это 17 аккаунтов. У нас есть около 5–6 на вызов CPI (чеканка и обмен) плюс учетные записи программы.

На Sui сигнатура эквивалентной функции будет такой:

Всего 3 объекта.

Так как же может быть, что мы передаем гораздо меньше объектов на Sui по сравнению с аккаунтами на Solana (3 против 17)?

По сути, причина в том, что в Move мы можем встраивать (оборачивать) их. Гарантии безопасности системы типов позволяют нам это делать.

Вот сравнение того, как может выглядеть учетная запись Solana и объект Sui, который содержит состояние пула AMM:

Мы видим, что в Solana мы храним адреса (Pubkeys) других аккаунтов, которые подобны указателям и не хранят фактические данные. Чтобы получить доступ к этим учетным записям, их необходимо передать отдельно, а также вручную проверить, переданы ли нужные учетные записи. В Move мы можем встраивать структуры друг в друга и получать прямой доступ к их значениям. . Мы можем смешивать и сопоставлять типы из любого модуля, сохраняя при этом свои ресурсы и гарантии безопасности типов. Это снова возможно благодаря глобальной системе типов Move и безопасности ресурсов, обеспечиваемой проверкой байт-кода.

Но основная проблема с передачей большого количества аккаунтов даже не в эргономике разработчика. Необходимость обхода (и, следовательно, проверки) многих учетных записей при составлении нескольких смарт-контрактов создает значительную сложность реализации и имеет последствия для безопасности. Отношения между этими учетными записями могут быть довольно запутанными, и в определенный момент становится довольно сложно отследить все необходимые проверки учетных записей и правильность их выполнения.

На самом деле, это то, что, как мне кажется, произошло с эксплойтом Cashio (48 миллионов долларов). Вот разбивка (недостаточных) проверок учетных записей, которые активировали эксплойт. Как видите, эти проверки учетной записи могут быть довольно сложными. У разработчика, вероятно, были самые лучшие намерения сделать проверки должным образом, но в определенный момент умственные затраты становятся слишком большими, и становится очень легко ошибиться. Чем больше учетных записей, тем выше вероятность наличия ошибки.

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

В качестве примечания, есть еще одна вещь, которую стоит учитывать с точки зрения безопасности Move по сравнению с Rust/Anchor, которая может быть не сразу очевидна. Дело в том, что TCB (надежная вычислительная база) для Move намного меньше, чем у Rust/Anchor. Меньший TCB означает, что меньшее количество компонентов, которые входят в компиляцию и выполнение смарт-контракта, должны быть доверенными. Это уменьшает площадь поверхности для уязвимостей, которые могут повлиять на смарт-контракты — ошибки за пределами TCB не влияют на безопасность смарт-контрактов.

Move был разработан с учетом сокращения TCB — был принят ряд решений, чтобы максимально уменьшить TCB. Верификатор байт-кода берет многие проверки, выполняемые компилятором Move, из TCB, в то время как в Rust/Anchor нужно доверять гораздо большему количеству компонентов, а область обнаружения критических с точки зрения безопасности ошибок намного больше.

6. Продолжайте Солану

Очевидно, что Move великолепен. Мы также знаем, что Solana была разработана таким образом, чтобы другие языки программирования могли использоваться для разработки смарт-контрактов для нее. Теперь вопрос в том, можем ли мы получить Move on Solana и как?

6.1. Якорь с глобальной безопасностью типов?

Прежде чем мы начнем изучать Move on Solana, давайте кратко рассмотрим Anchor и проведем небольшой мысленный эксперимент. Может быть, мы можем каким-то образом обновить Anchor, чтобы обеспечить некоторые преимущества, которые мы получаем от Move? Может быть, мы сможем получить встроенную поддержку безопасности типов при вызовах программ? Ведь инструкции Anchor уже напоминают функции входа Move:

// Function signatures of the "mint to" function from the
// "Mint Authority Lock" smart contract from the previous chapter.
// Sui Move
public entry fun mint_balance<T>(
  lock: &mut TreasuryLock<T>,
  cap: &mut MintCap<T>,
  amount: u64,
  ctx: &mut TxContext
) {
// Anchor
pub fn mint_to(ctx: Context<MintTo>, amount: u64) -> Result<()> {

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

// Anchor modified
pub fn mint_to(
  lock: &mut MintLock,
  authority: &mut MintAuthority,
  amount: u64
) -> Result<()> {

мы могли бы избежать проверки счетов?

В этом случае мы хотим, чтобы проверку типов выполняла среда выполнения, а не программа — среда выполнения считывала бы дискриминаторы учетных записей Anchor (или эквивалентные) и могла бы проверить, соответствует ли переданная учетная запись требуемому дискриминатору (первые 8 байтов якорный аккаунт).

Но помните, что Солана не различает вызовы разных инструкций в одной и той же программе, это реализуется программой вручную (в данном случае всю тяжелую работу выполняет Anchor). Таким образом, чтобы сделать это, среда выполнения каким-то образом должна знать о различных инструкциях, их сигнатурах, а также иметь информацию о типах.

Программы Solana компилируются в SBF (Solana Bytecode Format, модификация eBPF) и загружаются в цепочку (и выполняются) как таковые. И сам SBF не встраивает никакой информации о типах или функциях, которая могла бы нам здесь помочь. Но, может быть, мы могли бы модифицировать SBF, чтобы в двоичный файл можно было встроить информацию об инструкциях и типах? Тогда необходимая информация об инструкциях и сигнатурах могла быть прочитана средой выполнения из бинарного файла.

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

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

Мы по-прежнему не получаем:

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

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

Несмотря на то, что эти преимущества действительно хороши, они не сильно меняют с точки зрения разработки смарт-контрактов. Выполнение проверок типов во время выполнения вместо программ, вероятно, имеет некоторые преимущества в производительности, а отсутствие необходимости передавать учетные записи с адресами, известными во время компиляции, вручную от клиента несколько улучшает эргономику (это также можно облегчить с помощью инструментов). Но хотя эти улучшения эргономики и производительности действительно помогают, в конечном итоге мы по-прежнему имеем дело с моделью программирования Соланы, которая сама по себе не очень помогает нам в работе с цифровыми активами — мы по-прежнему не есть встроенная безопасность ресурсов, мы не можем встраивать учетные записи, поэтому учетная запись все еще раздувается, мы все еще имеем дело с подписями учетных записей и КПК…

В идеале мы хотели бы, чтобы все смарт-контракты жили в рамках единой системы типов и могли свободно передавать объекты в них и из них, как в Move. Но поскольку другим смарт-контрактам нельзя доверять, мы не можем сделать это напрямую.Чтобы обойти это, в Solana есть разделение программ и владение учетными записями — каждая программа управляет своими собственными учетными записями, и они взаимодействуют через вызовы CPI. Это безопасно и обеспечивает достаточную программируемость, но получающаяся в результате модель программирования не идеальна — нет глобальной системы типов, а без нее — и значимой безопасности ресурсов.

Нам нужна естественная модель программирования, но в то же время мы имеем дело с ненадежным кодом. Хотя в Solana мы можем безопасно работать с ненадежным кодом, это ставит под угрозу модель программирования. Верификация байт-кода — это то, что позволяет иметь и то, и другое одновременно. Так что действительно кажется, что без нее мы не сможем значительно улучшить модель программирования…

6.2. Формат байт-кода Соланы

Как упоминалось ранее, SBF (формат байт-кода Solana), в который компилируются смарт-контракты Solana и которые хранятся в цепочке, основан на eBPF. Основная мотивация использования eBPF в Solana в качестве основы вместо любого другого формата байт-кода (например, WASM) заключалась в том, что требования к безопасному и производительному выполнению смарт-контрактов, предъявляемые Solana, соответствуют требованиям к выполнению изолированной программы в ядре, для которого eBPF был разработан. (он также должен быть безопасным и эффективным). Подробнее об первоначальных дизайнерских решениях среды смарт-контрактов Solana вы можете прочитать в этой статье от Анатолия Яковенко (обратите внимание, что эта статья 2018 года, и некоторые из обсуждаемых там вещей устарели).

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

Но давайте посмотрим, что это означает на практике. Может быть, мы можем каким-то образом использовать верификатор eBPF, чтобы повысить безопасность наших смарт-контрактов? Вот некоторые из вещей, которые делает верификатор eBPF:

  • запрещает неограниченные циклы
  • проверяет, что программа является DAG
  • запрещает прыжки за пределы поля
  • проверяет типы аргументов при выполнении различных вызовов вспомогательных функций (помощники определяются в ядре и используются, например, для изменения сетевого пакета)

Что ж, запрет прыжков за пределы поля кажется полезным, но остальные вещи не так уж и важны. На самом деле, требование, чтобы программа была DAG и не имела неограниченных циклов, проблематично, поскольку это значительно ограничивает программируемость (у нас нет полноты по Тьюрингу). Причина, по которой это необходимо в программах eBPF, заключается в том, что верификатор должен определить, что программа завершается в течение определенного количества инструкций (чтобы программа не могла остановить ядро; это знаменитая проблема остановки), а учет газа не является вариант, потому что это будет слишком сильно снижать производительность.

Хотя этот компромисс отлично подходит для реализации высокопроизводительного брандмауэра, он не так хорош для разработки смарт-контрактов,и большую часть верификатора eBPF нельзя повторно использовать для программ Solana. На самом деле Солана вообще не использует оригинальный верификатор eBPF. Он использует (более простой) пользовательский верификатор, который в основном просто проверяет правильность инструкций и переходы за пределы допустимого диапазона.

Помимо верификатора, есть несколько других особенностей eBPF, которые немного проблематичны для компиляции смарт-контрактов. Как и тот факт, что eBPF по своей конструкции позволяет передавать не более 5 аргументов вызову функции. Фактически это означает, что стандартная библиотека Rust не может быть напрямую скомпилирована в eBPF. Или что размер стека ограничен 512 байтами, что уменьшает размер аргументов, которые мы можем передать функции без выделения кучи.

Таким образом, даже несмотря на то, что Rust компилируется в LLVM, для LLVM существует бэкэнд eBPF, и даже есть поддержка компилятора Rust для eBPF, вы все равно не сможете включить смарт-контракты Solana для компиляции в eBPF как есть. Вот почему команде Solana пришлось внести несколько изменений как в кодовую базу Rust, так и в бэкэнд eBPF LLVM (например, Поддержка передачи аргументов через стек).

Поскольку некоторые из этих изменений по своей природе не могут быть воспроизведены (ни для Rust, ни для LLVM), команда Solana в настоящее время поддерживает форки как Rust, так и LLVM с этими изменениями. Когда вы выполняете cargo build-bpf (каноническая команда для создания смарт-контрактов Solana), Cargo использует эту версию rustc, специфичную для Solana, для компиляции смарт-контракта (оригинальный rustc не будет работать).

Так и появился на свет SBF — некоторые требования, которые нужны Солане, несовместимы с eBPF. Команда Solana в настоящее время работает над апстримом SBF в качестве отдельного бэкенда LLVM и добавлением его в качестве цели Rust, чтобы избежать необходимости поддерживать отдельные форки.

Для любопытных вот некоторые из соответствующих обсуждений на GitHub:

Таким образом, хотя eBPF может работать как формат смарт-контрактов, он не так идеален, как кажется на бумаге. Его нужно немного подправить, а оригинальный верификатор не очень полезен.

Кроме того, в обсуждениях Move и Solana/SBF всплывает ошибочное представление, которое, по моему мнению, очень важно устранить. Некоторые комментарии говорят о том, что основные идеи Move должны работать для SBF. потому что он основан на eBPF, и его верификатор потенциально может быть использован для того, чтобы проверки мутации аккаунта выполнялись статически, а не динамически во время выполнения.

На мой взгляд, это сомнительное утверждение. Даже если можно доказать, что программы не изменяют учетные записи, которыми они не владеют, в eBPF, а Move действительно делает такие вещи, это определенно не *основная* идея Move.

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

На практике это означает:

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

Разделы в главе 5 (Flash Lending, Mint Authority Lock, Ограничения компонуемости Solana) показывают, насколько это важно с точки зрения безопасности смарт-контрактов, компонуемости и эргономики. Влияние этого выходит далеко за рамки пропуска некоторых проверок безопасности во время выполнения для повышения производительности межпрограммных вызовов. Это нельзя недооценивать.

Теперь, если вам интересно, насколько сложно было бы перенести основные идеи Move в eBPF/SBF, ответ — очень сложно. Применение таких свойств, как «этот ненадежный код не должен отбрасывать T», будет невозможно без значительных изменений в eBPF. На самом деле, потребуется так много изменений, что вы получите новый байт-код, который больше похож на Move, чем на eBPF. Это, безусловно, было бы большим исследовательским предприятием.

На самом деле именно такой образ мышления и привел к созданию Move. Команда Move (тогда еще в Diem) изначально рассматривала возможность начать с других форматов, таких как WASM, JVM или CLR, но слишком сложно добавить это постфактум — линейность/способности весьма нетрадиционны. Таким образом, Move был разработан с нуля с идеей эффективного обеспечения этих проверок с помощью облегченных проходов верификатора.

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

Чтобы было совершенно ясно, я не критикую Солану за использование eBPF. На самом деле, я думаю, что это действительно хороший выбор и здравый смысл команды с учетом контекста. Возможно, оглядываясь назад, команда могла уйти, например. с WASM вместо eBPF, что позволило бы избежать проблем с компиляцией смарт-контрактов в eBPF, упомянутых ранее, поскольку WASM имеет первоклассную поддержку в Rust (хотя с WASM могут быть другие проблемы), но я понимаю, как команда могла подумать, что eBPF был более безопасным выбором, учитывая упор на производительность. Кроме того, Move даже не был анонсирован, когда принимались эти решения по дизайну, и создание нового языка с нуля, безусловно, не было бы разумным вариантом для стартапа. В конце концов Солане удалось обеспечить успешный высокопроизводительный L1, и это то, что в конечном итоге имеет значение.

6.3. Бег на Солане

Расширение eBPF/SBF для поддержки функций Move кажется трудным, и мы, вероятно, в любом случае получим что-то похожее на байт-код Move. Вместо того, чтобы пытаться улучшить SBF, может быть, нам просто как-то заставить Move работать непосредственно на Солане? В конце концов, Солана очень открыт для поддержки нескольких языков программирования для разработки смарт-контрактов, и даже Анатолий поощряет интеграцию Move в некоторые из своих твитов.

Похоже, есть три способа получить Move on Solana:

  1. Добавьте виртуальную машину Move в качестве собственного загрузчика (наряду с виртуальной машиной SBF).
  2. Запустите Move VM как программу (например, Neon).
  3. Скомпилируйте, переместите в SBF (например, Solang)

Давайте сначала обсудим (3). Идея состоит в том, чтобы создать внешний интерфейс LLVM для Move, чтобы скомпилировать его в SBF. Смарт-контракты Move, скомпилированные в SBF, могут выполняться прозрачно так же, как смарт-контракты, созданные в Rust (или любом другом языке, который компилируется в SBF), и среда выполнения не должна иметь каких-либо различий или знаний о Move. Это было бы действительно элегантным решением с точки зрения среды выполнения, поскольку оно не требовало бы никаких изменений ни в ней, ни в ее предположениях о безопасности.

Но со стороны смарт-контракта это не имеет большого значения. Хотя есть веские причины для создания внешнего интерфейса LLVM для Move, например. с точки зрения производительности или переносимости выполнение смарт-контрактов Move с использованием среды выполнения SBF, на мой взгляд, не имеет большого значения. На самом деле, я бы сказал, что разработка смарт-контрактов таким образом будет хуже, чем простое использование Anchor.

Что вы получаете с (3), так это синтаксис Move в модели программирования Solana.Это означает, что все существенные преимущества Move, описанные в главе 5 (глобальная безопасность типов, глобальная безопасность ресурсов, встраиваемые объекты…) не было бы там. Вместо этого нам все равно пришлось бы иметь дело с проверками учетных записей, вызовами CPI, КПК и т. д. точно так же, как в Rust. А поскольку Move не поддерживает макросы, реализация фреймворка вроде Anchor с eDSL для упрощения некоторых из них была бы невозможна, поэтому код был бы похож на сырой Rust (но, вероятно, хуже). Стандартная библиотека и экосистема Rust также недоступны, поэтому такие вещи, как сериализация и десериализация учетных записей, должны быть заново реализованы в Move.

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

Отказ от байт-кода лишает Move всех основных преимуществ. Как говорится в статье Ресурсы: абстракция безопасного языка за деньги:

Отличительной особенностью Move является представление исполняемого байт-кода с гарантиями безопасности ресурсов для всех программ.Это крайне важно, учитывая открытую модель развертывания контрактов — помните, что любой контракт должен допускать произвольное взаимодействие с ненадежным кодом. strong> Линейность на уровне исходного кода имеет ограниченное значение, если она может быть нарушена ненадежным кодом на уровне исполняемого файла (например, ненадежным кодом, который дублирует линейный тип на уровне исходного кода).

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

Экосистему смарт-контрактов Move также нельзя было использовать на Solana — модели программирования настолько разные, что пришлось бы переписывать значительные части смарт-контрактов.

Принимая во внимание все это, я ожидаю, что использование Move, реализованного с помощью (3), не приживется — оно будет слишком громоздким в использовании по сравнению с Anchor. Вероятно, в некотором смысле это было бы даже более громоздко, чем сырой Rust.

Что касается (1), здесь идея состоит в том, чтобы (наряду с загрузчиком SBF) добавить поддержку загрузчика Move во время выполнения. Смарт-контракты Move будут храниться как байт-код Move в цепочке и выполняться виртуальной машиной Move (как и в Sui). Это означает, что у нас будет экосистема смарт-контрактов SBF и экосистема смарт-контрактов Move, где первая будет работать на текущей модели программирования Solana, а вторая — на (возможно, более совершенной) модели Move.

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

Есть и обратная сторона необходимости поддерживать два разных загрузчика во время выполнения. Это имеет последствия для безопасности, поскольку это означает, что поверхность атаки увеличивается в два раза — ошибка в любом загрузчике может означать, что вся цепочка будет взломана. Кстати, ранняя поддержка Move VM была фактически добавлена ​​в Solana еще в 2019 году (#5150), но позже была удалена (#11184) из соображений безопасности (см. эту ветку).

Что касается (2), идея состоит в том, чтобы запустить всю виртуальную машину Move как программу Solana (смарт-контракт). Виртуальная машина Move реализована на Rust, поэтому ее можно скомпилировать в SBF (если только она не использует потоки или какой-либо другой неподдерживаемый API). Как бы безумно это ни звучало, аналогичный подход был реализован компанией Neon для запуска EVM как программы Solana. Преимущество этого подхода заключается в том, что в среду выполнения не нужно вносить никаких изменений, и он может сохранять те же предположения о безопасности.

Я не знаком с техническими деталями Move VM, поэтому не могу подробно комментировать осуществимость этого и ограничения, которые у него будут. Первое, что приходит на ум, это то, что верификатор также должен быть запущен как программа, а это значит, что в пределах вычислительного бюджета. Этот подход также будет страдать от тех же проблем взаимодействия между смарт-контрактами SBF и Move, что и (1).

Нет простого способа перенести основные функции Move в Solana. Хотя можно создать внешний интерфейс LLVM и скомпилировать Move в SBF, это мало что даст, поскольку модель программирования останется прежней. Как показано в мысленном эксперименте в разделе 6.1., вы мало что можете сделать для улучшения модели программирования без какой-либо проверки байт-кода. Изменить eBPF/SBF для поддержки проверки байт-кода было бы очень сложно. Кажется, что единственный разумный вариант — как-то заставить Move VM работать. Но это означает, что будут две экосистемы, работающие на разных моделях программирования, и заставить их правильно взаимодействовать очень сложно.

6.4. Производительность в движении

Байт-код Move не является общим языком байт-кода. Он имеет очень самоуверенную систему типов, и для того, чтобы обеспечить все необходимые проверки, он находится на достаточно высоком уровне. Это может означать более низкую производительность по сравнению с другими форматами байт-кода, такими как eBPF/SBF, которые намного ближе к собственному коду, и можно утверждать, что это будет проблемой для использования в высокопроизводительных L1.

Но выполнение смарт-контрактов до сих пор не было узким местом ни для Solana (которая на момент написания статьи составляет в среднем 3 тыс. TPS), ни для Sui (на основе первоначальных тестов e2e, выполненных командой). Основным драйвером повышения производительности обработки транзакций является параллельное выполнение. И Solana, и Sui реализуют это, требуя априорного объявления зависимостей и планируя параллельное выполнение транзакций, зависящих от разных наборов объектов/учетных записей. Благодаря этому, а также из-за наличия узких мест в других местах, а именно на сетевом уровне, выполнение транзакций с точки зрения TPS все еще находится на несколько порядков от критического пути.

Кроме того, ничто не препятствует компиляции Move с помощью AOT или JIT-обработки для повышения производительности, как только выполнение TX действительно появляется на критическом пути. Именно здесь было бы полезно создать интерфейс LLVM для Move. Кроме того, дополнительные оптимизации, уникальные для Move, потенциально могут быть получены благодаря присущей Move возможности статического анализа.

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

7. Другие функции движения

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

7.1. Доказывающий

У Move есть формальный инструмент проверки смарт-контрактов, который называется Move Prover. С помощью этого инструмента вы можете утверждать, применимы ли различные инварианты к вашему смарт-контракту или нет. За кулисами условия проверки переводятся в формулы SMT, а затем проверяются с помощью решателя SMT. Это очень отличается, например. от нечеткого тестирования, где это метод проб и ошибок путем обхода входного пространства. Например, нечеткие и модульные/интеграционные тесты могут по-прежнему давать ложный положительный результат, если они не проверяют определенные входные данные или комбинацию входных данных, которые могут показать, что в программе есть ошибка. Доказательство, с другой стороны, фактически обеспечивает формальное доказательство того, что указанные инварианты выполняются для предоставленной программы. Это похоже на проверку программы на соответствие всем возможным входным данным, но без необходимости этого делать.

Move Prover работает очень быстро, поэтому его можно интегрировать в обычный рабочий процесс разработки точно так же, как средство проверки типов или линтер.

Вот пример того, как выглядит спецификация прувера (взято из технического документа Быстрая и надежная формальная проверка смарт-контрактов с помощью Move Prover):

[…] Это добавляет спецификацию передаточной функции, вспомогательную функцию bal для использования в спецификациях и два глобальных инварианта памяти. Первый инвариант гласит, что баланс никогда не может опускаться ниже определенного минимума. Второй инвариант относится к обновлению глобальной памяти с предварительным и пост-состоянием: баланс на счете никогда не может уменьшиться за один шаг больше, чем на определенную сумму.

Для получения более подробной информации о прувере я рекомендую следующие технические документы:

7.2. Безопасность кошелька

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

Например, если у нас есть функция со следующей сигнатурой:

public entry fun foo(
  asset1: &Asset,
  asset2: &mut Asset,
  asset3: Asset
)

только по сигнатуре функции мы можем сказать, что эта транзакция получит доступ к трем активам пользователя (типа Asset). Кроме того, на основе ключевых слов & и &mut (и их отсутствия) мы также можем сказать, что asset1 можно просто прочитать, asset2 можно изменить (но не передать или уничтожить), а asset3 потенциально можно изменить, передать , или уничтожены.

Кошелек может отображать эту информацию для пользователя, который затем имеет более полное представление о том, что транзакция может сделать с активами. Если что-то не так, т.е. вызов транзакции, исходящий из приложения web3, затрагивает некоторые активы или монеты, которых не должно быть, пользователь может заметить это и решить не продолжать транзакцию. На эту тему есть отличная статья от Ethos Wallet (ссылка).

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

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

7.3 Простые и сложные транзакции

Конкретно для Sui существует интересная оптимизация на уровне консенсуса, которая позволяет определенным типам транзакций отказаться от полного консенсуса и вместо этого совершать их с использованием более простого алгоритма, основанного на Byzantine Consistent Broadcast. Преимущество заключается в том, что эти транзакции могут быть распараллелены на уровне консенсуса, что устраняет блокировку очереди и фиксируется почти мгновенно, что обеспечивает масштабируемость в основном для Web2.

Это возможно благодаря тому, что Sui проводит различие между собственными и общими объектами (см. раздел 3.1.). Транзакции, включающие только принадлежащие объекты (называемые простыми транзакциями), не требуют полного согласия по Sui. Поскольку принадлежащие объекты не могут использоваться в транзакции кем-либо еще, кроме отправителя и отправителя, одну транзакцию за раз, это по своей сути означает, что эти транзакции не должны быть упорядочены относительно других транзакций (полное или причинное упорядочение) — мы точно знаем, что объекты, на которые ссылается транзакция, не могут быть затронуты другими транзакциями. транзакции, а также что эта транзакция не может влиять на другие объекты. Таким образом, нас не волнует порядок этих транзакций по отношению к другим транзакциям, происходящим параллельно в цепочке — это фактически не имеет значения. Sui может использовать этот факт, чтобы значительно оптимизировать обработку простых транзакций, достигая завершенности в течение нескольких сотен мс. Недостаток в том, что отправителям разрешено отправлять только одну транзакцию за раз. С другой стороны, транзакции, включающие любое количество общих объектов (называемые сложными транзакциями), всегда требуют полного консенсуса.

Учитывая, что создание, передача и модификация принадлежащих объектов могут полностью выполняться с помощью простых транзакций, некоторые типы приложений могут действительно эффективно использовать простые транзакции. Хорошими примерами являются NFT (включая массовую чеканку) и веб-игры. Эти варианты использования во многом выигрывают от окончательности с низкой задержкой и устранения блокирования очереди, что обеспечивает лучший пользовательский интерфейс и масштабируемость. Более полный список приложений, удобных для одного автора, можно найти здесь.

Однако некоторые другие типы приложений должны полагаться на сложные транзакции. Сюда входит большинство приложений DeFi. Например, пул ликвидности AMM должен быть общим объектом, потому что выполнение любого типа биржевого ордера требует полного консенсуса и полного упорядочения. Это потому, что, по сути, если несколько ордеров поступают от разных пользователей одновременно, нам нужно договориться о том, чей ордер будет выполнен первым, и это определяет, какую цену исполнения получит каждый пользователь.

Кроме того, существуют приложения, которые могут использовать сочетание простых и сложных транзакций. Эти приложения требуют сложных транзакций для достижения желаемой функциональности, но могут использовать простые транзакции для определенных операций для повышения эффективности. Ценовой оракул может быть разработан, например, так. У нас может быть несколько издателей, отправляющих данные о ценах для рынка с использованием простых транзакций, а затем уполномоченный орган для агрегирования цен (например, взвешенная медиана ставок) с использованием сложной транзакции. Невозможно реализовать ценовой оракул, не полагаясь в какой-то момент на сложные транзакции (в основном потому, что использование опубликованных цен в других транзакциях требует соглашения об упорядочении и, следовательно, полного консенсуса), но, по крайней мере, мы можем оптимизировать записи издателя с помощью простых транзакций.

Документы Sui содержат более подробную информацию о простых и сложных транзакциях:

8. Заключительные замечания

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

Глава 2 представляет собой краткое изложение модели программирования Соланы, а глава 3 знакомит с Sui Move и ее моделью программирования. Затем в главе 4 объясняется, как безопасность типов и ресурсов работает в Move. Значение функций Move для разработки смарт-контрактов не сразу очевидно, поэтому в главе 5 проводится более тщательное сравнение Solana и Sui Move на примерах из реальной жизни. В главе 6 обсуждается eBPF/SBF и показывается, что заставить функции Move или сам Move работать на Solana — непростая задача. Глава 7. касается некоторых других функций Move.

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

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

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

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

Настолько, что я бы сказал, что Move сделает для разработки смарт-контрактов то же, что React сделал для разработки внешнего интерфейса, и сказать, что «то, что вы можете сделать с Move, вы можете сделать с Rust», сродни утверждению «то, что вы можете сделать с React вы может сделать с jQuery». Конечно, можно реализовать приложение на основе jQuery, эквивалентное приложению React, но это нецелесообразно. React представил концепцию виртуального DOM, которая полностью прозрачна для разработчика, но позволяет гораздо быстрее, масштабируемее и проще разрабатывать интерфейсы. Аналогичным образом проверка байт-кода в Move — это базовая технология, которая также полностью прозрачна для разработчика, но предлагает более эргономичную, компонуемую и более безопасную разработку смарт-контрактов. Благодаря своей безопасности и более интуитивно понятной модели программирования Move также значительно снижает планку входа для разработчиков смарт-контрактов.

Если Move удастся набрать обороты (а есть ранние признаки того, что так оно и будет), это может представлять серьезную угрозу для Соланы. Это происходит по двум причинам.

Во-первых, время разработки смарт-контрактов Move намного меньше. Оказывается, разработка смарт-контракта с нуля в Move может быть в 2–5 раз быстрее, чем в Rust. Это особенно верно при составлении смарт-контрактов, которые тривиальны в Move, но могут быть сложными в Solana. Из-за этого развитие экосистемы Move может опередить Солану. Из-за открытого и неконтролируемого характера блокчейнов не существует сильного эффекта блокировки, и ликвидность может легко перемещаться. Разработчики Solana могут быть вынуждены принять Move исключительно по экономическим причинам — вы можете либо переключиться на Move, либо уступить место разработчикам Move, которые быстрее разрабатывают более безопасные смарт-контракты. Если вы нанимаете разработчика смарт-контрактов, вы можете либо нанять разработчика Rust, который создаст один смарт-контракт, либо разработчика Move, который создаст два более безопасных за одинаковое время. Это похоже на эффект, который React оказал на разработку внешнего интерфейса.

Вторая причина заключается в том, что порог входа для Move намного ниже, чем для Rust или Solidity. Поскольку синтаксис Move намного проще, а модель программирования более интуитивно понятна, существует целый класс разработчиков, которые не смогут заниматься разработкой смарт-контрактов в Rust или Solidity, но могут сделать это в Move. А поскольку концепций для изучения меньше, адаптация разработчика, не являющегося разработчиком смарт-контрактов, к Move намного проще, чем к Rust (который сам по себе является сложным языком, плюс концепции Solana, такие как КПК, которые вызывают много путаницы у новичков). или Solidity (где вам нужно быть знакомым с очень тонкими деталями языка, такими как повторный вход, чтобы иметь возможность разрабатывать безопасные смарт-контракты). Даже если существующие разработчики Solana и Solidity не перейдут на Move, рынок разработчиков, еще не вошедших в пространство, на порядки больше, чем количество существующих разработчиков в пространстве. Поскольку Move имеет более низкую планку для входа и позволяет быстрее разрабатывать, он гораздо лучше подходит для рынка, чем Rust или Solidity, и может занять гораздо больший кусок этого пирога. Если новые разработчики начнут массово приходить, я ожидаю, что они начнут с Move, а не с Rust или Solidity. Это снова похоже на то, что произошло с React в веб-индустрии.

Из-за этого я полностью ожидаю, что первоклассная поддержка Move будет добавлена ​​​​в Solana в среднесрочной и долгосрочной перспективе. Но это непростая задача. Чтобы получить основные преимущества Move, байт-код Move должен поддерживаться изначально. Это означает, что простая компиляция Move в eBPF/SBF не поможет (см. раздел 6.3.). Чтобы сохранить существующую экосистему, необходимо поддерживать обе среды выполнения. Основная техническая задача состоит в том, чтобы обеспечить надлежащее взаимодействие между средами выполнения. Это требует глубоких знаний как о Move, так и о Solana, поэтому я ожидаю, что команде Solana потребуется управлять этим напрямую при поддержке кого-то из команды Move.

Движение возникло в Meta (урожденная Facebook) для проекта Diem. Перед командой Move под руководством Сэма Блэкшира была поставлена ​​задача выяснить, что делать со смарт-контрактами. Изучив проблему более внимательно, они увидели, что программирование смарт-контрактов — это программирование цифровых активов (ресурсов), но ни один из существующих языков изначально не поддерживает этот вариант использования. Было принято решение создать новый язык программирования с нуля.

Я хотел бы подчеркнуть, что решение о создании нового языка вовсе не является очевидным, поскольку для его запуска требуются годы инженерных усилий, и в большинстве случаев лучше использовать существующие решения. Команда Move правильно предвидела, что язык смарт-контрактов, который является безопасным, имеет первоклассную поддержку ресурсов и в то же время является достаточно гибким, может быть создан, и уже одно это свидетельствует о высоком уровне профессионализма. Это был смелый шаг со стороны команды и инженерного руководства Novi/Meta, которые поддержали проект (в таком решении должно было участвовать несколько директоров и вице-президентов). С тех пор Meta прекратила свои усилия Diem и в конце концов не смогла пожинать плоды своих инвестиций в Move, но, тем не менее, это большой вклад в более широкое криптосообщество.

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