На какие еще языки EVM, помимо Solidity, стоит обратить внимание?
Автор: jtriley.ethjtriley.eth
Составил: 0x11, Новости Форсайта
Виртуальная машина Ethereum (EVM) — это 256-битная стековая глобально доступная машина Тьюринга. Поскольку архитектура существенно отличается от архитектуры других виртуальных и физических машин, EVM требует DSL языка, специфичного для предметной области (примечание: язык, специфичный для предметной области, относится к компьютерному языку, который ориентирован на определенный домен приложения).
В этой статье мы рассмотрим современное состояние проектирования EVM DSL, охватывающее шесть языков: Solidity, Vyper, Fe, Huff, Yul и ETK.
Языковая версия
Прочность: 0,8,19
Вайпер: 0.3.7
Железо: 0,21,0
Хафф: 0.3.1
ЭТК: 0.2.1
Юл: 0.8.19
Чтение этой статьи требует от вас базового понимания EVM, стека и программирования.
Обзор виртуальной машины Ethereum
EVM — это 256-битная машина Тьюринга на базе стека. Однако прежде чем углубляться в его компилятор, следует познакомиться с некоторыми функциональными особенностями.
Поскольку EVM является «полной по Тьюрингу», она будет страдать от «проблемы остановки». Короче говоря, перед выполнением программы невозможно определить, завершится ли она в будущем. Способ, которым EVM решает эту проблему, заключается в измерении единиц расчета через «Газ», который обычно пропорционален физическим ресурсам, необходимым для выполнения инструкций. Количество газа на транзакцию ограничено, и инициатор транзакции должен заплатить ETH пропорционально газу, потребленному транзакцией. Одним из последствий этой стратегии является то, что если есть два функционально идентичных смарт-контракта, тот, который потребляет меньше газа, будет более принят. Это приводит к тому, что протоколы конкурируют за максимальную эффективность использования газа, а инженеры стремятся минимизировать потребление газа для конкретных задач.
Кроме того, когда вызывается контракт, он создает контекст выполнения. В этом контексте контракт имеет стек для операций и обработки, экземпляр линейной памяти для чтения и записи, локальное постоянное хранилище для чтения и записи контракта, а данные, прикрепленные к вызову «calldata», могут быть прочитаны, но не записаны. .
Важное замечание относительно памяти: хотя не существует определенного «верхнего предела» ее размера, он все же ограничен. Стоимость газа для расширения памяти является динамической: после достижения порогового значения стоимость расширения памяти увеличивается квадратично, а это означает, что стоимость газа пропорциональна квадрату выделения дополнительной памяти.
Контракты также могут вызывать другие контракты, используя ряд различных инструкций. Инструкция «вызов» отправляет данные и необязательный ETH в целевой контракт, затем создает свой собственный контекст выполнения, пока выполнение целевого контракта не остановится. Директива staticcall аналогична директиве call, но добавляет проверку, подтверждающую, что никакая часть глобального состояния не была обновлена до завершения статического вызова. Наконец, директива «delegatecall» ведет себя как «call», за исключением того, что она сохраняет некоторую информацию об окружении из предыдущего контекста. Обычно это используется для внешних библиотек и прокси-контрактов.
Почему языковой дизайн имеет значение
Доменно-специфичные языки (DSL) необходимы при взаимодействии с нетипичными архитектурами. Хотя существуют наборы инструментов компилятора, такие как LLVM, полагаться на них для обработки смарт-контрактов далеко не идеально в ситуациях, когда корректность программы и эффективность вычислений имеют решающее значение.
Корректность программы важна, поскольку смарт-контракты по умолчанию неизменяемы и являются популярным выбором для финансовых приложений, учитывая свойства виртуальных машин (ВМ) блокчейна. Хотя для EVM существует обновляемое решение, в лучшем случае это патч, а в худшем — уязвимость, связанная с выполнением произвольного кода.
Эффективность вычислений также имеет решающее значение, поскольку минимизация вычислений имеет экономические преимущества, но не в ущерб безопасности.
Короче говоря, EVM DSL должен балансировать между корректностью программы и эффективностью газа, достигая того или другого за счет различных компромиссов, не жертвуя при этом слишком большой гибкостью.
Обзор языка
Для каждого языка мы опишем их основные особенности и варианты дизайна, а также добавим простой смарт-контракт с функцией подсчета. Вербальная популярность определяется на основе данных Total Value Locked (TVL) по Defi Llama.
Прочность
Solidity — это язык высокого уровня, синтаксис которого похож на C, Java и Javascript. Это самый популярный язык по версии TVL: TVL в десять раз выше, чем у языка, занявшего второе место. Для повторного использования кода используется объектно-ориентированный шаблон, в котором смарт-контракты рассматриваются как объекты классов, используя множественное наследование. Компилятор написан на C++, в будущем планируется перейти на Rust.
Изменяемые поля контракта хранятся в постоянном хранилище, если их значения не известны во время компиляции (константы) или во время развертывания (неизменяемые). Методы, объявленные в контракте, могут быть объявлены чистыми, просмотренными, платными или неоплачиваемыми по умолчанию, но с изменяемым статусом. Чистые методы не считывают данные из среды выполнения и не могут читать или записывать в постоянное хранилище, то есть при одних и тех же входных данных чистые методы всегда будут возвращать одни и те же выходные данные и не производят никаких побочных эффектов. Методы представления могут считывать данные из постоянного хранилища или среды выполнения, но они не могут записывать в постоянное хранилище и не могут создавать побочные эффекты, такие как добавление журналов транзакций. Платные методы могут читать и записывать в постоянное хранилище, считывать данные из среды выполнения, создавать побочные эффекты и могут получать ETH, прикрепленный к вызову. Неоплачиваемый метод аналогичен платному методу, но имеет проверку во время выполнения, чтобы убедиться, что в текущем контексте выполнения нет прикрепленных ETH.
ПРИМЕЧАНИЕ. Прикрепление ETH к транзакции отличается от оплаты комиссии за газ. Прикрепленный ETH принимается контрактом, и вы можете принять или отклонить его, восстановив контекст.
При объявлении в рамках контракта методы могут указывать четыре модификатора видимости: частный, внутренний, общедоступный или внешний. Доступ к частным методам можно получить изнутри с помощью инструкции перехода в текущем контракте. Ни один унаследованный контракт не может напрямую обращаться к частным методам. Доступ к внутренним методам также возможен внутри с помощью инструкции перехода, но унаследованные контракты могут использовать внутренние методы напрямую. Доступ к общедоступным методам можно получить из внешних контрактов через инструкцию «вызов», которая создает новый контекст выполнения, а также внутри через переходы при прямом вызове метода. Доступ к общедоступным методам также можно получить из того же контракта в новом контексте выполнения, добавив «this» к вызову метода. Доступ к внешним методам возможен только через инструкцию «вызов». Независимо от того, находятся ли они в разных контрактах или в одном контракте, перед вызовом метода необходимо добавить «this».
Примечание. Инструкция «переход» управляет счетчиком программ, а инструкция «вызов» создает новый контекст выполнения во время выполнения целевого контракта. Если возможно, используйте «прыжок» вместо «вызов», чтобы сэкономить газ.
Solidity также предоставляет три способа определения библиотек. Первая — это внешняя библиотека, представляющая собой контракт без сохранения состояния, который развертывается отдельно в цепочке, динамически связывается при вызове контракта и доступен через инструкцию «delegatecall». Это наименее распространенный подход, поскольку инструментальная поддержка внешних библиотек недостаточна, «делегатный вызов» дорог, он должен загружать дополнительный код из постоянного хранилища и требует нескольких транзакций для развертывания. Внутренние библиотеки определяются так же, как и внешние библиотеки, за исключением того, что каждый метод должен быть определен как внутренний метод. Во время компиляции внутренняя библиотека встраивается в окончательный контракт, а на этапе анализа мертвого кода неиспользуемые методы библиотеки удаляются. Третий способ аналогичен внутренней библиотеке, но вместо определения структур данных и функций внутри библиотеки они определяются на уровне файла и могут быть напрямую импортированы и использованы в окончательном контракте. Третий подход обеспечивает лучшее взаимодействие человека и компьютера за счет использования пользовательских структур данных, применения функций в глобальной области и применения операторов псевдонимов к определенным функциям в ограниченной степени.
Компилятор обеспечивает два прохода оптимизации. Первый — это оптимизатор на уровне инструкций, который выполняет операции оптимизации конечного байт-кода. Второй — недавнее добавление использования языка Yul (подробнее об этом позже) в качестве промежуточного представления (IR) во время процесса компиляции с последующим выполнением операций оптимизации над сгенерированным кодом Yul.
Для взаимодействия с общедоступными и внешними методами в контракте Solidity определяет стандарт двоичного интерфейса приложения (ABI) для взаимодействия со своими контрактами. В настоящее время Solidity ABI считается стандартом де-факто для EVM DSL. Стандарты Ethereum ERC, определяющие внешние интерфейсы, реализованы в соответствии со спецификацией и руководством по стилю ABI Solidity. Другие языки также следуют спецификации ABI Solidity с очень небольшими отклонениями.
Solidity также предоставляет встроенные блоки Yul, обеспечивающие низкоуровневый доступ к набору инструкций EVM. Блок Yul содержит подмножество функций Yul, подробности см. в разделе Yul. Обычно это используется для оптимизации газа, использования функций, не поддерживаемых синтаксисом более высокого уровня, а также настройки хранилища, памяти и данных вызовов.
Благодаря популярности Solidity, инструменты разработчика очень зрелые и хорошо спроектированные, и Foundry выделяется в этом отношении.
Ниже приведен простой контракт, написанный на Solidity:

Вайпер
Vyper — это язык высокого уровня, синтаксис которого похож на Python. Это почти подмножество Python с несколькими незначительными отличиями. Это второй по популярности EVM DSL. Vyper оптимизирован с точки зрения безопасности, читаемости, возможности аудита и эффективности использования газа. Он не использует объектно-ориентированные шаблоны, встроенную сборку и не поддерживает повторное использование кода. Его компилятор написан на Python.
Переменные, хранящиеся в постоянном хранилище, объявляются на уровне файла. Если их значение известно во время компиляции, они могут быть объявлены как «постоянные»; если их значение известно во время развертывания, они могут быть объявлены как «неизменяемые», если они помечены. Если они общедоступны, окончательный контракт будет открыт; функция только для чтения для переменной. Доступ к значениям констант и инвариантов осуществляется изнутри через их имена, но доступ к изменяемым переменным в постоянном хранилище можно получить, добавив к имени «self». Это полезно для предотвращения конфликтов пространства имен между хранимыми переменными, параметрами функции и локальными переменными.
Подобно Solidity, Vyper также использует атрибуты функций для представления видимости и изменчивости функций. Доступ к функциям с пометкой «@external» можно получить из внешних контрактов с помощью инструкции «вызов». Доступ к функциям с пометкой «@internal» возможен только в рамках одного и того же контракта, и им необходимо иметь префикс «self». Функции с пометкой «@pure» не могут читать данные из среды выполнения или постоянного хранилища, записывать в постоянное хранилище или создавать какие-либо побочные эффекты. Функции с пометкой «@view» могут считывать данные из среды выполнения или постоянного хранилища, но не могут записывать в постоянное хранилище или создавать побочные эффекты. Функции с пометкой «@payable» могут читать или записывать в постоянное хранилище, создавать побочные эффекты и получать ETH. Функции, которые не объявляют этот атрибут изменяемости, по умолчанию имеют значение «неоплачиваемые», то есть они аналогичны оплачиваемым функциям, но не могут получать ETH.
Компилятор Vyper также предпочитает хранить локальные переменные в памяти, а не в стеке. Это делает контракты проще и эффективнее, а также решает проблему «слишком глубокого стека», распространенную в других языках высокого уровня. Однако это также связано с некоторыми компромиссами.
Кроме того, поскольку структура памяти должна быть известна во время компиляции, максимальная емкость динамических типов также должна быть известна во время компиляции, что является ограничением. Кроме того, выделение больших объемов памяти может привести к нелинейному потреблению газа, как упоминалось в разделе обзора EVM. Однако во многих случаях стоимость газа незначительна.
Хотя Vyper не поддерживает встроенную сборку, он предоставляет больше встроенных функций, благодаря которым почти все функции Solidity и Yul также доступны в Vyper. Доступ к низкоуровневым битовым операциям, внешним вызовам и операциям прокси-контракта можно получить через встроенные функции, а пользовательские схемы хранения можно реализовать путем предоставления файлов наложения во время компиляции.
У Vyper нет богатого набора инструментов разработки, но есть инструменты, которые более тесно интегрированы и могут подключаться к инструментам разработки Solidity. Известные инструменты Vyper включают интерпретатор Titanaboa, который имеет множество встроенных инструментов, связанных с EVM и Vyper, для экспериментов и разработки, а также Dasy, Lisp на основе Vyper с выполнением кода во время компиляции.
Вот простой контракт, написанный на Vyper:

Фе
Fe — это язык высокого уровня, такой как Rust, который в настоящее время находится в активной разработке, и большинство функций еще не доступны. Его компилятор в основном написан на Rust, но использует Yul в качестве промежуточного представления (IR), полагаясь на оптимизатор Yul, написанный на C++. Ожидается, что ситуация изменится с добавлением Sonatina, собственного бэкэнда Rust. Fe использует модули для совместного использования кода, поэтому он не использует объектно-ориентированный шаблон, а повторно использует код через систему, основанную на модулях, где переменные, типы и функции объявляются внутри модулей и могут быть импортированы аналогично Rust.
Переменные постоянного хранилища объявляются на уровне контракта и не являются общедоступными без определенных вручную функций получения. Константы могут быть объявлены на уровне файла или модуля и доступны внутри контракта. Неизменяемые переменные времени развертывания в настоящее время не поддерживаются.
Методы могут быть объявлены на уровне модуля или внутри контракта и по умолчанию являются чистыми и закрытыми. Чтобы сделать метод контракта общедоступным, перед определением необходимо добавить ключевое слово «pub», что делает его доступным извне. Для чтения из переменной постоянного хранилища первым параметром метода должно быть «self». Добавьте «self» перед именем переменной, чтобы предоставить методу доступ только для чтения к переменной локального хранилища. Для чтения и записи в постоянное хранилище первым параметром должно быть «mut self». Ключевое слово «mut» указывает, что хранилище контракта может быть изменено во время выполнения метода. Доступ к переменным среды осуществляется путем передачи параметра «Контекст» методу, обычно называемому «ctx».
Функции и пользовательские типы могут быть объявлены на уровне модуля. По умолчанию элементы модуля являются частными и недоступны, если не используется ключевое слово «pub». Однако не следует путать с ключевым словом «pub» на уровне контракта. Доступ к открытым членам модуля возможен только в окончательном контракте или других модулях.
Fe в настоящее время не поддерживает встроенную ассемблерную сборку, вместо этого инструкции заключаются в встроенные функции компилятора или специальные функции, которые преобразуются в инструкции во время компиляции.
Fe следует синтаксису и системе типов Rust, поддерживая псевдонимы типов, перечисления с подтипами, свойства и обобщения. Поддержка этого в настоящее время ограничена, но над этим работают. Признаки могут быть определены и реализованы для разных типов, но дженерики и ограничения признаков не поддерживаются. Перечисления поддерживают подтипы, и в них можно реализовать методы, но их нельзя закодировать во внешних функциях. Хотя система типов Fe все еще находится в стадии разработки, она демонстрирует большой потенциал для написания более безопасного кода, проверяемого во время компиляции для разработчиков.
Вот простой контракт, написанный на Fe:

Хафф
Huff — это язык ассемблера с ручным управлением стеком и минимальной абстракцией набора инструкций EVM. С помощью директивы#includeлюбые включенные файлы Huff могут быть проанализированы во время компиляции для обеспечения повторного использования кода. Первоначально написанный командой Aztec для чрезвычайно оптимизированных алгоритмов эллиптических кривых, компилятор позже был переписан на TypeScript, а затем на Rust.
Константы должны быть определены во время компиляции, неизменяемые объекты в настоящее время не поддерживаются, а постоянные переменные хранилища не определены явно в языке. Поскольку именованные переменные хранилища представляют собой абстракцию высокого уровня, запись в постоянное хранилище в Huff осуществляется с помощью кодов операций «sstore» для записи и «sload» для чтения. Пользовательские схемы хранения могут быть определены пользователем или вы можете следовать соглашению о начале работы с нуля и увеличении каждой переменной, используя встроенную функцию компилятора «FREE_STORAGE_POINTER». Чтобы сделать хранимую переменную доступной извне, необходимо вручную определить путь кода, который может читать и возвращать переменную вызывающему объекту.
Внешние функции также являются абстракциями, введенными в языках высокого уровня, поэтому в Хаффе нет понятия внешних функций. Однако большинство проектов в той или иной степени следуют спецификациям ABI других языков высокого уровня, чаще всего Solidity. Распространенным шаблоном является определение «диспетчера», который загружает необработанные данные вызова и использует их для проверки соответствия селекторов функций. Если оно соответствует, выполняется его последующий код. Поскольку планировщики определяются пользователем, они могут использовать разные шаблоны планирования. Solidity сортирует селекторы в своих планировщиках в алфавитном порядке по имени, Vyper сортирует их по номерам и выполняет двоичный поиск во время выполнения, а большинство планировщиков Huff сортируют по ожидаемой частоте использования функций, редко используя таблицы переходов. В настоящее время таблицы переходов не поддерживаются в EVM изначально, поэтому для их реализации необходимо использовать инструкции самоанализа, такие как «codecopy».
Внутренние функции определяются с помощью директивы#definefn, которая может принимать параметры шаблона для повышения гибкости и указывать ожидаемую глубину стека в начале и конце функции. Поскольку эти функции являются внутренними, доступ к ним извне невозможен. Внутренний доступ требует использования инструкции перехода.
Другие потоки управления, такие как условные операторы и операторы цикла, могут использовать определения целей перехода. Цель перехода определяется идентификатором, за которым следует двоеточие. Переходы к этим целям можно выполнить, поместив идентификатор в стек и выполнив команду перехода. Это разрешается в смещение байт-кода во время компиляции.
Макросы определяются с помощью #definemacro» и в остальном аналогичны внутренним функциям. Ключевое отличие состоит в том, что макрос не генерирует инструкцию «перехода» во время компиляции, а вместо этого копирует тело макроса непосредственно в каждый вызов в файле.
Эта конструкция позволяет снизить произвольные скачки и снизить затраты газа во время выполнения за счет увеличения размера кода при многократном вызове. Макрос «MAIN» считается точкой входа в контракт, и первая инструкция в его теле станет первой инструкцией в байт-коде времени выполнения.
Другие функции, встроенные в компилятор, включают генерацию хэша событий для регистрации, селекторы функций для планирования, селекторы ошибок для обработки ошибок и средства проверки размера кода для внутренних функций и макросов.
Примечание. Комментарии стека, такие как «//[count]», не требуются, они используются только для указания состояния стека в конце выполнения строки.
Вот простой контракт, написанный на языке Huff:

ЕТК
EVM Toolkit (ETK) — это язык ассемблера с ручным управлением стеком и минимальными абстракциями. Код можно повторно использовать с помощью директив «%include» и «%import», а компилятор написан на Rust.
Одно существенное различие между Huff и ETK заключается в том, что Huff добавляет небольшую абстракцию к коду инициализации, также известную как код конструктора, который можно переопределить, определив специальный макрос «CONSTRUCTOR». В ETK они не абстрагированы, код инициализации и код времени выполнения должны определяться вместе.
Подобно Хаффу, ETK читает и записывает в постоянное хранилище с помощью инструкций «sload» и «sstore». Однако ключевого слова «константа» или «неизменяемый» не существует, но константы можно моделировать с помощью одного из двух макросов в ETK — макроса выражения. Макросы выражений не преобразуются в инструкции, а вместо этого генерируют числовые значения, которые можно использовать в других инструкциях. Например, он может не генерировать команду «push» полностью, но может генерировать число для включения в команду «push».
Как упоминалось ранее, внешние функции — это концепция языка высокого уровня, поэтому для внешнего представления пути кода требуется создание планировщика селектора функций.
Внутренние функции не определены явно, как в других языках. Вместо этого для целей перехода могут быть заданы определяемые пользователем псевдонимы и переходы к ним по их именам. Это также позволяет использовать другие потоки управления, такие как циклы и условные операторы.
ETK поддерживает два типа макросов. Первый — это макрос выражения, который принимает любое количество аргументов и возвращает числовое значение, которое можно использовать в других инструкциях. Макросы выражений не генерируют инструкции, а вместо этого генерируют непосредственные значения или константы. Однако макросы директив принимают любое количество аргументов и генерируют любое количество директив во время компиляции. Макросы инструкций в ETK аналогичны макросам Huff.
Ниже приведен простой контракт, написанный на ETK:

Юл
Yul — это ассемблер с высокоуровневым потоком управления и большим количеством абстракций. Он является частью набора инструментов Solidity и при необходимости может использоваться в проходах сборки Solidity. Yul не поддерживает повторное использование кода, поскольку он предназначен для компиляции, а не как отдельный язык. Его компилятор написан на C++, и есть планы перенести его на Rust вместе с остальной частью канала Solidity.
В Yul код разделен на объекты, которые могут содержать код, данные и вложенные объекты. Поэтому в Юле нет констант и внешних функций. Диспетчеры селекторов функций должны быть определены, чтобы предоставлять пути кода внешнему миру.
За исключением инструкций стека и потока управления, большинство инструкций в Yul представлены как функции. Инструкции могут быть вложенными для сокращения длины кода или присвоены временным переменным, а затем переданы другим инструкциям для использования. Условные ветки могут использовать блок «if», который выполняется, если значение не равно нулю, но нет блока «else», поэтому обработка нескольких путей кода требует использования «переключателя» для обработки любого количества случаев и запасной вариант «по умолчанию». Циклы могут выполняться с использованием цикла «for», хотя его синтаксис отличается от синтаксиса других языков высокого уровня, он обеспечивает те же базовые функции. Внутренние функции могут быть определены с помощью ключевого слова «function» и аналогичны определениям функций в языках высокого уровня.
Большая часть функциональности Yul реализуется в Solidity с помощью встроенных блоков сборки. Это позволяет разработчикам ломать абстракции и писать собственные функции или использовать Yul для функций, недоступных в синтаксисе высокого уровня. Однако использование этой функции требует глубокого понимания поведения Solidity в отношении данных вызовов, памяти и хранилища.
Есть также несколько уникальных функций. Функции datasize, dataoffset и datacopy работают с объектами Yul через их строковые псевдонимы. Функции setimmutable и loadimmutable позволяют устанавливать и загружать неизменяемые параметры в конструкторе, хотя их использование ограничено. Функция «memoryguard» указывает, что выделяется только заданный диапазон памяти, что позволяет компилятору использовать память за пределами защищенного диапазона для дополнительных оптимизаций. Наконец, «дословно» позволяет использовать инструкции, неизвестные компилятору Yul.
Вот простой контракт, написанный на Yul:

Особенности хорошего EVM DSL
Хороший EVM DSL должен учитывать плюсы и минусы каждого из перечисленных здесь языков, а также включать в себя основы, имеющиеся почти во всех современных языках, такие как условные выражения, сопоставление с образцом, циклы, функции и многое другое. Код должен быть явным, с добавлением минимума неявных абстракций ради красоты или читабельности кода. В средах с высокими ставками и критической корректностью каждая строка кода должна быть однозначно интерпретируемой. Более того, четко определенная система модулей должна лежать в основе любого хорошего языка. В нем должно быть четко указано, какие элементы определены, в какой области и какие из них доступны. Каждый элемент в модуле по умолчанию должен быть частным, и только явно общедоступные элементы должны быть общедоступными извне.
В среде с ограниченными ресурсами, такой как EVM, эффективность важна. Эффективность часто достигается за счет предоставления недорогих абстракций, таких как выполнение кода во время компиляции с помощью макросов, богатой системы типов для создания хорошо спроектированных повторно используемых библиотек и оболочек для общих взаимодействий в цепочке. Макросы генерируют код во время компиляции, что полезно для сокращения шаблонного кода для общих операций, а также в случаях, подобных Хаффу, когда его можно использовать для достижения компромисса между размером кода и эффективностью времени выполнения. Богатая система типов позволяет создавать более выразительный код, проводить больше проверок во время компиляции для выявления ошибок перед выполнением, а в сочетании с внутренними функциями компилятора с проверкой типов может устранить необходимость в большей части встроенной сборки. Дженерики также позволяют оборачивать значения, допускающие значение NULL (например, внешний код), в типы «опций» или операции, подверженные ошибкам (например, внешние вызовы), в типы «результат». Эти два типа являются примерами того, как авторы библиотек заставляют разработчиков обрабатывать каждый результат, определяя пути кода или транзакции, которые восстанавливают неудачные результаты. Однако имейте в виду, что это абстракции времени компиляции, которые во время выполнения разрешаются в простые условные переходы. Принуждение разработчиков обрабатывать каждый результат во время компиляции увеличивает начальное время разработки, но преимущество состоит в том, что во время выполнения возникает гораздо меньше сюрпризов.
Гибкость также важна для разработчиков, поэтому, хотя по умолчанию для сложных операций должен использоваться безопасный и потенциально менее эффективный маршрут, иногда необходимо использовать более эффективные пути кода или неподдерживаемые функции. Для этого встроенная сборка должна быть открыта для разработчиков без каких-либо ограничений. Встроенная сборка Solidity устанавливает некоторые ограничения для простоты и улучшения проходов оптимизатора, но когда разработчикам необходим полный контроль над средой выполнения, им следует предоставить эти права.
Некоторые потенциально полезные функции включают возможность манипулировать свойствами функций и других элементов во время компиляции. Например, атрибут «inline» позволяет копировать тело простой функции в каждый вызов вместо создания дополнительных переходов для повышения эффективности. Атрибут «abi» позволяет вручную переопределить ABI, сгенерированный данной внешней функцией, для адаптации к языкам с разными стилями кодирования. Кроме того, можно определить дополнительный планировщик функций, позволяющий настраивать язык высокого уровня для дополнительной оптимизации путей кода, которые, как ожидается, будут использоваться чаще. Например, перед выполнением «name» проверьте, установлен ли селектор «transfer» или «transferFrom».
в заключение
Проектированию EVM DSL предстоит пройти долгий путь. Каждый язык имеет свои уникальные дизайнерские решения, и я с нетерпением жду возможности увидеть, как они будут развиваться в будущем. В наших интересах как разработчиков выучить как можно больше языков. Во-первых, изучение нескольких языков и понимание их различий и сходств углубит наше понимание программирования и базовой машинной архитектуры. Во-вторых, язык обладает глубокими сетевыми эффектами и сильными свойствами удержания. Нет сомнений в том, что крупные игроки создают свои собственные языки программирования: от C#, Swift и Kotlin до Solidity, Sway и Cairo. Обучение плавному переключению между этими языками обеспечивает беспрецедентную гибкость в карьере инженера-программиста. Наконец, важно понимать, что за каждым языком стоит большая работа. Никто не идеален, но бесчисленное множество талантливых людей приложили немало усилий, чтобы создать безопасный и приятный опыт для таких разработчиков, как мы.
