Yul — это низкоуровневый промежуточный язык (IR) для EVM, который используется компилятором Solidity и подходит для «ручной» оптимизации байткода. Он ближе к ассемблеру, чем высокоуровневые языки (Solidity, Vyper), но при этом более структурирован и удобен для анализа, чем старый inline assembly.
Yul+ — расширение Yul с дополнительными конструкциями (структуры, модификаторы, сахар для циклов и т.п.), которое используют отдельные фреймворки и продвинутые команды.
Что такое Yul
Yul изначально задумывался как:
- единый промежуточный язык для разных бэкендов (EVM, eWASM и т.д.);
- «чистый» ассемблер с минимальным набором конструкций;
- формат, который удобно оптимизировать и анализировать, прежде чем превратить его в байткод.
Важные черты:
- низкоуровневый доступ к опкодам EVM;
- простая структура: блоки кода, функции, переменные через let;
- минимум синтаксического «сахара» и скрытой магии;
- удобство для оптимизатора Solidity: многие оптимизации делаются уже на уровне Yul, а не на уровне исходного Solidity-кода.
Yul против inline assembly в Solidity
До появления Yul разработчики часто использовали встроенный блок:
assembly {
// низкоуровневый код
}
Это legacy-assembly: выразительный, но трудный для анализа и оптимизации.
Yul решает несколько проблем:
- Более строгая структура
Код Yul представлен в виде имён функций, блоков и выражений. Это даёт возможность компилятору:
- лучше строить граф потока управления (CFG);
- делать дэдкод-элиминацию, инлайнинг и другие оптимизации.
- Одна модель для разных целей
IR-уровень позволяет:
- сначала скомпилировать Solidity в Yul;
- прогнать оптимизации;
- уже потом получить финальный байткод.
- Единый язык для «ручного» и автоматического кода
Разработчик может писать Yul сам, а может позволить компилятору генерировать его — в обоих случаях он попадает в один и тот же оптимизационный пайплайн.
При этом в Solidity до сих пор можно встретить оба варианта: «старый» assembly { ... } и новый синтаксис с блоком Yul. Современная практика — по возможности использовать именно Yul-стиль.
Основные элементы языка Yul
Yul остаётся языком низкого уровня, но в нём есть базовые высокоуровневые конструкции:
- Переменные и let
Переменные объявляются через let, например:
```text
let x := add(1, 2)
```
Значения — это 256-битные слова, с которыми работает EVM.
- Функции
Можно объявлять функции, которые компилятор потом развернёт или оставит как подпрограммы:
```text
function addTwo(a, b) -> c {
c := add(a, b)
}
```
- Условные конструкции
Простые if и switch без сложной логики типов:
```text
if iszero(x) {
// ...
}
switch opcode
case 0 { /* ... */ }
default { /* ... */ }
```
- Циклы
Есть конструкция for { init } condition { step } { body }, но без «бесконечных» динамических паттернов, опасных с точки зрения газа.
- Вызов встроенных примитивов
Для работы с памятью, storage и окружением используются встроенные функции-обёртки над опкодами:
- mload, mstore, sload, sstore;
- call, delegatecall, staticcall;
- calldataload, codesize, gas и т.п.
Все типы данных на уровне Yul фактически приводятся к 256-битным словам, как в EVM, — это важно понимать при оптимизации и проверке границ значений.
Где используется Yul на практике
- Внутри компилятора Solidity
При включённых оптимизациях (--optimize) компилятор часто:
- преобразует часть Solidity-кода в Yul IR;
- прогоняет оптимизации (упрощение выражений, удаление мёртвого кода, инлайнинг функций, оптимизацию работы с памятью и storage);
- генерирует байткод.
Разработчик может этого не замечать: Yul работает «за кулисами».
- Ручная оптимизация газа
Продвинутые команды иногда пишут отдельные модули напрямую на Yul:
- математические ядра;
- маршрутизацию селекторов функций;
- минимальные прокси-шаблоны;
- части DeFi-примитивов, критичных к газу.
- Системные и инфраструктурные контракты
Низкоуровневые компоненты (мосты, precompile-обёртки, кастомные маршрутизаторы) могут выигрывать от полного контроля над байткодом.
- Yul+ и фреймворки
Yul+ добавляет к базовому Yul более удобный синтаксис и конструкции:
- структуры;
- дополнительные циклы;
- более удобную работу с массивами и storage.
Это не «официальный» стандарт протокола, а практика отдельных проектов и тулчейнов, но влияние Yul+ заметно в экосистеме оптимизированных контрактов.
Преимущества Yul и когда он реально нужен
Использовать Yul имеет смысл, когда:
- важна максимальная экономия газа
Например, вы пишете ядро протокола, которое вызывается миллионы раз, или контракты, где каждая единица газа критична.
- нужен полный контроль над байткодом
Solidity иногда генерирует обвязку и дополнительные проверки. На Yul можно:
- точно контролировать последовательность опкодов;
- управлять layout’ом памяти и storage;
- реализовать нетипичные паттерны, которые трудно выразить на высокоуровневом языке.
- строятся продвинутые low-level-конструкции
Например:
- собственные прокси-шаблоны;
- кастомные роутеры вызовов;
- bridge-логика, тесно работающая с calldata, returndata и внешними вызовами.
Для «обычных» контрактов (ERC-20, NFT, простые DAO) типов «как по учебнику» чаще всего достаточно Solidity: выигрыш от Yul не окупит рост сложности.
Риски и сложности при работе с Yul
Yul даёт большую мощь, но требует высокой дисциплины:
- Вы теряете часть защитных механизмов Solidity
Нет автоматических проверок переполнения (если не добавить их вручную), нет безопасных абстракций уровня языка, меньше высокоуровневых предупреждений компилятора.
- Сложнее отладка
Отлаживать Yul-код приходится:
- по трассам исполнения (traces);
- через анализ опкодов и переменных в дебаггерах;
- по логам и событиям.
Это больше похоже на отладку ассемблера, чем высокоуровневого языка.
- Выше порог входа для команды и аудиторов
Не каждый Solidity-разработчик комфортно чувствует себя на Yul, а аудит такого кода требует отдельной экспертизы. Ошибки могут быть менее очевидны, чем в привычном Solidity.
- Меньше документации и примеров
Вокруг Solidity уже сформировался огромный массив гайдов, статей и шаблонов. По Yul и Yul+ информации меньше, многие паттерны живут только в коде конкретных проектов.
Поэтому в большинстве случаев подход таков: сначала пишут и отлаживают протокол на Solidity, а потом *точечно* переписывают самые горячие участки в Yul, если профилирование показывает, что это оправдано.
