Solidity 0.5.0 — что нового он нам несет

habr.png

Хочу рассказать об изменениях языка Solidity, которые ожидаются в версии 0.5.0. Сразу отмечу, что я ограничусь только языком — его грамматикой и семантикой.

Какого-то вменяемого текста на эту тему нет даже на английском языке, но недавно в репозитории Solidity появился проект.
По нему можно отслеживать прогресс подготовки версии 0.5.0.

Disclaimer: в статье описано текущее состояние проекта, к релизу многое может поменяться. Точную информацию можно получить из официального changelog’a.


Окончательный запрет устаревших конструкций

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


  • Отказ от throw в пользу revert()/assert(...)/require(...). Механизм откатывания транзакции отличается от механизма исключений, и хочется подчеркнуть эту разницу на уровне языка.
  • Отказ от var, который с учетом правил вывода типов легко приводит к ошибкам, например:
    for(var i = 0; i < 100500; i++) { ... // бесконечный цикл
    Дополнительно обсуждают, на что заменить конструкции вроде
    var (z, y, z) = foo();
    и как поэлегантнее пропускать ненужные значения.
  • Запретили использовать constant для функций — всюду должно быть view или pure.
  • Встроенная функция gasleft() вместо msg.gas. Так понятнее, что это не какая-то константа, а оставшееся количество газа. Да, gasleft() можно переопределить.
  • Перенесли block.blockhash в blockhash. Логично, ведь blockhash текущего блока недоступен (block.blockhash(block.number) == 0).
  • Запретили смешивать шестнадцатеричные константы и множители времени/эфира. В самом деле, не совсем понятно, что такое 0xaf days.
  • Отказались от suicide/sha3 в inline assembly. Не понятно, почему вне assembly эти конструкции по-прежнему доступны, хотя и deprecated.
  • Отказ от унарного плюса, потому что он не имеет какой-то специальной роли и может участвовать в глупых ошибках (вроде x=+1; вместо x+=1;).
  • Планируется отказ от множителя years, потому что сейчас он определен как 365 days, и это не слишком точно соотносится с привычным календарем. Мне это представляется спорным решением — казалось бы, пока можно ограничиться предупреждением.


Более строгий синтаксис

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


  • Обязательное ключевое слово emit при генерации событий.
    Создание эвента синтаксически не отличалось от вызова функции (и создания структуры). Особенно ярко эта проблема проявлялась в стандарте ERC20, в котором есть функция и эвент с одинаковой «сигнатурой».
  • Новый синтаксис для конструкторов:
    constructor() public { ....
    С выходом 0.5.0 это будет единственный вариант — функции, чьи имена совпадают с контрактом, запрещены.
    Это изменение решает проблему переименования файлов, когда конструктор внезапно превращается в обычную функцию и его можно вызывать повторно или не вызывать вовсе.
    Более обоснованным это решение выглядит, если вспомнить, что констрктор может быть только public или internal, не может быть view или pure, не может иметь возвращаемых значений, т.е. является особой сущностью.
    Любопытно, что при этом было открыто issue с предложением разрешить одноименные контракту функции в 0.6.0.
  • Изменение правил видимости локальных переменных — с принятых в Javascript на C99/C++.
    Мое любимое. Ничего не могу с собой сделать, каждый раз радуюсь, когда показываю, как компилируется код
    function foo() { x = 1; revert(); uint x; }
  • Принудительное включение строгого режима для ассемблера. Он и сейчас доступен с опцией компилятора --strict-assembly. В нем ограничены манипуляции со стеком и недоступны метки и переходы — вместо них предлагается использовать более привычные управляющие конструкции вроде for или switch.
  • Запретили использовать адреса без чек-суммы или неправильной длины (отличной от 20 байт). Неплохая идея, хотя с непривычки и вызовет трудности — придется срочно учиться писать адреса с чек-суммой, а вместо привычного 0x0 использовать address(0).
  • Запретили объявлять пустые структуры (struct A {}). Не очень-то и хотелось, но раньше грамматика такое позволяла.


Модификаторы видимости и ABI

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


  • Все функции в интерфейсах надо явно помечать как external.
    К сожалению, переопределить потом такую функцию как public в некоторых версиях компилятора нельзя — возникнет ошибка (например, в 0.4.21). Это стало возможным начиная с версии 0.4.22.
    Также обещают разрешить реализовывать external view функции с помощью public переменных.
  • Модификаторы видимости стали обязательными для функций.
    Это изменение долго ждало своего часа. Issue на гитхабе создали сразу после первого хака Parity.
    Дополнительное ограничение — fallback может быть только external.


Функции с произвольным количеством аргументов и упаковка данных

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


  • Новый глобальный объект abi и его методы
    encode, encodePacked, encodeWithSelector и encodeWithSignature, позволяющие контролировать сборку данных для вызова или хэширования.
    Предлагается с их помощью собирать аргументы для keccak256/sha256/ripemd160 и call/delegatecall. Планируется поменять синтаксис этих команд так, чтобы они не могли принимать список аргументов произвольной длины.
  • Изменение в автовыведении типов для констант в конструкциях с плотной упаковкой аргументов: в конструкциях вроде keccak256(1) теперь используется не наименьший достаточный тип (uint8), а uint256. Если такое поведение не устраивает, то придется использовать явное приведение типов (keccak256(uint8(1))).
    Это изменение выглядит логично рядом с отказом от var и высокой (хотя и конечной) точностью вычислений константных выражений.
  • Изменение правил упаковки массивов. Началось все с проблемы мультисиг кошельков, которые не могли выполнять транзакции контрактов, пытавшихся защититься от short address attack. Видимо, текущее поведение посчитали достаточно неочевидным, а может, это подготовка к введению проверки длины msg.data на уровне EVM.


Улучшение «работы с памятью»

В кавычках, потому что речь в основном идет о «неожиданном» доступе к storage без использования assembly.


  • Запрет непроинициализированных ссылок на storage, которые на деле указывали в начало storage, и поэтому пересекались с другими переменными состояния.
  • Может быть запретят прямую работу с .length.
    Изменение длины массива вручную — достаточно низкоуровневая операция. Ее основной плюс — экономия газа при небольших изменениях длины массива. Но такой синтаксис позволяет случайно (или умышленно) создать массив неадекватного размера, что может привести к overlap attack. Для очистки массива давно есть delete, для уменьшения размера предлагается использовать pop(), помимо этого обсуждают операции вроде truncate() и extend(). Ну и по-прежнему есть assembly, если очень надо.


Из сомнительного

В любой бочке меда окажется своя ложка дегтя.


  • Добавляют целую вязанку ключевых слов на все случаи жизни. Часть из них выглядит угрожающе, но пока ничего не понятно. Честно говоря, надеялся увидеть в этом списке revert, assert и require, но с точки зрения грамматики они остаются просто функциями (и их можно переопределить).


Не про язык

Есть несколько очень важных изменений, которые не касаются непосредственно языка, но при этом сильно повлияют на код новых контрактов.


  • Наконец-то появятся числа с дробной частью. Вроде все привыкли обходиться без них, а теперь будем учиться использовать их правильно.
  • На уровне EVM добавится защита от short address attack. Казалось бы, мелочь, но приятно, что об этом не надо больше думать (и вообще знать об этой проблеме). Может быть, она будет даже более строгой, но там есть свои трудности.


Какие-то выводы

Хотя дата выхода релиза еще неизвестна, многие новшества уже можно потрогать руками.
Включаются они с помощью
pragma experimental "v0.5.0";
и
pragma experimental "ABIEncoderV2";
Компилятор, конечно, выдает предупреждение.

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

© Habrahabr.ru