Развитие смарт-контрактов: исследование сравнения Move и Rust
Исходный заголовок: 《Разработка смарт-контрактов---Move против Rust》
Автор: Крешимир Клас, основатель языка Move
Скомпилировано: Го Цяньвэнь, ChainCatcher
1. Введение
Недавние обсуждения вокруг Aptos и Sui идут полным ходом, оба являются новыми высокопроизводительными L1 публичными блокчейнами, язык программирования смарт-контрактов Move является неотъемлемой частью этих новых цепочек. Некоторые разработчики активно переходят на Move, утверждая, что это будущее разработки смарт-контрактов. Другие более осторожны и считают, что Move не предлагает ничего действительно нового по сравнению с существующими языками программирования.
Криптоинвесторы также интересуются уникальностью этих L1 блокчейнов и тем, как они могут конкурировать с Solana, которая в настоящее время является основным игроком на рынке высокопроизводительных L1 и известна использованием Rust для программирования смарт-контрактов.
Однако обсуждения, которые мы видим в настоящее время, не достигают определенной глубины, чтобы действительно понять, как эти новые технологии влияют на нас. Это применимо к обеим сторонам обсуждения — критики Move принижают его до нуля, не в состоянии оценить его более тонкие (но очень важные) аспекты, в то время как сторонники Move, чрезмерно восхваляющие его, также не могут понять, что делает его великим. Это создает огромную серую зону и неясность, из-за чего внешние наблюдатели, крипторазработчики и инвесторы интересуются этой темой, но не могут быть уверены в своих взглядах.
В этой статье я проведу глубокое техническое исследование Move, его новой модели программирования, блокчейна Sui и того, как он использует возможности Move, а также сравню его с Solana и ее моделью программирования. Чтобы подчеркнуть особенности Move, я буду сравнивать Solana/Rust с Sui/Move. Поскольку понимание становится легче, когда вы сравниваете одну вещь с другой, с которой вы уже знакомы.
У Move есть и другие варианты, такие как Aptos Move, которые немного отличаются в некоторых аспектах. Основное внимание в этой статье не уделяется обсуждению тонкостей между различными вариантами Move, а демонстрации его универсальных преимуществ и сравнению с моделью программирования Solana. Поэтому для простоты я буду использовать только один вариант (Sui Move) в этой статье. Таким образом, некоторые концепции Move, представленные в этой статье (т.е. объекты и связанные функции), применимы только к варианту Move Sui и не относятся к другим вариантам. Хотя другие варианты Move могут не иметь этих концепций, они используют различные механизмы (например, глобальное хранилище), чтобы достичь аналогичных функций. Тем не менее, все основные преимущества Move, обсуждаемые в этой статье, применимы ко всем интеграциям Move (родная поддержка байт-кода Move), включая Aptos. Я выбрал Sui только потому, что мне с ним более знакомо, и я считаю, что он более интуитивно понятен и легче представлен в форме статьи.
2. Модель программирования Solana
В Solana программы (смарт-контракты) являются безсостояниевыми, они не могут сами получать доступ (читать или записывать) к любому состоянию, которое существует на протяжении всей транзакции. Чтобы получить доступ или сохранить состояние, программе необходимо использовать аккаунты. Каждый аккаунт имеет уникальный адрес (публичный ключ пары Ed25519), который может хранить произвольные данные.
Мы можем рассматривать пространство аккаунтов Solana как глобальное хранилище ключ-значение, где ключом является адрес аккаунта (pubkey), а значением — данные аккаунта. Программа выполняет операции на этом хранилище, читая и изменяя его значения.
У аккаунтов есть концепция собственности. Каждый аккаунт принадлежит одной (и только одной) программе. Когда аккаунт принадлежит программе, этой программе разрешено изменять его данные. Программа не может изменять аккаунты, которыми она не владеет (но ей разрешено их читать). В процессе выполнения, сравнивая состояние аккаунтов до и после выполнения программы, можно провести такую динамическую проверку, и если будут обнаружены незаконные изменения, транзакция завершится неудачей.
Каждый аккаунт также имеет связанный с ним приватный ключ (соответствующий публичный ключ — это его адрес), и пользователь, имеющий доступ к этому приватному ключу, может использовать его для подписания транзакций. Используя этот механизм, мы реализуем функции прав и собственности в смарт-контрактах Solana — например, чтобы получить доступ к определенным средствам, смарт-контракт может требовать от пользователя предоставить необходимые подписи.
При вызове программы клиенту необходимо указать, какие аккаунты эта программа будет использовать во время вызова. Таким образом, время обработки транзакции может быть организовано так, чтобы не пересекаться, и транзакции могут выполняться параллельно, обеспечивая при этом согласованность данных. Это одна из особенностей дизайна Solana, которая обеспечивает высокую пропускную способность.
Программа может вызывать другие программы через CPI. Эти вызовы работают аналогично вызовам от клиента — вызывающая программа должна указать, какие аккаунты будут использоваться вызываемой программой, и вызываемая программа проведет проверку входных данных, так же как и при вызове из клиента (поскольку она не доверяет вызывающей программе).
Аккаунты PDA — это особые аккаунты, которые позволяют программе предоставлять подписи аккаунтов, не обладая или не храня приватный ключ. PDA гарантирует, что только программа, создавшая PDA, может создать для него подпись (в то время как другие пользователи и программы не могут). Это полезно, когда программе необходимо взаимодействовать с другой программой через CPI и предоставить авторизацию (например, реализовать хранилище). PDA гарантирует, что никто, кроме программы, не может получить прямой доступ к ресурсам программы. PDA также может использоваться для создания аккаунтов по определенному адресу.
Это основные компоненты безопасного программирования смарт-контрактов в Solana. В некотором смысле, вы можете рассматривать программы Solana как программы в операционной системе, а аккаунты как файлы, которые любой может свободно выполнять, даже развертывать свои собственные программы. Когда программа (смарт-контракт) выполняется, она будет читать и записывать файлы (аккаунты). Все файлы могут быть прочитаны всеми программами, но только программы, имеющие права собственности на файл, могут его изменять. Программы также могут выполнять другие программы, но они не доверяют друг другу — независимо от того, кто выполняет программу, она должна предполагать, что входные данные могут быть потенциально вредоносными. Поскольку эта операционная система доступна для любого человека по всему миру, в программы встроена поддержка проверки подписей, чтобы обеспечить пользователям функции прав и собственности… это не идеальная аналогия, но все же довольно интересная.
3. Модель программирования Move
В Move смарт-контракты публикуются в виде модулей. Модуль состоит из функций и пользовательских типов (структур). Структура состоит из полей, которые могут быть примитивными типами (u8, u64, bool…) или другими структурами. Функции могут вызывать другие функции — как в том же модуле, так и в других открытых модулях.
В Solana это эквивалентно тому, что все смарт-контракты публикуются как модули в одной программе. Это означает, что все смарт-контракты (модули) находятся в одной системе и могут вызывать друг друга напрямую, без необходимости через промежуточные API или интерфейсы. Это очень важно, и его влияние будет полностью обсуждено в этой статье.
3.1. Объекты
Следует отметить, что нижеизложенная концепция объектов относится к варианту Sui Move. В других интеграциях Move (например, Aptos или Diem/core Move) ситуация может быть немного иной. Тем не менее, в других вариантах Move также есть аналогичные решения, которые могут достичь тех же целей (постоянство состояния), и эти решения не сильно отличаются.
Основная причина, по которой я представляю вариант Sui, заключается в том, что примеры кода в дальней части статьи основаны на варианте Sui Move, и его объекты, такие как механизм глобального хранилища в core Move, более интуитивно понятны. Важно, что все основные преимущества Move, обсуждаемые в этой статье, применимы ко всем интеграциям Move (родная поддержка байт-кода Move), включая Aptos.
Объекты — это экземпляры структур, хранящиеся в среде выполнения и сохраняющие состояние в транзакциях.
Существует три различных типа объектов (в Sui):
- Собственные объекты (owned objects)
- Общие объекты (shared objects)
- Неподвижные объекты (immutable objects)
Собственные объекты принадлежат пользователю. Только пользователь, владеющий этим объектом, может использовать его в транзакции. Метаданные о собственности полностью прозрачны и обрабатываются средой выполнения. Это реализуется с помощью технологии шифрования публичного ключа — каждый собственный объект связан с публичным ключом (хранящимся в метаданных объекта во время выполнения), и всякий раз, когда вы хотите использовать объект в транзакции, вам необходимо предоставить соответствующую подпись (в настоящее время поддерживается Ed25519, в будущем будет поддерживаться ECDSA и многофакторная подпись K-of-N).
Общие объекты похожи на собственные объекты, но у них нет связанного владельца. Таким образом, вам не нужно владеть каким-либо приватным ключом, чтобы использовать их в транзакции (любой может их использовать). Любой собственный объект может быть общим (от его владельца), и как только объект становится общим, он навсегда остается общим — его нельзя передать или снова сделать собственным объектом.
Неподвижные объекты не могут быть изменены. Как только объект помечен как неподвижный, его поля больше не могут быть изменены. Подобно общим объектам, эти объекты не имеют владельца и могут использоваться любым человеком.
Модель программирования Move очень интуитивна и проста. Каждый смарт-контракт является модулем, состоящим из функций и определений структур. Структуры инстанцируются в функциях и могут передаваться в другие модули через вызовы функций. Чтобы структура могла сохранять постоянство между транзакциями, мы превращаем ее в объект, который может быть собственным, общим или неподвижным (только для Sui, в других вариантах Move это немного иначе).
4. Безопасность Move
Мы уже видели, что в Move:
- вы можете передавать любой объект, которым вы владеете (или который является общим), любым функциям в любом модуле
- любой может опубликовать (потенциально враждебный) модуль
- не существует концепции владения структурой модулем, что дает владельцу модуля единственное право изменять структуру, как в случае с аккаунтами Solana — структура может поступать в другие модули и может быть встроена в другие структуры.
Вопрос в том, почему это безопасно? Что мешает людям публиковать вредоносные модули, получать общие объекты (например, AMM-пулы) и отправлять их в вредоносные модули, а затем продолжать истощать их средства?
В Solana существует концепция собственности аккаунта, то есть только программа, владеющая аккаунтом, может вносить изменения. Но в Move нет концепции владения объектами модулем, вы можете отправлять объекты в любой модуль — вы можете ссылаться на объект, на весь объект, а также на его внутреннюю стоимость. Более того, среда выполнения не проводит конкретные проверки, чтобы гарантировать, что этот объект не был незаконно изменен при передаче через ненадежный модуль. Так что же защищает безопасность этого объекта? Как гарантируется, что этот объект не будет злоупотреблен ненадежным кодом?
Вот в чем новизна Move… давайте поговорим о ресурсах.
4.1. Структура
Определить тип структуры (struct) так же, как вы ожидаете:

Пока все хорошо — это также способ, которым вы определяете структуру в Rust. Но в Move структура имеет свои уникальные особенности по сравнению с традиционными языками программирования, модули Move имеют больше пространства в том, как использовать типы. Структура, определенная в приведенном выше фрагменте кода, будет подвержена следующим ограничениям:
- Она может быть инстанцирована (упакована) и уничтожена (распакована) только в модуле, в котором определена — это означает, что вы не можете инстанцировать или уничтожить экземпляр структуры из любой функции другого модуля.
- Поля экземпляра структуры могут быть доступны (и, следовательно, изменены) только в ее модуле.
- Нельзя клонировать или копировать экземпляр структуры за пределами ее модуля.
- Нельзя хранить экземпляр структуры в полях других экземпляров структур.
Это означает, что если мы обрабатываем экземпляр этой структуры в функциях других модулей, мы не сможем изменять его поля, клонировать его, хранить его в полях других структур или уничтожать его (его необходимо передавать в другое место через вызов функции). Дело в том, что модуль структуры реализует функции, которые могут быть вызваны из нашего модуля, чтобы выполнить эти действия. Но кроме этого, мы не можем делать это напрямую для внешних типов. Это позволяет модулю полностью контролировать, как использовать и не использовать свои типы.
Из-за этих ограничений мы, кажется, теряем много гибкости. Это правда — в традиционном программировании работа с такими структурами может быть очень сложной, но на самом деле именно этого мы и хотим в смарт-контрактах. В конце концов, разработка смарт-контрактов — это программирование цифровых активов (ресурсов). Если вы посмотрите на структуру, описанную выше, это именно ее суть — это ресурс. Его нельзя произвольно создавать, нельзя копировать и нельзя случайно уничтожить. Таким образом, мы действительно теряем некоторую гибкость здесь, но потеря этой гибкости — это именно то, что мы хотим, потому что это делает операции с ресурсами интуитивными и безопасными.
Кроме того, Move позволяет нам ослабить некоторые из этих ограничений, добавляя возможности (capabilities) к структуре. Существует четыре возможности: ключ, хранение, копирование и удаление. Вы можете добавить любую комбинацию этих возможностей к структуре.

Вот как они работают:
- Ключ - позволяет структуре стать объектом (только для Sui, в core Move ситуация немного другая). Как уже упоминалось, объекты являются постоянными, и если это собственный объект, требуется подпись пользователя для его использования в вызове смарт-контракта. При использовании возможности ключа первым полем структуры должен быть идентификатор объекта с типом UID. Это даст ему уникальный глобальный идентификатор, который можно использовать для ссылки.
- Хранение - позволяет встраивать эту структуру как поле в другую структуру.
- Копирование - позволяет копировать/клонировать эту структуру откуда угодно.
- Удаление - позволяет уничтожать эту структуру откуда угодно.
По сути, каждая структура в Move по умолчанию является ресурсом. Возможности предоставляют нам власть, чтобы тонко ослабить эти ограничения, чтобы они вели себя более похоже на традиционные структуры.
4.2. Монета (Coin)
Монета в Sui реализует функции, аналогичные токенам ERC20/SPL, и является частью библиотеки Sui Move. Ее определение выглядит следующим образом:

Вы можете найти полную реализацию модуля в кодовой базе Sui (ссылка).
Тип монеты имеет возможности ключа и хранения. Ключ означает, что она может использоваться как объект. Это позволяет пользователям напрямую владеть монетами (как верхним объектом). Когда вы владеете монетой, никто, кроме вас, не может даже ссылаться на нее в транзакции (не говоря уже о ее использовании). Хранение означает, что монета может быть встроена как поле в другую структуру, что полезно для компоновки.
Поскольку нет функции удаления, монета не может быть случайно уничтожена в функции. Это очень хорошая особенность — это означает, что вы не потеряете монету случайно. Если вы реализуете функцию, принимающую монету в качестве параметра, в конце функции вам нужно будет явно что-то с ней сделать — передать ее пользователю, встроить в другой объект или отправить в другую функцию через вызов (также нужно будет что-то с ней сделать). Конечно, возможно уничтожить монету, вызвав функцию coin::burn в модуле монет, но вам нужно делать это целенаправленно (вы не будете делать это случайно).
Отсутствие возможности клонирования означает, что никто не может копировать монету, создавая новые запасы. Создание новых запасов может быть выполнено через функцию coin::mint, и ее может вызвать только владелец объекта с возможностью казначейства (treasury capability) этой монеты.
Кроме того, благодаря наличию обобщений (generics) каждая монета является уникальным типом. Поскольку две монеты могут быть объединены только через функцию coin::join (а не через прямой доступ к их полям), это означает, что невозможно сложить значения монет разных типов (монета A + монета B) — потому что нет функции с такой подписью. Система типов защищает нас от плохих долгов.
В Move безопасность ресурсов определяется их типом. Учитывая, что Move имеет глобальную систему типов, это делает модель программирования более естественной и безопасной, ресурсы могут напрямую передаваться в ненадежный код и выходить из него.
4.3. Проверка байт-кода
Как уже упоминалось, смарт-контракты Move публикуются в виде модулей. Любой может создавать и загружать любые произвольные модули в блокчейн, которые могут выполняться кем угодно. Мы также уже видели, что в Move есть определенные правила использования структур.
Так что же гарантирует соблюдение этих правил любыми модулями? Что мешает людям загружать модули с особым сгенерированным байт-кодом, например, получать объект монеты, а затем напрямую изменять его внутренние поля, чтобы обойти эти правила? Делая это, можно незаконно увеличить количество всех монет. Одна только синтаксическая структура байт-кода определенно позволяет это сделать.
Проверка байт-кода может предотвратить такого рода злоупотребления. Проверка Move — это инструмент статического анализа, который анализирует байт-код Move и определяет, соответствует ли он необходимым правилам безопасности типов, памяти и ресурсов. Весь код, загружаемый в цепочку, должен пройти проверку. Когда вы пытаетесь загрузить модуль Move в цепочку, узлы и проверка сначала запускают его через проверку, прежде чем разрешить отправку. Если какой-либо модуль пытается обойти правила безопасности Move, он будет отклонен проверкой и не будет опубликован.
Байт-код Move и проверка являются основными инновациями Move. Они реализуют интуитивную модель программирования, ориентированную на ресурсы, которая не может быть достигнута в других местах. Самое главное, это позволяет структурированным типам пересекать границы доверия, не теряя своей целостности.
В Solana смарт-контракты являются программами, а в Move они являются модулями. Это кажется лишь семантическим различием, но это не так, это имеет большое значение. Разница в том, что в Solana нет типобезопасности при пересечении границ программ — каждая программа загружает экземпляры, вручную декодируя исходные данные аккаунтов, что требует ручного выполнения ключевых проверок безопасности, и нет локальной безопасности ресурсов. Напротив, безопасность ресурсов должна быть реализована каждым смарт-контрактом отдельно. Это действительно может обеспечить достаточную программируемость, но по сравнению с моделью Move это в значительной степени препятствует компоновке и эргономике, поскольку модель Move имеет родную поддержку ресурсов, которые могут безопасно входить и выходить из ненадежного кода.
В Move типы действительно существуют в различных модулях — система типов является глобальной. Это означает, что нет необходимости в вызовах CPI, кодировании/декодировании аккаунтов, проверках прав собственности на аккаунты и т.д. — вам просто нужно напрямую вызывать функции в другом модуле с параметрами. Вся безопасность типов и ресурсов смарт-контракта обеспечивается проверкой байт-кода во время компиляции/публикации, без необходимости реализовывать это на уровне смарт-контрактов, как в Solana, а затем проверять это во время выполнения.
5. Solana и Move
Теперь, когда мы увидели, как работает программирование Move и основные причины его безопасности, давайте углубимся в то, как это влияет на программирование смарт-контрактов с точки зрения компоновки, эргономики и безопасности. Здесь я сравню разработку Move/Sui с EVM и Rust/Solana/Anchor, чтобы помочь понять преимущества, которые приносит модель программирования Move.
5.1. Мгновенные займы
Мгновенные займы — это тип займа в DeFi, сумма которого должна быть возвращена в той же транзакции, в которой он был взят. Основное преимущество этого заключается в том, что, поскольку транзакция является атомарной, заем может быть полностью без залога. Это можно использовать для арбитража между активами без необходимости иметь собственный капитал.
Основная трудность в достижении этой цели заключается в том, как гарантировать, что сумма займа будет возвращена в той же транзакции из смарт-контракта мгновенного займа? Чтобы заем не требовал залога, транзакция должна быть атомарной — это означает, что если сумма займа не была возвращена в той же транзакции, вся транзакция должна завершиться неудачей.
EVM имеет динамическое планирование, поэтому можно использовать повторные вызовы (reentrancy) для реализации этого, как показано ниже:
- Пользователь мгновенного займа создает и загружает пользовательский смарт-контракт, который, когда вызывается, передает управление смарт-контракту мгновенного займа через вызов.
- Затем смарт-контракт мгновенного займа отправляет запрашиваемую сумму займа пользовательскому смарт-контракту и вызывает функцию обратного вызова executeOperation() в пользовательском смарт-контракте.
- Затем пользовательский смарт-контракт использует полученную сумму займа для выполнения необходимых операций (например, арбитража).
- После завершения своих операций пользовательский смарт-контракт должен вернуть заимствованную сумму смарт-контракту мгновенного займа.
- Таким образом, выполнение функции executionOperation() пользовательского смарт-контракта завершается, и управление возвращается смарт-контракту мгновенного займа, который проверяет, была ли сумма займа правильно возвращена.
- Если пользовательский смарт-контракт не вернул сумму займа правильно, вся транзакция завершится неудачей.
Это хорошо реализует необходимую функциональность, но проблема в том, что это зависит от повторных вызовов, которые мы очень надеемся не видеть в программировании смарт-контрактов. Поскольку повторные вызовы по своей сути очень опасны и являются причиной многих уязвимостей, включая печально известную атаку хакеров DAO.
Solana в этом плане справляется лучше, поскольку она не позволяет повторные вызовы. Однако без повторных вызовов, если смарт-контракт мгновенного займа не может вызвать обратно пользовательский смарт-контракт, как реализовать мгновенные займы в Solana? Благодаря самоанализу инструкций (instruction introspection). В Solana каждая транзакция состоит из нескольких инструкций (вызовов смарт-контрактов), и из любой инструкции вы можете проверить другие инструкции, существующие в той же транзакции (их идентификаторы программ, данные инструкций и аккаунты). Это делает возможным реализацию мгновенных займов, как показано ниже:
- Смарт-контракт мгновенного займа реализует инструкции заимствования (borrow) и возврата (repay).
- Пользователь создает транзакцию мгновенного займа, комбинируя вызовы инструкций заимствования и возврата в одной транзакции. Инструкция заимствования, когда выполняется, проверяет с помощью самоанализа, запланирована ли инструкция возврата на более поздний этап той же транзакции. Если вызов инструкции возврата отсутствует или недействителен, транзакция завершится на этом этапе неудачей.
- Между вызовами заимствования и возврата заимствованные средства могут использоваться любыми другими инструкциями, находящимися между ними.
- В конце транзакции вызов инструкции возврата вернет средства смарт-контракту мгновенного займа (существование этой инструкции будет проверено в самоанализе инструкции заимствования).
Это решение достаточно хорошее, но все еще не идеальное. Самоанализ инструкций в некотором смысле является исключением, которое не часто используется в Solana, его использование требует от разработчиков понимания множества концепций, а его реализация сама по себе имеет большие технические требования, поскольку есть некоторые тонкости, которые необходимо должным образом учитывать. Существует также техническое ограничение — инструкция возврата должна статически существовать в транзакции, поэтому невозможно динамически вызывать возврат через CPI во время выполнения транзакции. Это не является большой проблемой, но при интеграции с другими смарт-контрактами это в некоторой степени ограничивает гибкость кода и переносит больше сложности на клиентскую сторону.
Move также запрещает динамическое планирование и повторные вызовы, но в отличие от Solana, у него есть очень простое и естественное решение для мгновенных займов. Линейная система типов Move позволяет создавать структуры, которые гарантируют, что они будут использованы ровно один раз в процессе выполнения транзакции. Это называется "горячий картофель" (Hot Potato) — структура без возможностей ключа, хранения, удаления или клонирования. Модули, реализующие эту модель, обычно имеют функцию инстанцирования структуры и функцию уничтожения структуры. Поскольку структура "горячий картофель" не имеет функций удаления, ключа или хранения, можно гарантировать, что ее функция уничтожения (destroy) будет вызвана, чтобы ее потребить. Хотя мы можем передавать ее в любые другие функции в любом модуле, в конечном итоге она все равно должна быть уничтожена. Поскольку нет другого способа с ней работать, и проверка требует, чтобы она была обработана в конце транзакции (ее нельзя произвольно уничтожить, поскольку нет функции удаления).
Давайте посмотрим, как это можно использовать для реализации мгновенных займов.
- Смарт-контракт мгновенного займа реализует структуру "горячий картофель" для квитанции (Receipt).
- Когда заем осуществляется через вызов функции займа, он отправляет заемщику два объекта — запрашиваемые средства (монету) и квитанцию, которая является записью о необходимости возврата суммы займа.
- Затем заемщик может использовать полученные средства для своих операций (например, арбитража).
- После завершения заемщиком своих предполагаемых операций ему необходимо вызвать функцию возврата, которая примет заимствованные средства и квитанцию в качестве параметров. Эта функция гарантированно будет вызвана в той же транзакции, поскольку у вызывающего нет другого способа избавиться от экземпляра квитанции (она не может быть уничтожена или встроена в другой объект, как требует проверка).
- Функция возврата проверяет, была ли возвращена правильная сумма, считывая информацию о займе, встроенную в квитанцию.
Безопасные характеристики ресурсов Move делают мгновенные займы возможными без необходимости использования повторных вызовов или самоанализа. Они гарантируют, что квитанция не может быть изменена ненадежным кодом и что она должна быть возвращена функции возврата в конце транзакции. Таким образом, мы можем гарантировать, что правильная сумма средств будет возвращена в той же транзакции.
Эта функция полностью реализована с использованием базовых языковых примитивов, реализация Move не будет подвержена проблемам интеграции, как это происходит в Solana, где транзакции требуют тщательной настройки. Никакая сложность не переносится на клиентскую сторону.
Мгновенные займы хорошо демонстрируют, как линейная система типов Move и безопасность ресурсов позволяют нам выражать функциональность таким образом, который невозможно реализовать в других языках программирования.
5.2. Блокировка полномочий на чеканку (Mint Authority Lock)
Смарт-контракт "Блокировка полномочий на чеканку" расширяет функции чеканки токенов, позволяя нескольким сторонним участникам (authority) чеканить токены. Необходимые функции этого смарт-контракта следующие (они применимы как для Solana, так и для Sui):
- Исходный полномочный участник по чеканке токенов создает "блокировку чеканки", что позволит нашему смарт-контракту контролировать процесс чеканки. Вызывающий становится администратором этой блокировки.
- Администратор может создавать дополнительные полномочия на чеканку для этой блокировки, которые могут быть предоставлены другим сторонам и позволяют им в любое время использовать эту блокировку для чеканки токенов.
- Каждое полномочие на чеканку имеет ограничение на количество токенов, которые могут быть чеканены в день.
- Администратор может в любой момент запретить (и снять запрет) любое полномочие на чеканку.
- Полномочия администратора могут быть переданы другой стороне.
Этот смарт-контракт может быть использован, например, для передачи полномочий на чеканку токенов другим пользователям или смарт-контрактам, в то время как исходный полномочный участник (администратор) сохраняет контроль над процессом чеканки. В противном случае нам пришлось бы передать полный контроль над чеканкой другой стороне, что не идеально, поскольку мы просто должны доверять, что она не злоупотребит этой властью. Кроме того, предоставление полномочий нескольким сторонам также невозможно.
Полные реализации этих смарт-контрактов можно найти здесь (Solana) и здесь (Sui).
Примечание: Пожалуйста, не используйте этот код в производстве! Это примерный код, предназначенный только для образовательных целей. Хотя я протестировал его функциональность, я еще не проводил тщательный аудит или тестирование безопасности.
Теперь давайте посмотрим на этот код и увидим, в чем различия в реализации. Ниже приведены полные реализации этого смарт-контракта для Solana и Sui в виде параллельных скриншотов кода.

Можно заметить, что для одной и той же функциональности реализация Solana занимает более чем в два раза больше строк, чем Sui (230 LOC против 104). Это большая проблема, поскольку меньшее количество кода обычно означает меньше ошибок и меньшее время разработки.
Так откуда же берутся эти дополнительные строки в Solana? Если мы внимательно посмотрим на код Solana, мы можем разделить его на две части — реализация инструкций (логика смарт-контракта) и проверки аккаунтов. Реализация инструкций довольно близка к тому, что мы видим в Sui — 136 строк в Solana и 104 строки в Sui. Дополнительные строки происходят от двух ссылок на вызовы CPI (каждая примерно 10 LOC). Наиболее важное различие заключается в проверках аккаунтов (выделенных красным в скриншоте выше), которые обязательны в Solana (фактически критичны), но не являются таковыми в Move. Проверки аккаунтов составляют около 40% этого смарт-контракта (91 LOC).
Move не требует проверок аккаунтов. Сокращение LOC может принести преимущества, но также крайне необходимо убрать проверки аккаунтов. Поскольку на практике оказывается, что правильная реализация этих проверок является очень сложной задачей, и если вы допустите даже одну ошибку, это часто приводит к серьезным уязвимостям и потерям средств пользователей. На самом деле некоторые из самых крупных уязвимостей смарт-контрактов Solana (в терминах потерь средств пользователей) были вызваны атаками на замену аккаунтов из-за неправильных проверок аккаунтов.
-Wormhole (336 миллионов долларов) - https://rekt.news/wormhole-rekt/
-Cashio (48 миллионов долларов) - https://rekt.news/cashio-rekt/
-Crema Finance (8.8 миллионов долларов) - https://rekt.news/crema-finance-rekt/
Так как же Move достигает безопасности без этих проверок? Давайте внимательно рассмотрим, какую реальную роль играют эти проверки. Вот проверки аккаунтов, необходимые для инструкции mint_to (владелец полномочий вызывает эту инструкцию для чеканки токенов):

Существует 6 проверок (выделенных красным):
Проверка, что предоставленный аккаунт блокировки принадлежит этому смарт-контракту и является типом MintLock. Необходимо передать блокировку, поскольку она будет использоваться для вызова CPI к токен-программе для чеканки (в которой хранятся полномочия).
Проверка, что предоставленный аккаунт полномочий на чеканку принадлежит предоставленной блокировке. Аккаунт полномочий на чеканку хранит состояние полномочий (его публичный ключ, запрещен ли он и т.д.).
Проверка, что вызывающий инструкцию владеет необходимым ключом полномочий (необходимый авторитет подписал эту транзакцию).
Необходимо передать целевой аккаунт токенов, поскольку токен-программа изменит его в вызове CPI (увеличив баланс). Проверка чеканки здесь не является строго необходимой, поскольку если передан неправильный аккаунт, вызов CPI завершится неудачей, но эта проверка все же является хорошей практикой.
Аналогично 4.
Проверка, что аккаунт токен-программы был правильно передан.
- Мы можем видеть, что проверки аккаунтов (в этом примере) делятся на пять категорий:
- Проверки прав собственности на аккаунты (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 существуют на протяжении всего смарт-контракта. Определения типов встроены в двоичный файл модуля (который публикуется на блокчейне и выполняется виртуальной машиной). Проверка будет осуществляться проверкой во время компиляции/публикации, когда наша функция вызывается, чтобы убедиться, что передаются правильные типы.
Проверки экземпляров аккаунтов — в Move (иногда также в Solana) вы будете делать это в теле функции. В этом конкретном случае это не нужно, поскольку обобщенные параметры типов lock и cap требуют правильного типа для cap (чеканки).












