<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Dmitriy Tarasov</title><author><name>Dmitriy Tarasov</name></author><id>https://teletype.in/atom/cryptodev</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/cryptodev?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/cryptodev?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-06-07T22:07:13.734Z</updated><entry><id>cryptodev:tc39-try-catch-raise-proposal-draft</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/tc39-try-catch-raise-proposal-draft?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>Предложение: try/catch как выражение (с ключевым словом raise)</title><published>2025-06-03T15:05:40.246Z</published><updated>2025-06-03T15:05:40.246Z</updated><summary type="html">Данное предложение предлагает новую возможность в JavaScript: использование конструкции try/catch/finally в контексте выражения, возвращающего значение. Для этого вводится новое ключевое слово raise, предназначенное для явного &quot;выноса&quot; значения из блока try или catch наружу. В итоге весь блок try/catch может выступать как единое выражение, результат которого определяется вызовами raise внутри него.</summary><content type="html">
  &lt;h2 id=&quot;xJKG&quot;&gt;Введение&lt;/h2&gt;
  &lt;p id=&quot;ruWx&quot;&gt;Данное предложение предлагает новую возможность в JavaScript: использование конструкции &lt;code&gt;try/catch/finally&lt;/code&gt; в контексте &lt;strong&gt;выражения&lt;/strong&gt;, возвращающего значение. Для этого вводится новое ключевое слово &lt;code&gt;raise&lt;/code&gt;, предназначенное для явного &amp;quot;выноса&amp;quot; значения из блока &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt; наружу. В итоге весь блок &lt;code&gt;try/catch&lt;/code&gt; может выступать как единое выражение, результат которого определяется вызовами &lt;code&gt;raise&lt;/code&gt; внутри него.&lt;/p&gt;
  &lt;p id=&quot;kxDM&quot;&gt;В классическом JavaScript конструкция &lt;code&gt;try...catch&lt;/code&gt; является оператором и не напрямую возвращает значение. Это означает, что для использования результата выполнения &lt;code&gt;try/catch&lt;/code&gt; в выражении приходится прибегать к дополнительным переменным или функциям. Новое же предложение делает синтаксис более &lt;strong&gt;выразительным и функциональным&lt;/strong&gt;, позволяя писать код в декларативном стиле без лишних промежуточных шагов.&lt;/p&gt;
  &lt;p id=&quot;Qfzp&quot;&gt;Ниже рассматриваются мотивация данного предложения, примеры использования, синтаксис и семантика ключевого слова &lt;code&gt;raise&lt;/code&gt;, потенциальные сложности и влияние на существующий код, а также сравнение с похожими возможностями в других языках. Предложение оформлено в формате объяснительного документа (explainer) для нулевого этапа TC39.&lt;/p&gt;
  &lt;h2 id=&quot;cRYG&quot;&gt;Мотивация&lt;/h2&gt;
  &lt;p id=&quot;GBD2&quot;&gt;Основная мотивация – упростить и &lt;strong&gt;улучшить читаемость кода&lt;/strong&gt;, связанного с обработкой ошибок, особенно в случаях, когда нужно присвоить переменной значение, полученное либо при успешном выполнении операции, либо при отлове исключения. В текущем JavaScript подобный паттерн требует многословного императивного кода:&lt;/p&gt;
  &lt;pre id=&quot;8gvv&quot;&gt;let result;
try {
  result = computeValue(x);
} catch (e) {
  result = fallbackValue(e);
}&lt;/pre&gt;
  &lt;p id=&quot;dq4c&quot;&gt;В таком коде переменная &lt;code&gt;result&lt;/code&gt; объявляется заранее, а затем внутри &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; ей присваиваются значения. Этот шаблон &lt;em&gt;нарушает&lt;/em&gt; декларативность и поток чтения кода – вместо того, чтобы определить &lt;code&gt;result&lt;/code&gt; в месте вычисления, мы вынуждены сначала объявить ее, а потом изменять. &lt;strong&gt;Желательно&lt;/strong&gt;, чтобы можно было получить значение из &lt;code&gt;try/catch&lt;/code&gt; прямо в месте вызова, как это делается с тернарными операторами или &lt;code&gt;if/else&lt;/code&gt; (если бы &lt;code&gt;if&lt;/code&gt; мог быть выражением).&lt;/p&gt;
  &lt;p id=&quot;RHqh&quot;&gt;Другой пример неудобства – использование результатов внутри других выражений. Сегодня, чтобы, например, вызвать функцию с аргументом, полученным через &lt;code&gt;try/catch&lt;/code&gt;, приходится либо выносить вычисление вне вызова, либо использовать Immediately-Invoked Function Expression (IIFE):&lt;/p&gt;
  &lt;pre id=&quot;rbS5&quot;&gt;// Текущий подход без try-выражения:
const finalResult = processData(
  (() =&amp;gt; {
    try {
      return riskyOperation();
    } catch {
      return defaultValue;
    }
  })()
);&lt;/pre&gt;
  &lt;p id=&quot;NV9Q&quot;&gt;Такой код труднее читать и поддерживать. Если бы &lt;code&gt;try/catch&lt;/code&gt; был выражением, можно было бы писать гораздо яснее:&lt;/p&gt;
  &lt;pre id=&quot;jIKy&quot;&gt;// С предлагаемым try-выражением:
const finalResult = processData(
  try {
    raise riskyOperation();
  } catch (e) {
    raise defaultValue;
  }
);&lt;/pre&gt;
  &lt;p id=&quot;jO5T&quot;&gt;Здесь результат &lt;code&gt;try&lt;/code&gt;-выражения сразу передается в функцию &lt;code&gt;processData&lt;/code&gt;, без дополнительных функций-обёрток. Код выглядит &lt;strong&gt;лаконичнее&lt;/strong&gt; и очевиднее.&lt;/p&gt;
  &lt;p id=&quot;OOv0&quot;&gt;Помимо присваивания, выражение &lt;code&gt;try&lt;/code&gt; вписывается в идеологию более &lt;strong&gt;функционального&lt;/strong&gt; стиля: можно использовать его внутри других выражений, комбинировать с тернарными операторами, &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;/&lt;code&gt;||&lt;/code&gt; логикой, шаблонными литералами и т.д. Это особенно ценно при сложных условиях, когда решение об значении переменной зависит как от булевых условий, так и от успешности выполнения какого-то кода.&lt;/p&gt;
  &lt;p id=&quot;e7Yd&quot;&gt;&lt;strong&gt;Обработка ошибок становится частью выражения&lt;/strong&gt;, а не отдельным &amp;quot;побочным эффектом&amp;quot; со стороны. Это может поощрять более &lt;strong&gt;консистентный стиль&lt;/strong&gt; кодирования, где минимум изменяемых переменных и промежуточных шагов.&lt;/p&gt;
  &lt;p id=&quot;K4rI&quot;&gt;Наконец, стоит отметить интерес сообщества к подобным возможностям. В других языках и предложениях уже есть схожие решения. Например, сообщество Python обсуждало возможность так называемых &amp;quot;exception-catching expressions&amp;quot; – выражений с обработкой исключений встроенной, по аналогии с тернарными операторами &lt;a href=&quot;https://peps.python.org/pep-0463&quot; target=&quot;_blank&quot;&gt;peps.python.org&lt;/a&gt;. Хотя в Python это не было принято, сам факт появления PEP 463 показывает потребность разработчиков в более кратком синтаксисе для таких случаев. Java добавила в версии 13 выражения &lt;code&gt;switch&lt;/code&gt; с ключевым словом &lt;code&gt;yield&lt;/code&gt; для возврата значений из блоков &lt;code&gt;case&lt;/code&gt;, чтобы избавиться от шаблона с внешней переменной &lt;a href=&quot;https://stackoverflow.com/questions/58049131/what-does-the-new-keyword-yield-mean-in-java-13&quot; target=&quot;_blank&quot;&gt;stackoverflow.com&lt;/a&gt;. Наше предложение в духе этой тенденции – предоставить JavaScript-разработчикам инструмент, делающий код с &lt;code&gt;try/catch&lt;/code&gt; менее шаблонным и более выразительным.&lt;/p&gt;
  &lt;h2 id=&quot;1Vbs&quot;&gt;Примеры использования до и после&lt;/h2&gt;
  &lt;p id=&quot;CgAQ&quot;&gt;Чтобы лучше понять предлагаемое изменение, рассмотрим несколько пар примеров &amp;quot;до и после&amp;quot;. Они иллюстрируют, как новый синтаксис с &lt;code&gt;raise&lt;/code&gt; сократит код и улучшит его понятность.&lt;/p&gt;
  &lt;p id=&quot;uP50&quot;&gt;&lt;strong&gt;Присваивание с обработкой ошибок&lt;/strong&gt;:&lt;/p&gt;
  &lt;p id=&quot;60wu&quot;&gt;&lt;em&gt;До:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;Qdep&quot;&gt;let config;
try {
  config = loadConfig();
} catch {
  config = getDefaultConfig();
}&lt;/pre&gt;
  &lt;p id=&quot;jGSt&quot;&gt;&lt;em&gt;После:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;EbFq&quot;&gt;const config = try {
  raise loadConfig();
} catch {
  raise getDefaultConfig();
};&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;OEG9&quot;&gt;Теперь переменная &lt;code&gt;config&lt;/code&gt; объявляется как &lt;code&gt;const&lt;/code&gt; и сразу инициализируется результатом &lt;code&gt;try&lt;/code&gt;-выражения. В случае успеха вызова &lt;code&gt;loadConfig()&lt;/code&gt; значение &amp;quot;поднимается&amp;quot; наружу через &lt;code&gt;raise&lt;/code&gt; внутри блока &lt;code&gt;try&lt;/code&gt;. Если же случится исключение, блок &lt;code&gt;catch&lt;/code&gt; выполнит свой &lt;code&gt;raise&lt;/code&gt; с результатом &lt;code&gt;getDefaultConfig()&lt;/code&gt;. В итоге &lt;code&gt;config&lt;/code&gt; получит либо загруженную конфигурацию, либо конфигурацию по умолчанию – и все это в одном выразительном конструктиве.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;bio1&quot;&gt;&lt;strong&gt;Интеграция с условными операторами (&lt;code&gt;if&lt;/code&gt;/тернарный)&lt;/strong&gt;:&lt;/p&gt;
  &lt;p id=&quot;Ows4&quot;&gt;&lt;em&gt;До:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;spuN&quot;&gt;let userName;
if (user.isLoggedIn) {
  try {
    userName = fetchUserName(user.id);
  } catch {
    userName = &amp;quot;Гость&amp;quot;;
  }
} else {
  userName = &amp;quot;Не авторизован&amp;quot;;
}&lt;/pre&gt;
  &lt;p id=&quot;1oFk&quot;&gt;Этот код можно переписать с использованием выражений, вложив &lt;code&gt;try&lt;/code&gt; внутрь тернарного оператора.&lt;/p&gt;
  &lt;p id=&quot;f0jU&quot;&gt;&lt;em&gt;После:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;bBql&quot;&gt;const userName = user.isLoggedIn 
  ? try {
      raise fetchUserName(user.id);
    } catch {
      raise &amp;quot;Гость&amp;quot;;
    }
  : &amp;quot;Не авторизован&amp;quot;;&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;dpgb&quot;&gt;Логика осталась прежней, но запись стала короче. Вместо того чтобы объявлять &lt;code&gt;userName&lt;/code&gt; заранее и несколько раз присваивать, мы напрямую определяем ее через выражение. При залогиненом пользователе предпринимается попытка получить имя (в случае ошибки подставится &amp;quot;Гость&amp;quot;), а при отсутствующей авторизации сразу используется строка &amp;quot;Не авторизован&amp;quot;. Код легко читается как &lt;strong&gt;единое выражение&lt;/strong&gt;, описывающее правила вычисления значения.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;WR5X&quot;&gt;&lt;strong&gt;Использование в &lt;code&gt;async&lt;/code&gt; функциях с &lt;code&gt;await&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
  &lt;p id=&quot;qZA9&quot;&gt;Нововведение совместимо с асинхронным кодом. Ключевое слово &lt;code&gt;raise&lt;/code&gt; поддерживает &lt;code&gt;await&lt;/code&gt; для правильного получения промиса.&lt;/p&gt;
  &lt;p id=&quot;L3OH&quot;&gt;&lt;em&gt;До:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;pcl0&quot;&gt;async function getData() {
  try {
    const response = await fetch(url);
    return process(response);
  } catch (err) {
    return getFallbackData(err);
  }
}&lt;/pre&gt;
  &lt;p id=&quot;mV1f&quot;&gt;&lt;em&gt;После:&lt;/em&gt;&lt;/p&gt;
  &lt;pre id=&quot;0sSj&quot;&gt;async function getData() {
  return try {
    raise await fetch(url);
  } catch (err) {
    raise getFallbackData(err);
  };
}&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;wjOl&quot;&gt;Здесь мы сразу &lt;code&gt;return&lt;/code&gt;-им результат &lt;code&gt;try&lt;/code&gt;-выражения из функции. Внутри &lt;code&gt;try&lt;/code&gt; происходит &lt;code&gt;await fetch(url)&lt;/code&gt;, и полученный ответ подается в &lt;code&gt;raise&lt;/code&gt; – то есть либо возвращается как результат всей функции (если все прошло успешно), либо, при ошибке запроса, управление перейдет в блок &lt;code&gt;catch&lt;/code&gt;, который вызовет &lt;code&gt;raise getFallbackData(err)&lt;/code&gt; и вернет альтернативные данные. Обратите внимание: &lt;code&gt;raise await&lt;/code&gt; аналогичен комбинации &lt;code&gt;await&lt;/code&gt; + &lt;code&gt;return&lt;/code&gt; для промиса – он дожидается результата и &amp;quot;поднимает&amp;quot; его наружу.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;Rovv&quot;&gt;Эти примеры демонстрируют, как новая возможность устраняет шаблонное кодирование (boilerplate) и делает намерения разработчика более прозрачными. Вместо фрагментированного кода с объявлениями вне блоков и присвоениями внутри, мы получаем &lt;strong&gt;целостные выражения&lt;/strong&gt;, которые проще воспринимать.&lt;/p&gt;
  &lt;h2 id=&quot;O8Zf&quot;&gt;Синтаксис и семантика try-выражения с raise&lt;/h2&gt;
  &lt;p id=&quot;1SY0&quot;&gt;Предлагаемое изменение включает два связанных элемента: разрешить конструкции &lt;code&gt;try/catch/finally&lt;/code&gt; возвращать значение, и ввести ключевое слово &lt;code&gt;raise&lt;/code&gt; для явного указания возвращаемого значения. Ниже описаны основные правила и поведение.&lt;/p&gt;
  &lt;h3 id=&quot;6erg&quot;&gt;Основные правила и ограничения&lt;/h3&gt;
  &lt;p id=&quot;r9da&quot;&gt;&lt;strong&gt;Обязательность &lt;code&gt;raise&lt;/code&gt; в ветвях:&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;e7Kv&quot;&gt;Если хотя бы одна из ветвей (&lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt;) внутри конструкции использует &lt;code&gt;raise&lt;/code&gt; для возврата значения, то &lt;strong&gt;обе&lt;/strong&gt; ветви должны содержать &lt;code&gt;raise&lt;/code&gt;. Другими словами, в выражении вида &lt;code&gt;try { ... } catch { ... }&lt;/code&gt; не допускается ситуация, когда &lt;code&gt;try&lt;/code&gt; возвращает значение через &lt;code&gt;raise&lt;/code&gt;, а в &lt;code&gt;catch&lt;/code&gt; при этом нет &lt;code&gt;raise&lt;/code&gt; (или наоборот). Такое требование гарантирует, что &lt;em&gt;все возможные пути выполнения возвращают значение&lt;/em&gt;. Это схоже с тем, как в выражениях &lt;code&gt;switch&lt;/code&gt; в Java каждой ветке должно соответствовать возвращаемое значение (через &lt;code&gt;-&amp;gt;&lt;/code&gt; или &lt;code&gt;yield&lt;/code&gt;), иначе код считается неполным &lt;a href=&quot;https://docs.oracle.com/en/java/javase/17/language/switch-expressions-and-statements.html&quot; target=&quot;_blank&quot;&gt;docs.oracle.comdocs.oracle.com&lt;/a&gt;. Если ни одна из ветвей не содержит &lt;code&gt;raise&lt;/code&gt;, конструкция &lt;code&gt;try/catch&lt;/code&gt; работает как обычный оператор и &lt;strong&gt;не производит значения&lt;/strong&gt; (то есть не может использоваться там, где ожидается значение).&lt;/p&gt;
  &lt;p id=&quot;sCMG&quot;&gt;&lt;strong&gt;Возврат значения из блока &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt;:&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;jTU8&quot;&gt;Ключевое слово &lt;code&gt;raise&lt;/code&gt; действует по аналогии с &lt;code&gt;return&lt;/code&gt;, но не на уровне функции, а на уровне &lt;em&gt;самого блока &lt;code&gt;try/catch&lt;/code&gt;&lt;/em&gt;. Когда выполнение кода встречает &lt;code&gt;raise &amp;lt;выражение&amp;gt;&lt;/code&gt;, текущий блок (&lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt;) немедленно завершается, и указанное значение &amp;quot;поднимается&amp;quot; наружу, становясь результатом всего выражения &lt;code&gt;try/catch&lt;/code&gt;. Код после вызова &lt;code&gt;raise&lt;/code&gt; в том же блоке не выполняется (как и в случае с &lt;code&gt;return&lt;/code&gt; внутри функции). Например:&lt;/p&gt;
  &lt;pre id=&quot;Gy24&quot;&gt;const value = try {
  if (conditionFailed) {
    raise null;  // немедленный выход из try-блока с результатом null
  }
  // ... иначе продолжаем вычисления
  let result = compute();
  raise result;  // возвращаем вычисленное значение
  console.log(&amp;quot;Этот код уже не исполнится, т.к. выше был raise&amp;quot;);
} catch (e) {
  raise handleError(e);
};&lt;/pre&gt;
  &lt;p id=&quot;w13f&quot;&gt;Здесь внутри &lt;code&gt;try&lt;/code&gt; предусмотрен ранний выход: при нарушении условия сразу возвращается &lt;code&gt;null&lt;/code&gt;. Если же условие в порядке, вычисляется &lt;code&gt;result&lt;/code&gt; и он возвращается. Любой код после &lt;code&gt;raise&lt;/code&gt; не выполнится. Блок &lt;code&gt;catch&lt;/code&gt; также обязательно возвращает значение (обработку ошибки) через &lt;code&gt;raise&lt;/code&gt;. Таким образом, переменная &lt;code&gt;value&lt;/code&gt; получит либо &lt;code&gt;null&lt;/code&gt;, либо корректно вычисленный результат, либо (если исключение не перехвачено данным catch) произойдет обычное распространение исключения вверх (см. ниже).&lt;/p&gt;
  &lt;p id=&quot;njuE&quot;&gt;&lt;strong&gt;Поведение блока &lt;code&gt;catch&lt;/code&gt;:&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;g0oT&quot;&gt;Блок &lt;code&gt;catch&lt;/code&gt; с &lt;code&gt;raise&lt;/code&gt; срабатывает только если в блоке &lt;code&gt;try&lt;/code&gt; было брошено исключение (с помощью обычного &lt;code&gt;throw&lt;/code&gt; или из-за ошибки). В этом случае выполнение переходит в &lt;code&gt;catch&lt;/code&gt;, и там зачастую пишется &lt;code&gt;raise&lt;/code&gt; с каким-то значением-заменой или обработкой. Если в &lt;code&gt;catch&lt;/code&gt; есть &lt;code&gt;raise&lt;/code&gt;, как требует правило, то при перехвате исключения именно его значение станет результатом всего &lt;code&gt;try&lt;/code&gt;-выражения. Если же исключение не произошо в &lt;code&gt;try&lt;/code&gt;, то блок &lt;code&gt;catch&lt;/code&gt; пропускается (как обычно) и не влияет на результат.&lt;/p&gt;
  &lt;p id=&quot;ASNm&quot;&gt;&lt;strong&gt;Опциональный блок &lt;code&gt;finally&lt;/code&gt;:&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;OWYb&quot;&gt;Конструкция может включать блок &lt;code&gt;finally&lt;/code&gt; так же, как обычный &lt;code&gt;try/catch/finally&lt;/code&gt;. Блок &lt;code&gt;finally&lt;/code&gt; выполняется всегда после &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; – вне зависимости, было исключение или нет. Однако, &lt;strong&gt;в контексте выражения&lt;/strong&gt; важно, как &lt;code&gt;finally&lt;/code&gt; влияет на возвращаемое значение: по умолчанию, если в &lt;code&gt;finally&lt;/code&gt; &lt;em&gt;ничего не предпринимать&lt;/em&gt;, результат, сформированный в &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt; через &lt;code&gt;raise&lt;/code&gt;, сохраняется. Но если внутри &lt;code&gt;finally&lt;/code&gt; будет вызван &lt;code&gt;raise&lt;/code&gt;, он &lt;strong&gt;переопределит&lt;/strong&gt; ранее подготовленный результат. То есть &lt;code&gt;raise&lt;/code&gt; в &lt;code&gt;finally&lt;/code&gt; имеет приоритет: он заставляет всё выражение вернуть то значение, которое указано в &lt;code&gt;finally&lt;/code&gt;, даже если до этого в &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt; уже был выполнен &lt;code&gt;raise&lt;/code&gt; с другим значением. Это поведение аналогично тому, как в сегодняшнем JavaScript блок &lt;code&gt;finally&lt;/code&gt; может перезаписать результат функции, если внутри него сделать &lt;code&gt;return&lt;/code&gt; или выбросить новое исключение (что, кстати, считается нежелательной практикой, но технически возможно).&lt;/p&gt;
  &lt;p id=&quot;9kjf&quot;&gt;&lt;strong&gt;Доступ к поднятому значению в &lt;code&gt;finally&lt;/code&gt;:&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;cDQi&quot;&gt;Возникает вопрос – как &lt;code&gt;finally&lt;/code&gt; может узнать, какое значение было поднято из &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt;? Предлагается расширение синтаксиса: блок &lt;code&gt;finally&lt;/code&gt; может объявить псевдопараметр, например: &lt;code&gt;finally (raised) { ... }&lt;/code&gt;. Переменная (идентификатор) в скобках после ключевого слова &lt;code&gt;finally&lt;/code&gt; будет доступна внутри блока и содержать &lt;em&gt;значение, переданное последним вызовом &lt;code&gt;raise&lt;/code&gt;&lt;/em&gt; в &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt;. Если исключение произошло, а &lt;code&gt;catch&lt;/code&gt; поднял значение, &lt;code&gt;raised&lt;/code&gt; будет равно этому значению; если исключения не было и &lt;code&gt;try&lt;/code&gt; поднял значение – соответственно тому. Если ни один из блоков не вызывал &lt;code&gt;raise&lt;/code&gt; (то есть на самом деле выражение не вернуло ничего), &lt;code&gt;raised&lt;/code&gt; может быть &lt;code&gt;undefined&lt;/code&gt;. Это дает возможность в &lt;code&gt;finally&lt;/code&gt; например логгировать или модифицировать результат перед окончательным возвратом. Блок &lt;code&gt;finally&lt;/code&gt; при этом &lt;strong&gt;не обязан&lt;/strong&gt; вызывать &lt;code&gt;raise&lt;/code&gt; – он может просто выполнить побочные действия. Но если внутри &lt;code&gt;finally&lt;/code&gt; все же вызвать &lt;code&gt;raise&lt;/code&gt;, то новое значение замещает собой прежнее. Пример:&lt;/p&gt;
  &lt;pre id=&quot;PGBI&quot;&gt;const data = try {
  raise fetchData();
} catch (e) {
  raise null;
} finally (result) {
  console.log(&amp;quot;Результат перед выходом:&amp;quot;, result);
  if (result === null) {
    // если данные не получены, переопределим результат на заглушку
    raise { status: &amp;quot;empty&amp;quot; };
  }
  // иначе не вызываем raise, и значение из try/catch пройдет далее
};&lt;/pre&gt;
  &lt;p id=&quot;jm7D&quot;&gt;В этом коде мы всегда логируем полученный результат (успешный или &lt;code&gt;null&lt;/code&gt; при ошибке). Затем, если результат равен &lt;code&gt;null&lt;/code&gt; (т.е. произошла ошибка и &lt;code&gt;catch&lt;/code&gt; вернул null), мы решаем &lt;em&gt;переопределить&lt;/em&gt; итоговый результат на объект &lt;code&gt;{ status: &amp;quot;empty&amp;quot; }&lt;/code&gt; посредством &lt;code&gt;raise&lt;/code&gt; в &lt;code&gt;finally&lt;/code&gt;. Если же результат не &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;finally&lt;/code&gt; ничего не возвращает, и значение из &lt;code&gt;try&lt;/code&gt; (результат &lt;code&gt;fetchData()&lt;/code&gt;) пройдет наружу. Таким образом, &lt;code&gt;data&lt;/code&gt; окажется либо объектом с данными (при успешном запросе), либо объектом &lt;code&gt;{ status: &amp;quot;empty&amp;quot; }&lt;/code&gt; (если произошла ошибка).&lt;/p&gt;
  &lt;p id=&quot;MJ36&quot;&gt;&lt;strong&gt;Ключевое слово &lt;code&gt;raise&lt;/code&gt;:&lt;/strong&gt; &lt;/p&gt;
  &lt;p id=&quot;lTGQ&quot;&gt;Предполагается ввести новое ключевое слово &lt;code&gt;raise&lt;/code&gt; в язык. Оно выступает как управляющая конструкция, &lt;strong&gt;не&lt;/strong&gt; как функция. С точки зрения грамматики, &lt;code&gt;raise&lt;/code&gt; будет аналогично &lt;code&gt;return&lt;/code&gt; или &lt;code&gt;throw&lt;/code&gt; – за ним должно следовать выражение (или семантически допустимо опустить выражение, чтобы вернуть &lt;code&gt;undefined&lt;/code&gt;, как &lt;code&gt;return;&lt;/code&gt;). Использование &lt;code&gt;raise&lt;/code&gt; вне контекста блока &lt;code&gt;try/catch&lt;/code&gt; (который предназначен для возвращения значения) будет синтаксической ошибкой. Также нельзя использовать &lt;code&gt;raise&lt;/code&gt; внутри вложенных функций, генераторов или arrow-функций &lt;strong&gt;вместо&lt;/strong&gt; &lt;code&gt;return&lt;/code&gt; – его действие ограничено именно текущим &lt;code&gt;try&lt;/code&gt;-выражением. Если попытаться написать:&lt;/p&gt;
  &lt;pre id=&quot;JKCX&quot;&gt;function f() {
  try {
    raise 42;
  } catch { raise -1; }
  // ...
  return 0;
}&lt;/pre&gt;
  &lt;p id=&quot;Cgc7&quot;&gt;то поведение будет следующим: когда &lt;code&gt;f()&lt;/code&gt; вызвана, внутри нее выполнится &lt;code&gt;try&lt;/code&gt;-выражение. Столкнувшись с &lt;code&gt;raise 42&lt;/code&gt;, оно завершит &lt;strong&gt;это &lt;code&gt;try&lt;/code&gt;-выражение&lt;/strong&gt; и вернет 42 наружу – в нашем случае, внутрь из функции &lt;code&gt;f()&lt;/code&gt;, но так как результат выражения не был присвоен и использован, выполнение дойдет до &lt;code&gt;return 0 и&lt;/code&gt; &lt;code&gt;f()&lt;/code&gt; вернет 0. Здесь важно разграничить: &lt;code&gt;return&lt;/code&gt; выходит из функции, &lt;code&gt;throw&lt;/code&gt; выходит из функции (если не пойман), а &lt;code&gt;raise&lt;/code&gt; выходит только из блока &lt;code&gt;try/catch&lt;/code&gt;-выражения. Это подобно тому, как оператор &lt;code&gt;yield&lt;/code&gt; в Java вызывает выход лишь из &lt;code&gt;switch&lt;/code&gt;-выражения, а не из окружающей функции &lt;a href=&quot;https://stackoverflow.com/questions/58049131/what-does-the-new-keyword-yield-mean-in-java-13&quot; target=&quot;_blank&quot;&gt;stackoverflow.com&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;P9n0&quot;&gt;&lt;strong&gt;Допустимые возвращаемые значения:&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;jRt6&quot;&gt;Вызов &lt;code&gt;raise&lt;/code&gt; может &amp;quot;поднять&amp;quot; любое значение. Это может быть примитив (число, строка, и т.д.), объект, &lt;code&gt;null&lt;/code&gt; или &lt;code&gt;undefined&lt;/code&gt;. Нет ограничений или специальных требований, как к &lt;code&gt;throw&lt;/code&gt; (который зачастую используют с объектами Error, но формально тоже может бросить что угодно). &lt;code&gt;raise&lt;/code&gt; просто берет указанное выражение и делает его результатом. Даже &lt;code&gt;raise undefined;&lt;/code&gt; считается корректным – это явный способ вернуть &amp;quot;ничего&amp;quot;. Если &lt;code&gt;raise&lt;/code&gt; написать без выражения (как отдельную инструкцию &lt;code&gt;raise;&lt;/code&gt;), по аналогии с &lt;code&gt;return;&lt;/code&gt; это будет означать поднять &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;0T4S&quot;&gt;&lt;strong&gt;Асинхронность и &lt;code&gt;await&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Dcm6&quot;&gt;Как показано ранее, &lt;code&gt;raise&lt;/code&gt; полноценно работает в сочетании с &lt;code&gt;await&lt;/code&gt; внутри &lt;code&gt;async&lt;/code&gt;-функций. При использовании &lt;code&gt;await&lt;/code&gt; результат промиса будет получен, а затем передан наружу. Это эквивалентно тому, как &lt;code&gt;return await&lt;/code&gt; обрабатывает значение: сначала ждет, затем возвращает его. Важно подчеркнуть: если внутри &lt;code&gt;try&lt;/code&gt; используется &lt;code&gt;await&lt;/code&gt;, то и сам &lt;code&gt;try&lt;/code&gt;-блок становится асинхронным, но поскольку весь код находится внутри &lt;code&gt;async&lt;/code&gt; функции, это естественно. Нет дополнительных ограничений – можно писать &lt;code&gt;raise await somePromise;&lt;/code&gt;. Если обещание (промис) разрешится нормально, его значение станет результатом, если отклонится (throw внутри промиса), то будет брошено исключение и, возможно, перехвачено блоком &lt;code&gt;catch&lt;/code&gt; данного выражения.&lt;/p&gt;
  &lt;h2 id=&quot;HsOx&quot;&gt;Правила области видимости (scoping)&lt;/h2&gt;
  &lt;p id=&quot;dvEx&quot;&gt;Ключевое слово &lt;code&gt;raise&lt;/code&gt; не вводит новой области видимости – оно действует внутри уже существующей области того блока, где находится. Переменные, объявленные внутри &lt;code&gt;try&lt;/code&gt; или &lt;code&gt;catch&lt;/code&gt; (через &lt;code&gt;let&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt;), видимы до конца этого блока (включая до его закрывающей фигурной скобки). Вызов &lt;code&gt;raise&lt;/code&gt; может использовать любую переменную, доступную в данной области (как локальную, так и внешнюю). Однако после выполнения &lt;code&gt;raise&lt;/code&gt; дальнейшие инструкции блока не выполняются, что важно учитывать: например, если ниже по коду определена еще какая-то переменная, она никогда не будет инициализирована, что эквивалентно ситуации с преждевременным &lt;code&gt;return&lt;/code&gt;. Это не создает новых проблем, аналогичная ситуация бывает с &lt;code&gt;return&lt;/code&gt; или &lt;code&gt;throw&lt;/code&gt;. Просто разработчику и инструментам нужно будет следить за тем, чтобы код после &lt;code&gt;raise&lt;/code&gt; не содержал критически важных операций (lint-правила могут подсвечивать &amp;quot;unreachable code after raise&amp;quot;, подобно тому, как делают для &lt;code&gt;return&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;57F6&quot;&gt;Отдельно стоит сказать про &lt;em&gt;Temporal Dead Zone (TDZ)&lt;/em&gt;. Появление &lt;code&gt;try&lt;/code&gt;-выражений не меняет существующих правил TDZ для &lt;code&gt;let&lt;/code&gt;/&lt;code&gt;const&lt;/code&gt;. Но могут быть тонкости, если &lt;code&gt;try&lt;/code&gt;-выражение используется в месте объявления переменной. Рассмотрим пример:&lt;/p&gt;
  &lt;pre id=&quot;bAdg&quot;&gt;// Неправильно:
let value = try {
  raise someVar;
} catch (e) {
  console.log(e); // ReferenceError: Cannot access &amp;#x27;someVar&amp;#x27; before initialization
  raise 1;
};
let someVar = 5;&lt;/pre&gt;
  &lt;p id=&quot;y9Cl&quot;&gt;Здесь мы пытаемся использовать &lt;code&gt;someVar&lt;/code&gt; внутри инициализации &lt;code&gt;value&lt;/code&gt; до того, как &lt;code&gt;someVar&lt;/code&gt; объявлена. Разработчикам нужно по-прежнему следить за порядком объявлений. В остальном &lt;code&gt;try&lt;/code&gt;-выражение не вводит новых ограничений: оно вычисляется последовательно, и любые переменные, объявленные &lt;strong&gt;внутри&lt;/strong&gt; него, живут только в нем и не &amp;quot;просачиваются&amp;quot; наружу (наружу выходит лишь значение). Переменная, объявленная во внешней функции с тем же именем, что и идентификатор в &lt;code&gt;finally (ident)&lt;/code&gt;, не находится в конфликте – идентификатор в скобках &lt;code&gt;finally&lt;/code&gt; работает как параметр, подобно параметру функции или названному exception-параметру в catch.&lt;/p&gt;
  &lt;h2 id=&quot;ltw3&quot;&gt;Потенциальные сложности и влияние на экосистему&lt;/h2&gt;
  &lt;p id=&quot;ekJI&quot;&gt;Внедрение нового синтаксиса – особенно с введением нового ключевого слова – требует внимательного отношения к деталям реализации и обратной &lt;/p&gt;
  &lt;p id=&quot;L2UA&quot;&gt;Внедрение нового синтаксиса – особенно с введением нового ключевого слова – требует внимательного отношения к деталям реализации и обратной совместимости.&lt;/p&gt;
  &lt;ul id=&quot;xwqV&quot;&gt;
    &lt;li id=&quot;hyLL&quot;&gt;&lt;strong&gt;Обратная совместимость (ключевое слово &lt;code&gt;raise&lt;/code&gt;):&lt;/strong&gt; На сегодняшний день слово &lt;code&gt;raise&lt;/code&gt; &lt;strong&gt;не является&lt;/strong&gt; зарезервированным в JavaScript. Это означает, что существующий код мог использовать &lt;code&gt;raise&lt;/code&gt; как имя переменной, функции, свойства объекта и т.д. Введение его в качестве ключевого слова теоретически может &lt;em&gt;сломать&lt;/em&gt; такой код. Например, если где-то определено &lt;code&gt;var raise = 5;&lt;/code&gt; или функция &lt;code&gt;function raise(x) { ... }&lt;/code&gt;, то после появления ключевого слова это станет синтаксической ошибкой или будет восприниматься иначе. Чтобы минимизировать ущерб, можно рассмотреть &lt;strong&gt;контекстное ключевое слово&lt;/strong&gt;: сделать так, что &lt;code&gt;raise&lt;/code&gt; распознается как ключевое только внутри конструкций &lt;code&gt;try { ... } catch { ... } finally { ... }&lt;/code&gt;, где по грамматике ожидается возврат значения. Подход с контекстными (или &amp;quot;ограниченными&amp;quot;) ключевыми словами уже применялся: например, &lt;code&gt;yield&lt;/code&gt; в ES6 стал ключевым словом только внутри генераторов, &lt;code&gt;await&lt;/code&gt; – только внутри &lt;code&gt;async&lt;/code&gt; функций или модулей. Вероятно, &lt;code&gt;raise&lt;/code&gt; может быть сделан зарезервированным в строгом режиме или только в специальных контекстах, чтобы не сломать существующие скрипты. Но в любом случае это обсуждаемо: возможно, придется выбрать другое слово или подход (альтернативы рассмотрим ниже).&lt;/li&gt;
    &lt;li id=&quot;52jc&quot;&gt;&lt;strong&gt;Поддержка инструментов (lint, форматтеры, IDE):&lt;/strong&gt; Появление нового синтаксиса потребует обновления инструментов разработки. Правила линтеров (ESLint и др.) должны будут научиться корректно парсить &lt;code&gt;try&lt;/code&gt; как выражение и не ругаться, например, на присваивание в &lt;code&gt;const&lt;/code&gt; через &lt;code&gt;try { ... } catch { ... }&lt;/code&gt;. Также могут появиться новые правила: например, предупреждение о коде после &lt;code&gt;raise&lt;/code&gt; (как упоминалось), или требование, чтобы и в try, и в catch присутствовал &lt;code&gt;raise&lt;/code&gt; (это, скорее, синтаксический уровень, но линтер может помогать ловить такие ошибки понятнее). Форматтеры кода (Prettier и др.) тоже должны знать про новый синтаксис, чтобы правильно расставлять отступы. В средах вроде VSCode появятся подсветка и автодополнение. Это все рабочие моменты, которые сопровождают любую новую возможность языка.&lt;/li&gt;
    &lt;li id=&quot;NXYH&quot;&gt;&lt;strong&gt;Изменения в парсерах и AST:&lt;/strong&gt; JavaScript-движки (V8, SpiderMonkey, JavaScriptCore) должны будут адаптировать парсер под новый конструктив. Появится необходимость распознавать конструкцию &lt;code&gt;try&lt;/code&gt; как часть выражения. По спецификации, скорее всего, будет введено что-то вроде &lt;em&gt;TryExpression&lt;/em&gt;. AST (Abstract Syntax Tree) форматы, такие как ESTree, тоже расширятся новым типом узла, например &lt;code&gt;TryExpression&lt;/code&gt; с узлами &lt;code&gt;block&lt;/code&gt;, &lt;code&gt;handler&lt;/code&gt; (catch) и &lt;code&gt;finalizer&lt;/code&gt;. Внутри него новые узлы &lt;code&gt;RaiseExpression&lt;/code&gt; для точек выхода. Инструменты, работающие с AST (бабели, трансформеры, minifiers) тоже потребуют обновления. Хотя изменение не тривиальное, оно &lt;strong&gt;локализовано&lt;/strong&gt;: не затрагивает тонны существующей семантики, а лишь добавляет новый вид выражения и оператор. Близкий по масштабу пример – добавление опциональной цепочки (&lt;code&gt;a?.b&lt;/code&gt;), которое тоже потребовало внести новый тип узла. Сообщество обычно справляется с такими изменениями достаточно быстро, особенно если фича популярна.&lt;/li&gt;
    &lt;li id=&quot;Umm8&quot;&gt;&lt;strong&gt;Влияние на читаемость и соглашения:&lt;/strong&gt; Хотя цель предложения – улучшить читаемость, у него могут быть и критики. Некоторые разработчики могут найти непривычным видеть &lt;code&gt;try/catch&lt;/code&gt; внутри выражения. Будут вопросы стиля: можно ли злоупотреблять и создавать слишком громоздкие выражения? Будет ли код сложнее отладки, если в одной строке много всего, включая обработку ошибок? Эти проблемы не технические, но влияют на принятие. Вероятно, появятся рекомендации (в документации или сообществе), как уместно использовать try-выражения, а когда лучше оставить явный &lt;code&gt;try/catch&lt;/code&gt; блок. Например, правило: если логика обработки исключений сложная (больше нескольких строк), лучше не вкладывать ее в тернарный оператор, а писать отдельно. Или: избегать вложенных try-выражений ради читабельности. Эти аспекты находятся вне рамок спецификации, но важно иметь их в виду.&lt;/li&gt;
    &lt;li id=&quot;K9cM&quot;&gt;&lt;strong&gt;Производительность:&lt;/strong&gt; В текущем виде &lt;code&gt;try/catch&lt;/code&gt; в JavaScript несет незначительные издержки, особенно когда исключения не происходят. Введение &lt;code&gt;try&lt;/code&gt;-выражений с &lt;code&gt;raise&lt;/code&gt; скорее всего аналогично по затратам: пока не брошено исключение, выполнение идет линейно, с парой дополнительных проверок на &lt;code&gt;raise&lt;/code&gt;. Если &lt;code&gt;raise&lt;/code&gt; вызывается, это примерно то же, что &lt;code&gt;return&lt;/code&gt; из функции – мгновенное завершение блока. Возможно, реализация затронет механизм &lt;em&gt;completion records&lt;/em&gt; в спецификации (уже сейчас у каждого блока есть значение завершения). Тонкая оптимизация может потребоваться, но ничего не указывает на серьезное падение производительности. Тем не менее, движкам надо будет обработать новые пути выхода (например, в JIT-компиляции), чтобы эффективно предсказывать и инлайнить такие вещи. Это работа оптимизаторов, но особых препятствий нет.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;ROif&quot;&gt;Аналоги в других языках&lt;/h2&gt;
  &lt;p id=&quot;rO6S&quot;&gt;Идея делать конструкции вроде &lt;code&gt;try/catch&lt;/code&gt; &lt;strong&gt;вычисляемыми выражениями&lt;/strong&gt; не нова. В разных языках реализовано разными путями:&lt;/p&gt;
  &lt;p id=&quot;h6mm&quot;&gt;&lt;strong&gt;Kotlin:&lt;/strong&gt; В Kotlin блочный оператор &lt;code&gt;try/catch&lt;/code&gt; является выражением и может возвращать значение. Конструкция очень похожа на предлагаемую, но &lt;strong&gt;без специального ключевого слова&lt;/strong&gt; – результатом считается последнее выражение, выполненное в блоке &lt;code&gt;try&lt;/code&gt; либо в блоке &lt;code&gt;catch&lt;/code&gt; &lt;a href=&quot;https://kotlinlang.org/docs/exceptions.html#:~:text=The%20returned%20value%20of%20a,catch%60%20block&quot; target=&quot;_blank&quot;&gt;kotlinlang.org&lt;/a&gt;. Например, в Kotlin:&lt;/p&gt;
  &lt;pre id=&quot;02jC&quot;&gt;val num = try {
    riskyOperation()
} catch (e: Exception) {
    -1
}&lt;/pre&gt;
  &lt;p id=&quot;fMCF&quot;&gt;Здесь &lt;code&gt;num&lt;/code&gt; получит либо результат &lt;code&gt;riskyOperation()&lt;/code&gt;, либо -1 в случае исключения. Блок &lt;code&gt;finally&lt;/code&gt; в Kotlin, хотя и выполняется всегда, &lt;strong&gt;не влияет&lt;/strong&gt; на результат выражения (его используют только для побочных эффектов) &lt;a href=&quot;https://kotlinlang.org/docs/exceptions.html&quot; target=&quot;_blank&quot;&gt;kotlinlang.org&lt;/a&gt;. Наше предложение для JavaScript, по сути, стремится к аналогичному удобству, но более явным синтаксисом. Мы вводим &lt;code&gt;raise&lt;/code&gt; чтобы явно обозначать возвращаемое значение, вместо неявного &amp;quot;последнего выражения&amp;quot;. Это решение принято из соображений ясности: явный ключевой слово снижает вероятность ошибки, когда, например, кто-то забыл, что надо вернуть значение, или случайно разместил ненужный код после нужного выражения.&lt;/p&gt;
  &lt;p id=&quot;eDdU&quot;&gt;&lt;strong&gt;Scala:&lt;/strong&gt; В языке Scala &lt;code&gt;try-catch-finally&lt;/code&gt; также является выражением. Поскольку Scala – язык выражений, там практически все конструкции могут возвращать значение. Семантика как у Kotlin: результат – это либо значение из &lt;code&gt;try&lt;/code&gt;, либо из &lt;code&gt;catch&lt;/code&gt;. Отличие Scala в том, что &lt;code&gt;catch&lt;/code&gt; там основан на сопоставлении с образцом (pattern matching), но суть та же. Кроме того, в Scala имеется класс &lt;code&gt;Try&lt;/code&gt; (в библиотеке), представляющий результат выполнения, который может быть успешным (&lt;code&gt;Success&lt;/code&gt;) или содержать исключение (&lt;code&gt;Failure&lt;/code&gt;). Он позволяет писать код в функциональном стиле: вместо использования ключевых слов, оборачивает результат в объект. Например:&lt;/p&gt;
  &lt;pre id=&quot;eIUM&quot;&gt;import scala.util.Try
val resultTry = Try(riskyOperation())  // вернет Success(значение) или Failure(ошибка)
val finalVal = resultTry.getOrElse(defaultValue)&lt;/pre&gt;
  &lt;p id=&quot;xiDk&quot;&gt;Такой подход похож по цели – избавиться от явного try/catch при присваивании – но реализован через библиотечный класс. Наш путь – интегрировать удобство на уровне языка, без дополнительных объектов-оберток.&lt;/p&gt;
  &lt;p id=&quot;nEb3&quot;&gt;&lt;strong&gt;Rust:&lt;/strong&gt; В Rust отсутствуют исключения, поэтому напрямую аналога &lt;code&gt;try-catch&lt;/code&gt; нет, но есть &lt;strong&gt;результатный тип&lt;/strong&gt; &lt;code&gt;Result&lt;/code&gt; (и &lt;code&gt;Option&lt;/code&gt;) и оператор &lt;code&gt;?&lt;/code&gt; для распространения ошибок. Интересно, что в версии Rust 2018+ появилось понятие &lt;em&gt;try-блока&lt;/em&gt; (пока экспериментального, на момент появления) &lt;a href=&quot;https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html&quot; target=&quot;_blank&quot;&gt;doc.rust-lang.org&lt;/a&gt;. Try-блок в Rust выглядит как:&lt;/p&gt;
  &lt;pre id=&quot;EeJo&quot;&gt;let result: Result&amp;lt;T, E&amp;gt; = try {
    let x = doSomething()?;   // если doSomething() вернет Err, то выходим из try-блока
    let y = doOther()?;       // аналогично, ? оператор возвращает из блока при ошибке
    compute(x, y)
};&lt;/pre&gt;
  &lt;p id=&quot;yzMe&quot;&gt;Здесь, если внутри блока встречается &lt;code&gt;?&lt;/code&gt;, он немедленно прерывает блок, возвращая &lt;code&gt;Err(...)&lt;/code&gt; как значение всего блока. Если же ни один &lt;code&gt;?&lt;/code&gt; не сработал, результатом будет &lt;code&gt;Ok(...)&lt;/code&gt; с последним выражением (в примере &lt;code&gt;compute(x,y)&lt;/code&gt;). Таким образом, Rust решает сходную задачу – получить из блока либо успех, либо ошибку – но делая это через типизацию (Result) вместо исключений, а &lt;code&gt;try {}&lt;/code&gt; + &lt;code&gt;?&lt;/code&gt; служат синтаксическим сахаром. Для нас Rust интересен тем, что демонстрирует: &lt;strong&gt;блок-выражение с ранним выходом по специальному оператору&lt;/strong&gt; – концепция не чуждая современным языкам. По аналогии, &lt;code&gt;raise&lt;/code&gt; в JavaScript – это &amp;quot;ранний выход&amp;quot; с готовым значением, не приводящий к выбросу исключения.&lt;/p&gt;
  &lt;p id=&quot;P6pZ&quot;&gt;&lt;strong&gt;Python (PEP 463)&lt;/strong&gt;: Как упоминалось, в Python был предложен синтаксис для &amp;quot;exception-catching expressions&amp;quot;. Идея заключалась в том, чтобы позволить конструкцию вида:&lt;/p&gt;
  &lt;pre id=&quot;hOM8&quot;&gt;result = expr1 except SomeException: expr2&lt;/pre&gt;
  &lt;p id=&quot;FRrT&quot;&gt;Это бы означало: вычислить &lt;code&gt;expr1&lt;/code&gt;, и если возникает &lt;code&gt;SomeException&lt;/code&gt;, вместо него взять значение &lt;code&gt;expr2&lt;/code&gt;. Предложение (PEP 463) обосновывалось тем, что в Python не хватает выражения для EAFP-стиля (Easier to Ask Forgiveness than Permission) прямо внутри других выражений &lt;a href=&quot;https://peps.python.org/pep-0463&quot; target=&quot;_blank&quot;&gt;peps.python.org&lt;/a&gt;. Например:&lt;/p&gt;
  &lt;pre id=&quot;I29S&quot;&gt;process(value if key in dict else default)      # LBYL стиль (есть тернарный оператор)
process(dict[key] except KeyError: default)     # Предлагаемый EAFP стиль&lt;/pre&gt;
  &lt;p id=&quot;5ahb&quot;&gt;Хотя PEP 463 был вежливо отклонен Guido ван Россумом (отчасти из опасений усложнения языка), он является показателем потребности. Однако предлагаемое решение в JS несколько отличается синтаксисом. Python-предложение реализовывало это более лаконично (буквально как инфиксный оператор &lt;code&gt;except&lt;/code&gt; внутри выражения). Мы же в JavaScript стараемся сохранить привычную форму &lt;code&gt;try { } catch { }&lt;/code&gt; даже в выражении, и вводим &lt;code&gt;raise&lt;/code&gt; вместо перегрузки существующего слова. Это выглядит более громоздко чем PEP 463, но лучше вписывается в синтаксис JS. Стоит отметить, что Python до сих пор не имеет никаких try-выражений — то есть разработчики по-прежнему используют обычные try/except блоки.&lt;/p&gt;
  &lt;p id=&quot;BcBF&quot;&gt;&lt;strong&gt;Другие языки:&lt;/strong&gt; В ряде функциональных и скриптовых языков есть схожие механизмы. Например, &lt;strong&gt;OCaml/F#&lt;/strong&gt; (ML-семейство) – там &lt;code&gt;try...with&lt;/code&gt; (эквивалент catch) является выражением, возвращающим значение, поскольку в этих языках вообще все конструкции возвращают значение. &lt;strong&gt;Haskell&lt;/strong&gt; не имеет исключений как основного механизма, но есть монада &lt;code&gt;Either&lt;/code&gt;/&lt;code&gt;IO&lt;/code&gt; для обработки ошибок без выброса. &lt;strong&gt;Go&lt;/strong&gt; пошел по пути вообще отказаться от исключений, возвращая ошибку как второй результат функции. В контексте JavaScript, где исключения есть и активно используются, наш подход ближе к Kotlin/Scala – сделать их использование более гибким и менее шаблонным.&lt;/p&gt;
  &lt;h2 id=&quot;RMRI&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;gDts&quot;&gt;В этом документе был представлен набросок предложения для TC39, позволяющего использовать &lt;code&gt;try/catch/finally&lt;/code&gt; как выражение с возвращаемым значением, благодаря введению нового ключевого слова &lt;code&gt;raise&lt;/code&gt;. Мы рассмотрели мотивацию (упрощение шаблонного кода, улучшение декларативности), детально описали предполагаемый синтаксис и поведение &lt;code&gt;raise&lt;/code&gt;, включая правила его использования и взаимодействия с &lt;code&gt;finally&lt;/code&gt;. Также были обсуждены потенциальные подводные камни (ключевое слово, AST, линтеры, совместимость) и показано, что подобные возможности существуют или обсуждались в других языках (Kotlin, Scala, Rust, Python и др.), что подтверждает ценность идеи. Наконец, проанализированы альтернативные подходы и обосновано, почему выбран именно данный путь.&lt;/p&gt;
  &lt;p id=&quot;gmTg&quot;&gt;&lt;strong&gt;Призыв к обсуждению:&lt;/strong&gt; На стадии 0 важно собрать мнения и выявить возможные проблемы, которые не покрыты черновиком. Открыты вопросы могут включать:&lt;/p&gt;
  &lt;ul id=&quot;D8n5&quot;&gt;
    &lt;li id=&quot;Cv0k&quot;&gt;Выбор ключевого слова: подходит ли &lt;code&gt;raise&lt;/code&gt; по смыслу и нет ли риска путаницы с &amp;quot;raise exception&amp;quot; из других языков?&lt;/li&gt;
    &lt;li id=&quot;TB0A&quot;&gt;Нужен ли параметр в &lt;code&gt;finally&lt;/code&gt; или можно обойтись без нового синтаксиса вроде &lt;code&gt;finally (raised)&lt;/code&gt;?&lt;/li&gt;
    &lt;li id=&quot;04Vi&quot;&gt;Все ли случаи учтены (например, поведение при вложенных try-выражениях, при использовании &lt;code&gt;break/return&lt;/code&gt; внутри блоков и т.п.)?&lt;/li&gt;
    &lt;li id=&quot;oLft&quot;&gt;Насколько часто такая конструкция пригодится на практике, есть ли реальные примеры из кода, где это существенно улучшит качество?&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;1crH&quot;&gt;Обсуждение этих вопросов, а также эксперименты с прототипированием (в Babel либо в отдельных ветках V8) помогут продвинуть предложение к Stage 1 и дальше, если сообщество посчитает идею полезной. Мы надеемся, что предоставленный explainer ясно передает суть и преимущества &lt;code&gt;try&lt;/code&gt;-выражений с &lt;code&gt;raise&lt;/code&gt;, и с нетерпением ждем обратной связи от TC39 и сообщества JavaScript разработчиков.&lt;/p&gt;
  &lt;p id=&quot;HWqw&quot;&gt;Ссылка на начальный драфт пропозала:&lt;/p&gt;
  &lt;p id=&quot;OFsf&quot;&gt;&lt;a href=&quot;https://github.com/dmitrytarassov/tc39-raise-proposal-draft?tab=readme-ov-file&quot; target=&quot;_blank&quot;&gt;https://github.com/dmitrytarassov/tc39-raise-proposal-draft?tab=readme-ov-file&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>cryptodev:0y-eigen-airdrop-contract</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/0y-eigen-airdrop-contract?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>Как я токены Eigen раздавал. Часть вторая - контракт.</title><published>2024-11-18T11:43:17.537Z</published><updated>2024-11-18T11:43:17.537Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/52/e7/52e73bc7-8ba2-40f3-b1cc-cd8b4878c0bb.png"></media:thumbnail><category term="crypto-development" label="Crypto Development"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/a7/69/a7695f5a-08f7-4d75-bf57-198ca883ff48.png&quot;&gt;Привет, мой дорогой читатель. В прошлый раз я рассказал тебе о том, как я считал поинты для эйрдропа от 0y. В этот раз - я расскажу тебе о том, как писал контракт.</summary><content type="html">
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;qjAC&quot;&gt;Привет, мой дорогой читатель. В &lt;a href=&quot;https://teletype.in/@cryptodev/editor/eigen-layer-and-0y&quot; target=&quot;_blank&quot;&gt;прошлый раз &lt;/a&gt;я рассказал тебе о том, как я считал поинты для эйрдропа от 0y. В этот раз - я расскажу тебе о том, как писал контракт. Я изначально предполагаю, что ты немного шаришь в блокчейне, и понимаешь что такое газ (о нем сегодня будет много), в частности.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;00E7&quot;&gt;Давай, сначала, попробуем определиться с той проблемой, которая есть. И так, у нас есть n токенов Eigen и надо каким-то образом из раздать. Как это можно сделать? Напрашивается несколько вариантов:&lt;/p&gt;
  &lt;ul id=&quot;PrIB&quot;&gt;
    &lt;li id=&quot;Kzpg&quot;&gt;&lt;strong&gt;Разослать в ручную. &lt;/strong&gt;Просто, долго и дорого - по итогу. Запираем одного человека в комнате с компуктером, даем ему список адресов и ждем. Просто - да. Долго - о, да! А еще надо насыпать ему эфира на оплату газа. Даже если предположить, что 1 трансфер будет стоить в районе 3 USDT в газе - трансфер на все 264 аккаунта, обойдется уже под 1К. И это на низком газе, что в последнее время - редкость. Так что, это еще и дорого.&lt;/li&gt;
    &lt;li id=&quot;aBH3&quot;&gt;&lt;strong&gt;Мультисенд. &lt;/strong&gt;Вариант. Но тут тоже есть нюансы. Вопервых, надо запариться с контрактом. Если нет подходящего, придется писать. А если есть, то платить за газ, ведь нам все равно надо выполнить почти 300 трансферов, а это в любом случае затратно. И еще вот какой психологический момент, не то что бы так не принято делать, но пользователь не сможет почувствовать свою принадлежность сообществу, если просто получит свои токены, никуда не нажав.&lt;/li&gt;
    &lt;li id=&quot;sOCc&quot;&gt;&lt;strong&gt;Контракт.&lt;/strong&gt; Это самый крутой вариант. Мы можем написать контракт, и каждый пользователь, которого мы укажем - сможет в интерфейсе нажать на  кнопку, отправить транзакцию и получить свои токены. Мне нравится. 0Y нравится. Пользователям тоже должно понравиться. Ну и еще момент, на себя бы берем только оплату деплоя контракта, а за дроп - платит уже пользователь. Вполне справедливо.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;h3 id=&quot;uSL6&quot;&gt;2 варианта рассылки токенов&lt;/h3&gt;
    &lt;p id=&quot;IEbt&quot;&gt;Стандарт &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-20&quot; target=&quot;_blank&quot;&gt;ERC-20&lt;/a&gt; предполагает удобный интерфейс апрувов, который говорит о том, что я как пользователь могу позволить любому другому адресу в блокчейне использовать любое, указанное мной количество токенов, с моего аккаунта. То есть, написав контракт мы могли бы не закидывать на него токены, а просто выдать апрув на их использование. Мы так делать не стали. &lt;/p&gt;
    &lt;p id=&quot;Szfy&quot;&gt;Тут нет какой-то идеологии, просто лично мне показалось чуть менее затратным по времени разработки хранение токенов на самом контракте. Ну и еще момент: в идеале тогда надо было бы в метод airdrop добавлять проверку на allowance, что привело бы к большим затратам газа, при выполнении транзакции. Оно нам надо? Оно нам не надо.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;5hIc&quot;&gt;7 раз отмерь - 1 раз отрежь&lt;/h2&gt;
  &lt;p id=&quot;Xc3x&quot;&gt;Еще перед тем, как написать первую строчку я задумался на тему того, какие могут быть уязвимости у такого контракта. И как мне из избежать. Ну во первых у контракта должно быть какое-то управление. То есть должен быть владелец, который мог бы принимать какие-то решения. Окей, как-будто &lt;code&gt;Ownable&lt;/code&gt;, чуть позже расскажу тебе про это. Возможно дроп надо будет поставить на паузу, звучит как &lt;code&gt;Pausable&lt;/code&gt;. Окей, что еще...&lt;/p&gt;
  &lt;h3 id=&quot;wYe9&quot;&gt;Атака повторного входа.&lt;/h3&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;mvoT&quot;&gt;Атака повторного входа (reentrancy attack) – это уязвимость в смарт-контрактах, которая позволяет злоумышленнику вызвать функцию контракта повторно до завершения предыдущего вызова. Эта уязвимость обычно возникает, когда контракт отправляет средства на адрес, который может выполнять произвольный код (например, другой контракт), и при этом не обновляет свое состояние перед переводом средств.&lt;/p&gt;
    &lt;p id=&quot;7iZR&quot;&gt;Суть атаки заключается в том, что злоумышленник может использовать возможность многократно вызывать одну и ту же функцию, например, для получения средств, пока состояние контракта не обновлено.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;TABW&quot;&gt;Окей, как обезопаситься? Умные дядьки уже все придумали за нас. &lt;a href=&quot;https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol&quot; target=&quot;_blank&quot;&gt;ReentrancyGuard&lt;/a&gt; из OpenZeppelin спешит на помощь. Забираем модификатор &lt;code&gt;nonReentrant&lt;/code&gt; и пишем код. Код, конечно, лучше писать тоже обдумано, к этому как раз и переходим.&lt;/p&gt;
  &lt;h2 id=&quot;JE7M&quot;&gt;Time to code&lt;/h2&gt;
  &lt;p id=&quot;0u24&quot;&gt;Окей, открываем единственный нормальный редактор для Solidity - &lt;a href=&quot;https://remix.ethereum.org&quot; target=&quot;_blank&quot;&gt;Remix IDE&lt;/a&gt;, создаем пустой шаблон и погнали писать. Первым делом забираем все необходимое из &lt;a href=&quot;https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master&quot; target=&quot;_blank&quot;&gt;OpenZeppelin&lt;/a&gt;.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;M8bF&quot;&gt;OpenZeppelin — это набор инструментов и библиотек на Solidity для разработки безопасных и стандартизированных смарт-контрактов. В основном используется для разработки на блокчейне Ethereum, но также поддерживает другие сети, совместимые с EVM.&lt;/p&gt;
    &lt;p id=&quot;yG8l&quot;&gt;Основные компоненты OpenZeppelin:&lt;/p&gt;
    &lt;ul id=&quot;SKgj&quot;&gt;
      &lt;li id=&quot;TC8i&quot;&gt;Контракты: Библиотека стандартизированных смарт-контрактов, включая токены ERC-20, ERC-721 (NFT), ERC-1155 и другие. Включает проверенные временем реализации популярных стандартов, что снижает вероятность ошибок и уязвимостей.&lt;/li&gt;
      &lt;li id=&quot;uh8a&quot;&gt;Безопасность: Контракты включают защитные механизмы против распространенных атак, таких как повторный вход (ReentrancyGuard), защита от превышения и переполнения (SafeMath), а также функции контроля доступа (Ownable, AccessControl).&lt;/li&gt;
      &lt;li id=&quot;HRRO&quot;&gt;Прокси-контракты: Инструменты для разработки апгрейдируемых смарт-контрактов, что позволяет обновлять контракт без изменения его адреса и состояния.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p id=&quot;K6ZF&quot;&gt;OpenZeppelin стал де-факто стандартом для создания безопасных смарт-контрактов и широко используется проектами в Ethereum-сообществе.&lt;/p&gt;
  &lt;/section&gt;
  &lt;pre id=&quot;qIvK&quot;&gt;import &amp;quot;@openzeppelin/contracts/token/ERC20/IERC20.sol&amp;quot;;
import &amp;quot;@openzeppelin/contracts/access/Ownable.sol&amp;quot;;
import &amp;quot;@openzeppelin/contracts/security/ReentrancyGuard.sol&amp;quot;;
import &amp;quot;@openzeppelin/contracts/security/Pausable.sol&amp;quot;;&lt;/pre&gt;
  &lt;p id=&quot;DNRN&quot;&gt;Ownable и Pausable - реализуют логику владения и управления контрактом, ReentrancyGuard - защитит от атаки повторного входа, IERC20 - интерфейс токена.&lt;/p&gt;
  &lt;pre id=&quot;nZok&quot;&gt;contract EigenAirdrop0y is Ownable, ReentrancyGuard, Pausable { // наследуемся
    IERC20 public immutable token; // объявляем переменную токена

    constructor(address _token) Ownable(msg.sender) { // инициализируем овнера
        require(_token != address(0), &amp;quot;Invalid token address&amp;quot;);
        token = IERC20(_token); // определяем переменную токена
    }
}&lt;/pre&gt;
  &lt;p id=&quot;gWok&quot;&gt;Пока что изи. Давай перегрузим пару методов, отвечающих за состояние контракта, добавив к ним модификатор onlyOwner.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;yPnF&quot;&gt;Модификаторы в Solidity — это специальные конструкции, которые позволяют изменять поведение функций, добавляя условия или проверки до выполнения основной логики функции. Воспринимай модификатор, как декоратор. Модификатор onlyOwner — один из самых популярных и часто используется для ограничения доступа к функциям только владельцу контракта.&lt;/p&gt;
    &lt;pre id=&quot;tGsZ&quot;&gt;pragma solidity ^0.8.0;

contract Ownable {
    address public owner;

    constructor() {
        owner = msg.sender; // Устанавливаем владельцем создателя контракта
    }

    modifier onlyOwner() {
        require(msg.sender == owner, &amp;quot;Not the owner&amp;quot;);
        _; // Продолжить выполнение функции
    }

    function restrictedFunction() public onlyOwner {
        // Логика функции, доступная только владельцу
    }
}&lt;/pre&gt;
  &lt;/section&gt;
  &lt;pre id=&quot;DRst&quot;&gt;function pause() external onlyOwner {
    _pause(); // вызов родительского _pause();
}

function unpause() external onlyOwner {
    _unpause(); // вызов родительского _unpause();
}&lt;/pre&gt;
  &lt;p id=&quot;ac5Z&quot;&gt;Го некст! Надо определить кому и сколько будем дропать. Значение и состояние будем хранить в маппингах &amp;lt;address =&amp;gt; uint256&amp;gt; и &amp;lt;address =&amp;gt; bool&amp;gt;.&lt;/p&gt;
  &lt;pre id=&quot;o9Q4&quot;&gt;mapping(address =&amp;gt; uint256) public airdropList;
mapping(address =&amp;gt; bool) public done;&lt;/pre&gt;
  &lt;p id=&quot;V5eW&quot;&gt;Пишем  view функцию, которая вернет значение, может ли аккаунт получить дроп.&lt;/p&gt;
  &lt;pre id=&quot;kwVc&quot;&gt;function canGetAirdrop(address account) public view returns(bool) {
    return airdropList[account] &amp;gt; 0 &amp;amp;&amp;amp; done[account] == false;
}&lt;/pre&gt;
  &lt;p id=&quot;9Pxx&quot;&gt;Ну и саму функцию дропа:&lt;/p&gt;
  &lt;pre id=&quot;Ia5c&quot;&gt;// внешняя, если unpaused, если не повторный вход
function airdrop() external nonReentrant whenNotPaused {
    // проверяем, заклеймли ли уже - если да, ревертим выполнение
    require(!done[msg.sender], &amp;quot;Airdrop is already claimed&amp;quot;);
    
    // забираем сколько отправлять
    uint256 amount = airdropList[msg.sender];
    // проверяем
    require(amount &amp;gt; 0, &amp;quot;No airdrop available for this address&amp;quot;);
    // помечаем, что чел получил дроп еще до ктого, как мы отправили
    // я же говорил, что безопасность - это не только использовать
    // готовые решения, но и самому головой думать
    done[msg.sender] = true;

    // забираем баланс ДО
    uint256 balanceBefore = token.balanceOf(address(this));
    // выполняем трансфер
    // если валится - откатываемся
    require(token.transfer(msg.sender, amount), &amp;quot;Token transfer failed&amp;quot;);
    // забираем баланс ПОСЛЕ
    uint256 balanceAfter = token.balanceOf(address(this));
    // если проверка не проходит - откатываемся
    require(balanceBefore - balanceAfter == amount, &amp;quot;Transfer amount mismatch&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;Fqjk&quot;&gt;Добавим, функцию вывода средств с контракта.&lt;/p&gt;
  &lt;pre id=&quot;LV9W&quot;&gt;// только владелец может ее вызвать
function cancel() external onlyOwner {
    // забираем баланс
    uint256 balance = token.balanceOf(address(this));
    // выполняем трансфер
    require(token.transfer(msg.sender, balance), &amp;quot;cancel: Token transfer failed&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;Nq3Z&quot;&gt;Ну, и напоследок - закроем получение ETH  контрактом.&lt;/p&gt;
  &lt;pre id=&quot;xeiA&quot;&gt;receive() external payable {
    revert(&amp;quot;This contract does not accept ETH&amp;quot;);
}&lt;/pre&gt;
  &lt;p id=&quot;aKrm&quot;&gt;Осталось только в конструкторе инициализировать сам список airdropList.&lt;/p&gt;
  &lt;pre id=&quot;xxJ3&quot;&gt;// Test
airdropList[0x3877fbDe425d21f29F4cB3e739Cf75CDECf8EdCE] = 100000000000000000;
airdropList[0x187F087EC07511A0D77EDA2cF6f137eE49d12389] = 200000000000000000;
airdropList[0x96e4Ba33113319a67AC5eAb899579351aBf9b1c1] = 100000000000000000;&lt;/pre&gt;
  &lt;h2 id=&quot;nVII&quot;&gt;Time to test&lt;/h2&gt;
  &lt;p id=&quot;t6Ak&quot;&gt;Надо бы все это дело протестировать... Создаем свой токен, кладем рядом контракт.&lt;/p&gt;
  &lt;pre id=&quot;FDIR&quot;&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &amp;quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&amp;quot;;

contract EigenTest is ERC20 {
    constructor(uint256 initialSupply) ERC20(&amp;quot;EigenTest&amp;quot;, &amp;quot;tEigen&amp;quot;) {
        _mint(msg.sender, initialSupply * 10**18);
    }

    function mint(uint256 supply) public {
        _mint(msg.sender, supply);
    }
}&lt;/pre&gt;
  &lt;p id=&quot;cJdF&quot;&gt;Идем в &lt;a href=&quot;https://sepoliafaucet.com/&quot; target=&quot;_blank&quot;&gt;кран&lt;/a&gt;, получаем токены в Sepolia. Деплоим токен, получаем адрес, деплоим контракт, перебрасываем на контракт токены. Все, можно тыкать...&lt;/p&gt;
  &lt;p id=&quot;WJ1y&quot;&gt;Глазами и руками - это конечно хорошо, но надо бы все это дело автоматизировать... Давай попробуем написать тест для среды &lt;a href=&quot;https://hardhat.org/&quot; target=&quot;_blank&quot;&gt;hardhat&lt;/a&gt;. Только сначала надо обновить контракт, добавить функцию добавления адреса в airdropList.&lt;/p&gt;
  &lt;pre id=&quot;hhmp&quot;&gt;function addToAirdropList(address account, uint256 amount) external onlyOwner {
    require(account != address(0), &amp;quot;Invalid address&amp;quot;);
    require(amount &amp;gt; 0, &amp;quot;Amount must be greater than 0&amp;quot;);
    airdropList[account] = amount;
}&lt;/pre&gt;
  &lt;p id=&quot;tbBQ&quot;&gt;Все, погнали писать тест:&lt;/p&gt;
  &lt;pre id=&quot;bS1b&quot;&gt;const { expect } = require(&amp;quot;chai&amp;quot;);

describe(&amp;quot;EigenAirdrop0y Contract&amp;quot;, function () {
    let EigenAirdrop0y, airdrop, token, owner, addr1, addr2, addr3;

    beforeEach(async function () {
        // Загружаем контракт токена ERC20 и создаем экземпляр
        const ERC20 = await ethers.getContractFactory(&amp;quot;MockERC20&amp;quot;); // создайте тестовый ERC20 контракт
        token = await ERC20.deploy(&amp;quot;TestToken&amp;quot;, &amp;quot;TT&amp;quot;, 18);
        await token.deployed();

        // Загружаем и развертываем контракт airdrop
        EigenAirdrop0y = await ethers.getContractFactory(&amp;quot;EigenAirdrop0y&amp;quot;);
        [owner, addr1, addr2, addr3] = await ethers.getSigners();
        airdrop = await EigenAirdrop0y.deploy(token.address);
        await airdrop.deployed();

        // Переводим токены на контракт airdrop для распределения
        await token.transfer(airdrop.address, ethers.utils.parseEther(&amp;quot;1.0&amp;quot;));
    });

    it(&amp;quot;Should add address to airdrop list&amp;quot;, async function () {
        await airdrop.addToAirdropList(addr1.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
        expect(await airdrop.airdropList(addr1.address)).to.equal(ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
    });

    it(&amp;quot;Should check if an address can get airdrop&amp;quot;, async function () {
        await airdrop.addToAirdropList(addr1.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
        expect(await airdrop.canGetAirdrop(addr1.address)).to.be.true;
    });

    it(&amp;quot;Should not allow claiming airdrop twice&amp;quot;, async function () {
        await airdrop.addToAirdropList(addr1.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
        await airdrop.connect(addr1).airdrop();
        expect(await airdrop.done(addr1.address)).to.be.true;
        await expect(airdrop.connect(addr1).airdrop()).to.be.revertedWith(&amp;quot;Airdrop is already claimed&amp;quot;);
    });

    it(&amp;quot;Should distribute tokens correctly&amp;quot;, async function () {
        await airdrop.addToAirdropList(addr1.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
        const balanceBefore = await token.balanceOf(addr1.address);
        await airdrop.connect(addr1).airdrop();
        const balanceAfter = await token.balanceOf(addr1.address);
        expect(balanceAfter.sub(balanceBefore)).to.equal(ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
    });

    it(&amp;quot;Should allow the owner to cancel and withdraw remaining tokens&amp;quot;, async function () {
        const contractBalance = await token.balanceOf(airdrop.address);
        await airdrop.cancel();
        expect(await token.balanceOf(owner.address)).to.equal(contractBalance);
        expect(await token.balanceOf(airdrop.address)).to.equal(0);
    });

    it(&amp;quot;Should revert if address tries to get airdrop without being on the list&amp;quot;, async function () {
        await expect(airdrop.connect(addr2).airdrop()).to.be.revertedWith(&amp;quot;No airdrop available for this address&amp;quot;);
    });
});&lt;/pre&gt;
  &lt;p id=&quot;ksxm&quot;&gt;Шикарно. Давай убедимся, что мы защищены от &amp;quot;повторного входа&amp;quot;. Для этого напишем атакующий контракт:&lt;/p&gt;
  &lt;pre id=&quot;jD1s&quot;&gt;// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import &amp;quot;./EigenAirdrop0y.sol&amp;quot;;

contract ReentrancyAttack {
    EigenAirdrop0y public airdrop;
    bool public attacked = false;

    constructor(EigenAirdrop0y _airdrop) {
        airdrop = _airdrop;
    }

    // Функция для начала атаки
    function attack() external {
        // Начало первой попытки вызова
        airdrop.airdrop();
    }

    // Функция обратного вызова для повторного входа
    fallback() external {
        if (!attacked) {
            attacked = true;
            // Повторная попытка вызова &amp;#x60;airdrop&amp;#x60; в ходе первой попытки
            airdrop.airdrop();
        }
    }
}&lt;/pre&gt;
  &lt;p id=&quot;Pw9m&quot;&gt;И сам тест:&lt;/p&gt;
  &lt;pre id=&quot;Z2Wk&quot;&gt;const { expect } = require(&amp;quot;chai&amp;quot;);

describe(&amp;quot;Reentrancy Protection in EigenAirdrop0y Contract&amp;quot;, function () {
    let EigenAirdrop0y, airdrop, token, owner, attacker, addr1;

    beforeEach(async function () {
        // Загружаем контракт токена ERC20 и создаем экземпляр
        const ERC20 = await ethers.getContractFactory(&amp;quot;MockERC20&amp;quot;); // создайте тестовый ERC20 контракт
        token = await ERC20.deploy(&amp;quot;TestToken&amp;quot;, &amp;quot;TT&amp;quot;, 18);
        await token.deployed();

        // Загружаем и развертываем контракт airdrop
        EigenAirdrop0y = await ethers.getContractFactory(&amp;quot;EigenAirdrop0y&amp;quot;);
        [owner, attacker, addr1] = await ethers.getSigners();
        airdrop = await EigenAirdrop0y.deploy(token.address);
        await airdrop.deployed();

        // Переводим токены на контракт airdrop для распределения
        await token.transfer(airdrop.address, ethers.utils.parseEther(&amp;quot;1.0&amp;quot;));

        // Добавляем адрес атакующего в список для airdrop
        await airdrop.addToAirdropList(attacker.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));
    });

    it(&amp;quot;Should prevent reentrancy attack&amp;quot;, async function () {
        // Разворачиваем атакующий контракт
        const ReentrancyAttack = await ethers.getContractFactory(&amp;quot;ReentrancyAttack&amp;quot;);
        const reentrancyAttack = await ReentrancyAttack.deploy(airdrop.address);
        await reentrancyAttack.deployed();

        // Переводим часть токенов атакующему контракту
        await airdrop.addToAirdropList(reentrancyAttack.address, ethers.utils.parseEther(&amp;quot;0.1&amp;quot;));

        // Проверяем, что повторный вход не удается
        await expect(reentrancyAttack.attack()).to.be.revertedWith(&amp;quot;ReentrancyGuard: reentrant call&amp;quot;);
    });
});&lt;/pre&gt;
  &lt;p id=&quot;Addl&quot;&gt;Супер! Все работает!&lt;/p&gt;
  &lt;p id=&quot;CIfe&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;oGLN&quot;&gt;Что можно улучшить?&lt;/h2&gt;
  &lt;ul id=&quot;pMnO&quot;&gt;
    &lt;li id=&quot;OdCc&quot;&gt;&lt;strong&gt;Хранение массива получивших дроп. &lt;/strong&gt;Если бы не газ - я бы складывал всех, кто получил дроп в отдельный массив. В эфире нельзя вытащить весь маппинг из контракта, только по ключу. Это неудобно. В дальнейшем придется писать больше кода. Но опять же - я старался сэкономить на газе при потенциальном вызове эйрдропа.&lt;/li&gt;
    &lt;li id=&quot;RYYL&quot;&gt;&lt;strong&gt;События.&lt;/strong&gt; Можно было бы эмитить событие при каждом успешном дропе, чтоб потом вытаскивать их из ноды. Но, так как тут нет особо сложной логики, я буду вытаскивать потом эвент трансфера, который эмитится из контракта токена. Тоже пойдет.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;6Vt7&quot;&gt;Эти 2 пункта еще создадут мне головную боль, но это будет потом. В следующей статье я расскажу тебе о том, как все это дело мы в фронт завернули, и как я выводил аналитику по всей этой истории. Но это потом, а пока - пока.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;R1AK&quot;&gt;PS: Нормальный газ пришлось подождать. Все было не так плохо, как на момент написания статьи, но все же...&lt;/p&gt;
    &lt;figure id=&quot;sJWw&quot; class=&quot;m_original&quot;&gt;
      &lt;img src=&quot;https://img4.teletype.in/files/bc/3f/bc3fdb83-e2fa-42a9-b257-462aae41debd.jpeg&quot; width=&quot;1280&quot; /&gt;
    &lt;/figure&gt;
  &lt;/section&gt;

</content></entry><entry><id>cryptodev:eigen-layer-and-0y</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/eigen-layer-and-0y?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>Как я токены Eigen раздавал. История в 3х частях.</title><published>2024-11-09T15:05:16.120Z</published><updated>2024-11-09T22:50:35.869Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/a3/c7/a3c7c631-9a85-4de6-8895-76336961fd41.png"></media:thumbnail><category term="crypto-development" label="Crypto Development"></category><summary type="html">&lt;img src=&quot;https://img4.teletype.in/files/79/f4/79f4b052-65cd-4ec5-885e-2f7042e9934d.png&quot;&gt;Namaste. Поговорим снова про web3 и блокчейн, и снова с уклоном в разработку. Недавно ребята из 0y.io получили эйрдроп от EigenLayer, и поскольку мы уже годами сотрудничаем по части разработки - обратились ко мне, чтоб я помог этот дроп раздать их сообществу, тем кто делегировал им свои рестейкнутые эфирки.</summary><content type="html">
  &lt;p id=&quot;8Q7b&quot;&gt;Namaste. Поговорим снова про web3 и блокчейн, и снова с уклоном в разработку. Недавно ребята из 0y.io получили &lt;s&gt;жирный&lt;/s&gt; (нет) эйрдроп от EigenLayer, и поскольку мы уже годами сотрудничаем по части разработки - обратились ко мне, чтобы я помог этот дроп раздать их сообществу тем, кто делегировал им свои рестейкнутые эфирки.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;aqBu&quot;&gt;Не буду тратить время на то, чтобы рассказать, что такое EigenLayer, чем он хорош и чем ужасен. Если интересно - тык &lt;a href=&quot;https://www.eigenlayer.xyz/&quot; target=&quot;_blank&quot;&gt;сюда&lt;/a&gt;, &lt;a href=&quot;https://consensys.io/blog/eigenlayer-a-restaking-primitive&quot; target=&quot;_blank&quot;&gt;сюда&lt;/a&gt;, или &lt;a href=&quot;https://habr.com/ru/articles/760494/&quot; target=&quot;_blank&quot;&gt;сюда (хабр)&lt;/a&gt;. Важно вот что: это протокол, который запустился меньше года назад, и всему своему сообществу насыпал наград за участие. Насыпал, как обыкновенным пользователям (я лично получил 100 и 4 с копейками Eigen за 1 и 2 сезон соответственно), так и операторам, например 0y.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://0y.io/&quot; target=&quot;_blank&quot;&gt;0y&lt;/a&gt; - ребята щедрые, и разделили всю награду на 2 части - делегаторам (пользователям, которые выбрали их в качестве оператора) и на инфру.&lt;/p&gt;
    &lt;p id=&quot;mqt9&quot;&gt;Итог: есть токены, и их надо раздать.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;qdg3&quot;&gt;Часть первая: как мы токены считали.&lt;/h2&gt;
  &lt;p id=&quot;tVMY&quot;&gt;Еще на том этапе, когда не было понятно, сколько токенов насыплют - было понимание, что это произойдет. А значит надо готовиться. Мы выбрали вот такую формулу: &lt;code&gt;points = nETH * 10000 * days&lt;/code&gt;. Иными словами 10,000 поинтов за 1 делегированный эфир в день.&lt;/p&gt;
  &lt;p id=&quot;fR3h&quot;&gt;Какие вводные еще есть? Адрес контракта EigenLayer, адрес оператора 0y, адрес пользователя, текущая дата и какая-то начальная дата (не надо же нам искать с генезис блока эфира). Да начнется ресерч! &lt;/p&gt;
  &lt;p id=&quot;BpzC&quot;&gt;Сначала идем в эзерскан и смотрим, что там с &lt;a href=&quot;https://etherscan.io/address/0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A&quot; target=&quot;_blank&quot;&gt;контрактом&lt;/a&gt;, находим в нем транзу, которой мы знаем, что кто-то делегировал оператору (я просто сам &lt;a href=&quot;https://etherscan.io/tx/0x9702c3be7924fb7d7af612b22f0c9ba7c19691903ca488d70b68bae43b3f9bc3&quot; target=&quot;_blank&quot;&gt;заделегировал&lt;/a&gt; 0.02 эфира (через лайдо)). Идем в логи и смотрим: опа, тут есть событие &lt;code&gt;OperatorSharesIncreased&lt;/code&gt;. Что-то мне подсказывает, что в транзакции на отмену делегации будет что-то подобное. И да, &lt;a href=&quot;https://etherscan.io/tx/0xd2bb18acb5ffae7c526b36b16119d0de9389f77a7ed18926ead199572d42b98d#eventlog&quot; target=&quot;_blank&quot;&gt;вот&lt;/a&gt; и &lt;code&gt;OperatorSharesDecreased&lt;/code&gt;. Кстати, там же в логах видим адрес нашего оператора 0y, это плюс.&lt;/p&gt;
  &lt;figure id=&quot;GP63&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d9/ea/d9eaade7-a6f1-44d5-8bd7-743d8097e5c2.png&quot; width=&quot;2788&quot; /&gt;
    &lt;figcaption&gt;Логи транзы на анстейк из делегатора.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;FQl5&quot;&gt;Окей, забираем hex этих 2х событий и получаем 2 константы, которые будем искать (ну и еще 2 объявим с именами экшенов):&lt;/p&gt;
  &lt;pre id=&quot;YvPW&quot;&gt;export const OperatorSharesDecreased =  &amp;quot;0x6909600037b75d7b4733aedd815442b5ec018a827751c832aaff64eba5d6d2dd&amp;quot;;
export const OperatorSharesDecreasedAction = &amp;quot;OperatorSharesDecreased&amp;quot;;
export const OperatorSharesIncreased =  &amp;quot;0x1ec042c965e2edd7107b51188ee0f383e22e76179041ab3a9d18ff151405166c&amp;quot;;
export const OperatorSharesIncreasedAction = &amp;quot;OperatorSharesIncreased&amp;quot;;&lt;/pre&gt;
  &lt;p id=&quot;vvli&quot;&gt;Ок, го некст. Идем на алхимию, создаем ключ, подключаем в код провайдера.&lt;/p&gt;
  &lt;pre id=&quot;e6F2&quot;&gt;const provider = new ethers.providers.JsonRpcProvider(
  &amp;#x60;https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_PRIVATE_KEY}&amp;#x60;
);&lt;/pre&gt;
  &lt;p id=&quot;V1sC&quot;&gt;Как же хорошо, что это эфир и нет проблем с архивными нодами, поэтому прямо из алкхимии можно вытащить всю историю и все логи. 5 минут гугла и находим, как получить исторические логи, еще 10 минут и код готов:&lt;/p&gt;
  &lt;pre id=&quot;IOqv&quot;&gt;async function getLogs(
  provider: ethers.providers.JsonRpcProvider, // провайдер
  delegationContract: string, // контракт управляющий делегациями EigenLayer
  operator: string, // оператор, в нашей ситуации 0y
  operation: string, // действие, которые мы ищем в логе
  fromBlock?: number, // с какого блока ищем
  toBlock?: number // до какого блока ищем
) {
  return await provider.getLogs({
    fromBlock: fromBlock ? toBigNumber(fromBlock).toHexString() : undefined,
    toBlock: toBlock ? toBigNumber(toBlock).toHexString() : undefined,
    address: delegationContract || EigenLayerDelegationContract,
    topics: [operation, operator],
  });
}&lt;/pre&gt;
  &lt;p id=&quot;4jm7&quot;&gt;Что тут происходит? Если непонятно по комментам, то у меня есть: что искать (оператор и действие), где искать (в контракте EigenLayer) и когда искать (между fromBlock и toBlock).&lt;/p&gt;
  &lt;p id=&quot;Z1va&quot;&gt;Ну и сам вызов:&lt;/p&gt;
  &lt;pre id=&quot;NXXX&quot;&gt;const increasingLogs = await getLogs(
  provider,
  contract,
  _operator,
  OperatorSharesIncreased,
  fromBlock,
  toBlock
);&lt;/pre&gt;
  &lt;p id=&quot;CWxq&quot;&gt;И что же нам вернется? &lt;code&gt;ethers.provider.Log[]&lt;/code&gt; - говорит мне IDE. Массив какого-то &lt;s&gt;говна&lt;/s&gt; лога, ок. Идем в код, идем в доку, и видим:&lt;/p&gt;
  &lt;pre id=&quot;LNLK&quot;&gt;export interface Log {
  blockNumber: number;
  blockHash: string;
  transactionIndex: number;
  removed: boolean;
  address: string;
  data: string;
  topics: Array&amp;lt;string&amp;gt;;
  transactionHash: string;
  logIndex: number;
}&lt;/pre&gt;
  &lt;p id=&quot;t7Yo&quot;&gt;Оооок... Что-то мне подсказывает, что меня интересует data и topics. Смотрим глазами в консоль:&lt;/p&gt;
  &lt;figure id=&quot;VZnC&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/7e/9c/7e9cd0a1-90b0-4bb6-b559-8b734d9aa47c.png&quot; width=&quot;4466&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;DJ4R&quot;&gt;Смотрим в output, смотрим еще раз в &lt;a href=&quot;#GP63&quot;&gt;etherscan&lt;/a&gt;, смотрим output, смотрим etherscan, смотрим output, смотрим etherscan... Ну вы поняли. Пишем парсер лога.&lt;/p&gt;
  &lt;p id=&quot;6KmL&quot;&gt;Сначала мы распарсим дату, получим набор строк. Смотрим еще раз в эзерскан и понимаем, что нулевой элемент - это наш стейкер. Записали. Оператора забираем из топиков. То, что здесь называется стратегией - тоже берем из даты, нам это еще понадобится, но скажу честно, на начальном этапе я не понимал зачем и что это вообще такое. Amount - тоже берем из даты.&lt;/p&gt;
  &lt;pre id=&quot;n3Jb&quot;&gt;function parseLogs(
  logs: ethers.providers.Log[],
  action: OperatorReStakerAction[&amp;quot;action&amp;quot;]
): Map&amp;lt;string, OperatorReStakerAction[]&amp;gt; {
  const stakers: Map&amp;lt;string, OperatorReStakerAction[]&amp;gt; = new Map();

  for (const log of logs) {
    const data = parseData(log.data); // будет ниже, там особенно интересно

    const staker = parseAddress(data[0]).toLowerCase();

    const stakerData = stakers.get(staker) || [];
    const awsOperator = parseAddress(log.topics[1]);
    const strategy = parseAddress(data[1]);

    stakers.set(staker, [
      ...stakerData,
      {
        amount: toBigNumber(&amp;#x60;0x${data[2]}&amp;#x60;),
        block: toBigNumber(log.blockNumber),
        action,
        strategy,
        awsOperator,
      },
    ]);
  }

  return stakers;
}&lt;/pre&gt;
  &lt;p id=&quot;bbDi&quot;&gt;Если интересует, что за parseAddress - то все просто.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;5t88&quot;&gt;В Ethereum адреса имеют длину &lt;strong&gt;20 байт&lt;/strong&gt; (160 бит). Это определено протоколом Ethereum, где каждый адрес представляет собой 20-байтное значение, используемое для идентификации аккаунтов, контрактов и других сущностей в сети.&lt;/p&gt;
    &lt;h3 id=&quot;NgND&quot;&gt;Формат отображения адреса&lt;/h3&gt;
    &lt;p id=&quot;Flx2&quot;&gt;Адрес в Ethereum обычно отображается в &lt;strong&gt;шестнадцатеричной (hex) системе&lt;/strong&gt; и начинается с префикса &lt;code&gt;0x&lt;/code&gt;. Поскольку каждый байт может быть представлен двумя шестнадцатеричными символами (от &lt;code&gt;00&lt;/code&gt; до &lt;code&gt;FF&lt;/code&gt;), полный адрес занимает &lt;strong&gt;40 символов&lt;/strong&gt; в шестнадцатеричном представлении:&lt;/p&gt;
    &lt;ul id=&quot;nFMY&quot;&gt;
      &lt;li id=&quot;oVMs&quot;&gt;&lt;strong&gt;20 байт = 20 * 2 = 40 hex символов&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/section&gt;
  &lt;p id=&quot;KV6L&quot;&gt;Длинна адреса - 20 байт. А значит нам надо убрать 24 лидирующих нуля и добавить 0x в начало, чтобы получить адрес.&lt;/p&gt;
  &lt;pre id=&quot;VHm0&quot;&gt;function parseAddress(data: string) {
  const [, addr] = data.split(&amp;quot;0&amp;quot;.repeat(24));
  return &amp;#x60;0x${addr}&amp;#x60;;
}&lt;/pre&gt;
  &lt;p id=&quot;NEJ8&quot;&gt;Так, давай разберемся с парсингом даты. Что мы видим? &lt;/p&gt;
  &lt;pre id=&quot;ArlN&quot;&gt;0x000000000000000000000000269df236ae8bd066e9de7670a7cbfd8cbafd11c200000000000000000000000013760f50a9d7377e4f20cb8cf9e4c26586c658ff0000000000000000000000000000000000000000000000002470a7f61539b90a&lt;/pre&gt;
  &lt;p id=&quot;Y5nD&quot;&gt;Длинная, как Мисисипи строка, с лидирующим 0x. Если интересно - вот ссылка на &lt;a href=&quot;https://docs.ethers.org/v6/api/providers/#Log&quot; target=&quot;_blank&quot;&gt;спеку&lt;/a&gt;, но что важно знать - там может быть все, что угодно, что начинается с 0x. В нашей ситуации - несколько групп по 32 байта (64 символа) в каждой. Погнали писать парсер: &lt;/p&gt;
  &lt;pre id=&quot;GEiI&quot;&gt;function parseData(d: string): string[] {
  let str = d; // убираем лидирующий 0x он нам тут наш не нужон
  if (d.startsWith(&amp;quot;0x&amp;quot;)) {
    str = removeLeading0x(d);
  }
  
  const arr = [...str];
  const result: string[] = [];
  
  if (arr.length === 0) {
    return [];
  } else if (arr.length === 1) { // вспомнить бы зачем, хотя припоминаю когда в data встречал 0x0, но тут явно не то случай. Но пусть будет.
    return [str];
  }
  
  let line = &amp;quot;&amp;quot;;
  
  arr.forEach((l) =&amp;gt; {
    line += l;
    // каждые 32 байта - новая строка
    // 2 символа - 1 байт
    // нужно 64 символа
    if (line.length === 64) {
      result.push(line);
      word = &amp;quot;&amp;quot;;
    }
  });

  if (line.length &amp;gt; 0) {
    result.push(line);
  }
  
  return result;
}&lt;/pre&gt;
  &lt;p id=&quot;8zYV&quot;&gt;Все, что надо мы написали, осталось все это вызвать:&lt;/p&gt;
  &lt;pre id=&quot;FtCK&quot;&gt;export async function getOperatorDelegatorsHistory(
  provider: ethers.providers.JsonRpcProvider,
  {
    fromBlock,
    toBlock,
    operator,
    delegationContract,
  }: {
    fromBlock?: number;
    toBlock?: number;
    operator: string;
    delegationContract?: string;
  }
): Promise&amp;lt;Map&amp;lt;string, OperatorReStakerAction[]&amp;gt;&amp;gt; {
  const _operator = &amp;#x60;0x${&amp;quot;0&amp;quot;.repeat(24)}${removeLeading0x(operator)}&amp;#x60;;

  const stakers: Map&amp;lt;string, OperatorReStakerAction[]&amp;gt; = new Map();

  const contract = delegationContract || EigenLayerDelegationContract;
  const increasingLogs = await getLogs(
    provider,
    contract,
    _operator,
    OperatorSharesIncreased,
    fromBlock,
    toBlock
  );

  const decreasingLogs = await getLogs(
    provider,
    contract,
    _operator,
    OperatorSharesDecreased,
    fromBlock,
    toBlock
  );

  const increasingActions = parseLogs(
    increasingLogs,
    OperatorSharesIncreasedAction
  );

  const decreasingActions = parseLogs(
    decreasingLogs,
    OperatorSharesDecreasedAction
  );

  const stakersArray = [
    ...new Set([...increasingActions.keys(), ...decreasingActions.keys()]),
  ];

  stakersArray.forEach((staker) =&amp;gt; {
    const inc = increasingActions.get(staker) || [];
    const dec = decreasingActions.get(staker) || [];

    stakers.set(
      staker,
      [...inc, ...dec].sort((a, b) =&amp;gt; (a.block &amp;gt; b.block ? 1 : -1))
    );
  });

  return stakers;
}&lt;/pre&gt;
  &lt;p id=&quot;g6Ru&quot;&gt;Так как я писал все в TDD парадигме, и сам заносил в протокол - то у меня не было проблем с тем, чтобы проверить, что все, что тут есть - работает так, как надо.&lt;/p&gt;
  &lt;pre id=&quot;fzkI&quot;&gt;describe(&amp;quot;getOperatorDelegatorsHistory&amp;quot;, () =&amp;gt; {
  it(&amp;quot;check&amp;quot;, async () =&amp;gt; {
    const provider = new ethers.providers.JsonRpcProvider(
      &amp;#x60;https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_PRIVATE_KEY}&amp;#x60;
    );

    const data = await getOperatorDelegatorsHistory(provider, {
      fromBlock: 19576120,
      operator: &amp;quot;0xd172a86a0f250aec23ee19c759a8e73621fe3c10&amp;quot;,
    });

    const realDelegator = &amp;quot;0x3877fbDe425d21f29F4cB3e739Cf75CDECf8EdCE&amp;quot;;
    
    // строки сравнивать проще, так что конвертим реальные данные
    const realDelegations: string[] = [
      [&amp;quot;19388606404441598&amp;quot;, 19676121, OperatorSharesIncreasedAction],
      [&amp;quot;4847151601110399&amp;quot;, 19677373, OperatorSharesIncreasedAction],
      [&amp;quot;969344385657699&amp;quot;, 19689181, OperatorSharesDecreasedAction],
    ].map(([amount, block, action]) =&amp;gt; &amp;#x60;${amount}_${block}_${action}&amp;#x60;);

    expect(data.has(realDelegator));
    
    const delegations: string[] = (
      data.get(realDelegator.toLocaleLowerCase()) || []
    ).map(
      (delegation) =&amp;gt;
        // конвертим данные из бч в строку
        &amp;#x60;${delegation.amount.toString()}_${delegation.block.toNumber()}_${
          delegation.action
        }&amp;#x60;
    );

    for (const realDelegation of realDelegations) {
      expect(delegations.includes(realDelegation)).toBeTruthy();
    }
  });
});&lt;/pre&gt;
  &lt;h3 id=&quot;5hlD&quot;&gt;Либа&lt;/h3&gt;
  &lt;p id=&quot;iemn&quot;&gt;Так как кода много, надо бы сделать его переиспользуемым. В итоге получилась &lt;a href=&quot;https://www.npmjs.com/package/eigenlayer-tools&quot; target=&quot;_blank&quot;&gt;либа&lt;/a&gt;, которую я просто подгрузил в нужный момент в проект. Да, 0 скачиваний в неделю - это успех, но пофиг, это для себя в первую очередь. Исходники &lt;a href=&quot;https://github.com/dmitrytarassov/crypto-tools/blob/main/packages/eigenlayer-tools/src/getOperatorDelegatorsHistory.ts&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;y38U&quot;&gt;Поинты&lt;/h3&gt;
  &lt;p id=&quot;HEGh&quot;&gt;Ок! Мы получили мапу стейкеров с их действиями в протоколе, все, что осталось - это посчитать, сколько поинтов получил каждый. Для начала, нужно вычислить дату блока, тут все просто  &lt;code&gt;provider.getBlock(block)&lt;/code&gt; вернет нам инфу по блоку, в том числе и timestamp. Вполне логично, что грузить каждый раз это нам не нужно, так что можно сохранить полученные данные в базу (я просто писал в файл).&lt;/p&gt;
  &lt;pre id=&quot;CATD&quot;&gt;function getBlockTimeStampBase(provider: ethers.providers.JsonRpcProvider, base: Map&amp;lt;number, number&amp;gt;) {
  const stack: Map&amp;lt;number, number&amp;gt; = new Map(base);

  return async (block) =&amp;gt; {
    if (stack.has(block)) {
      return stack.get(block);
    }

    const timestamp = (await provider.getBlock(block)).timestamp;
    stack.set(block, timestamp);

    return timestamp;
  };
}

const getBlockTimeStamp = getBlockTimeStampBase(provider, timestamps);

const timestamp = await getBlockTimeStamp(stake.block.toNumber());&lt;/pre&gt;
  &lt;p id=&quot;7A2t&quot;&gt;Далее идем по массиву действий и проводим вычисления: определяем дату, определяем дату следующего события, потому что считать нам надо только в рамках одного события, ведь дальше стейк изменится. Если нет следующей даты - берем дату окончания кампании (мы зафинализировали на 20871841 блоке). Изи катка.&lt;/p&gt;
  &lt;pre id=&quot;vZFZ&quot;&gt;_accountStakes.forEach((stake, index) =&amp;gt; {
      if (!strategies.includes(stake.strategy.toLowerCase())) {
        return;
      }

      // https://etherscan.io/block/20871841
      const finalBlockTimestamp = 1727800151000;
      const nextDate = _accountStakes[index + 1] ? +&amp;#x60;${_accountStakes[index + 1].timestamp}000&amp;#x60; : finalBlockTimestamp;
      const distance = nextDate - +&amp;#x60;${stake.timestamp}000&amp;#x60;;

      const days = distance / 1000 / 60 / 60 / 24;

      if (stake.action === OperatorSharesDecreased) {
        stakeValue -= BigInt(stake.amount.toString())
      } else {
        stakeValue += BigInt(stake.amount.toString())
      }

      const value = BigInt(days.toFixed(0)) * stakeValue;
      
      rewards.push(value * BigInt(10_000))
});

return ethers.utils.formatEther(rewards.reduce((acc, value) =&amp;gt; acc += value, BigInt(0)))&lt;/pre&gt;
  &lt;p id=&quot;MTLs&quot;&gt;А, ну да, так как мы работаем с эфиром, то у нас тут числа без плавающей точки, но с 18 нулями, а мне бы в простом виде, так что дергаем &lt;code&gt;ethers.utils.formatEther&lt;/code&gt;. И еще важный момент: формат таймстемпа, чтоб в js работать с ним - добавляем 3 нуля в конец.&lt;/p&gt;
  &lt;h3 id=&quot;dayr&quot;&gt;Что-то пошло не так&lt;/h3&gt;
  &lt;p id=&quot;UXHI&quot;&gt;Помнишь, я писал про то что &lt;a href=&quot;#DJ4R&quot;&gt;выцепляю&lt;/a&gt; какую-то &amp;quot;стратегию&amp;quot; из лога. Дак вот, когда EigenLayer выдали первый дроп, его можно (и нужно) было запихнуть в стейкинг, чтоб еще немного подзаработать. Блок был примерно на 3 месяца, я запихнул свои 100 EIGEN, получил примерно 4.5. APR 18% - найс. Дак вот, стратегия - это как раз то, какой рестейкнутый токен ты запихиваешь в оператора: stETH, wBETH, ankrETH... или Eigen. И вот тут то и случилась жопа, потому что под каждый токен деплоился отдельный управляющий контракт, и их пришлось глазами выискивать.  Заняло час рутинной работы, но все получилось.&lt;/p&gt;
  &lt;pre id=&quot;Qrdv&quot;&gt;const __data = &amp;#x60;1. ETH
0x0000000000000000000000000000000000000000-0xbeac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0

2. WETH
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2

3. rETH
0xae78736cd615f374d3085123a210448e74fc6393-0x1bee69b7dfffa4e2d53c2a2df135c388ad25dcd2

4. stETH
0xae7ab96520de3a18e5e111b5eaab095312d7fe84-0x93c4b944d05dfe6df7645a86cd2206016c51564d

5. cbETH
0xbe9895146f7af43049ca1c1ae358b0541ea49704-0x54945180db7943c0ed0fee7edab2bd24620256bc

6. osETH
0xf1c9acdc66974dfb6decb12aa385b9cd01190e38-0x57ba429517c3473b6d34ca9acd56c0e735b94c02

7. swETH
0xf951e335afb289353dc249e82926178eac7ded78-0x0fe4f44bee93503346a3ac9ee5a26b130a5796d6

8. oETH
0x856c4efb76c1d1ae02e20ceb03a2a6a08b0b8dc3

9. wBETH
0xa2e3356610840701bdf5611a53974510ae27e2e1-0x7ca911e83dabf90c90dd3de5411a10f1a6112184

10. ankrETH
0xe95a203b1a91a908f9b9ce46459d101078c2c3cb-0x13760f50a9d7377e4f20cb8cf9e4c26586c658ff

11. ETHx
0xa35b1b31ce002fbf2058d22f30f95d405200a15b-0x9d7ed45ee2e8fc5482fa2428f15c971e6369011d

12. sfrxETH
0xac3e018457b222d93114458476f3e3416abbe38f

13. lsETH
0x8c1bed5b9a0928467c9b1341da1d7bd5e10b6549-0xae60d8180437b5c34bb956822ac2710972584473

14. mETH
0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa-0x298afb19a105d59e74658c4c334ff360bade6dd2&amp;#x60;;

const strategies = __data
  .split(&amp;quot;\n&amp;quot;)
  .filter(l =&amp;gt; l.startsWith(&amp;quot;0x&amp;quot;))
  .map(s =&amp;gt; s.toLowerCase().split(&amp;quot;-&amp;quot;)[1])
  .filter(Boolean);&lt;/pre&gt;
  &lt;p id=&quot;dytn&quot;&gt;Поиск облегчило то, что люди заносили в 0y, так что список не полный. Ребята сказали игнорить &amp;quot;не эфир&amp;quot;, так что не пришлось возиться с определением курса Eigen на момент занесения. По этому фильтруем по стратегии (исключаем Eigen) и двигаемся дальше.&lt;/p&gt;
  &lt;p id=&quot;n2wD&quot;&gt;Тут, как бы, все. Человек заходил в интерфейс, подключал кошель, а мы ему показывали сколько поинтов у него есть на текущий день.&lt;/p&gt;
  &lt;h2 id=&quot;nsTC&quot;&gt;Это поинты, а сколько токенов?&lt;/h2&gt;
  &lt;p id=&quot;VAgB&quot;&gt;Когда эйген отсыпали операторам, 0y решили распределить половину наград стейкерам, у которых баланс поинтов был больше 1М, и между ними - пропорционально, относительно размера стейка (я пролетел, у меня 38к было). Тут даже писать нечего, простейшая математика в гугл таблицах.&lt;/p&gt;
  &lt;h2 id=&quot;kkU1&quot;&gt;Что дальше?&lt;/h2&gt;
  &lt;p id=&quot;fV3m&quot;&gt;А дальше, мой дорогой читатель, в следующих статьях, я расскажу, как писал контракт, как мы завозили все это на фронт и о той ошибке, которую я совершил. Спойлер - она не критичная, просто чуть больше мне самому головной боли.&lt;/p&gt;

</content></entry><entry><id>cryptodev:js-new-operators</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/js-new-operators?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>??= ||= &amp;&amp;= WTF?</title><published>2024-10-29T10:16:27.461Z</published><updated>2024-10-29T10:16:27.461Z</updated><category term="type-script" label="TypeScript"></category><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/2c/ba/2cbae8fb-1aba-4a60-9fab-b4666c4c1f79.png&quot;&gt;Ок, с ходу - три новых оператора в JS, которые проскочили мимо большинства. С виду просто «какая-то ерунда», но не тут-то было! Давайте разберёмся.</summary><content type="html">
  &lt;p id=&quot;7rr6&quot;&gt;Ок, с ходу - три новых оператора в JS, которые проскочили мимо большинства. С виду просто «какая-то ерунда», но не тут-то было! Давайте разберёмся.&lt;/p&gt;
  &lt;h2 id=&quot;AbO6&quot;&gt;&lt;strong&gt;&lt;code&gt;??=&lt;/code&gt;&lt;/strong&gt; — Нулевое присваивание&lt;/h2&gt;
  &lt;p id=&quot;iHqe&quot;&gt;Начнём с того, что &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment&quot; target=&quot;_blank&quot;&gt;этот оператор&lt;/a&gt; работает (если честно так и подмывало тут и закончить предложение) только, если переменная &lt;code&gt;null&lt;/code&gt; или &lt;code&gt;undefined&lt;/code&gt;. Это как сказать: «Если ничего не задано, тогда бери вот это». Пример:&lt;/p&gt;
  &lt;pre id=&quot;5taR&quot;&gt;let foo;
foo ??= 42;
console.log(foo); // 42 — подхватил значение, потому что foo был undefined

foo ??= 100;
console.log(foo); // 42 — остался, ведь foo уже не null и не undefined&lt;/pre&gt;
  &lt;p id=&quot;3nH9&quot;&gt;В ECMAScript под капотом называет это &lt;code&gt;Nullish Coalescing Assignment&lt;/code&gt; — логика такая: проверяет, пусто ли (null/undefined), если да, — присваивает, если нет, — пропускает. Эдакий мини-страж от ошибок, когда значение по умолчанию нужно только при «пустоте».&lt;/p&gt;
  &lt;h2 id=&quot;oZbh&quot;&gt;&lt;code&gt;||=&lt;/code&gt; Логическое присваивание через OR&lt;/h2&gt;
  &lt;p id=&quot;ytHC&quot;&gt;А вот этот &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment&quot; target=&quot;_blank&quot;&gt;оператор&lt;/a&gt; нужен, когда надо присвоить значение, если переменная — &lt;code&gt;falsy&lt;/code&gt; (то есть &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;&amp;#x27;&amp;#x27;&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt; или &lt;code&gt;NaN&lt;/code&gt;). Представь: проверка на пустые строки или ноль и дефолтное значение при этом. Кайф:&lt;/p&gt;
  &lt;pre id=&quot;7yEu&quot;&gt;let y = &amp;#x27;&amp;#x27;;
y ||= &amp;#x27;Привет&amp;#x27;;
console.log(y); // &amp;quot;Привет&amp;quot;, потому что y был пустой строкой

let z = &amp;#x27;Я тут&amp;#x27;;
z ||= &amp;#x27;Пока&amp;#x27;;
console.log(z); // &amp;quot;Я тут&amp;quot;, потому что z уже правдиво&lt;/pre&gt;
  &lt;p id=&quot;LGY6&quot;&gt;Это называется Logical OR Assignment. Логика простая — если переменная «ложна», то она обновляется. Если правдива — то остаётся как есть. Меньше &amp;#x60;if&amp;#x60;, меньше кода, больше читаемости.&lt;/p&gt;
  &lt;h2 id=&quot;GtBi&quot;&gt;&lt;code&gt;&amp;amp;&amp;amp;=&lt;/code&gt; — Логическое присваивание через AND&lt;/h2&gt;
  &lt;p id=&quot;3Wpe&quot;&gt;А вот тут всё &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND_assignment&quot; target=&quot;_blank&quot;&gt;наоборот&lt;/a&gt;: присваивание происходит только если переменная &lt;code&gt;truthy&lt;/code&gt;. Представь: «если правда, то продолжай». Чёткий инструмент для ситуации, когда обновляем только работающие переменные.&lt;/p&gt;
  &lt;pre id=&quot;ITmn&quot;&gt;let alpha = 5;
alpha &amp;amp;&amp;amp;= 10;
console.log(alpha); // 10 — обновил, потому что alpha был 5

let beta = 0;
beta &amp;amp;&amp;amp;= 10;
console.log(beta); // 0 — остался как был, ведь beta был falsy&lt;/pre&gt;
  &lt;p id=&quot;MPQ7&quot;&gt;По спецификации это &lt;code&gt;Logical AND Assignment&lt;/code&gt;. Если переменная правдива, оператор присвоит новое значение, иначе — нет.&lt;br /&gt;&lt;br /&gt;На самом деле тут применений я вижу меньше, чем у первых 2х, их то я точно буду использовать, а этот.. хзхз!&lt;/p&gt;
  &lt;p id=&quot;MHBT&quot;&gt;Итого, у нас есть три инструмента, которые решают частую головную боль по работе с дефолтами и проверками. Да, не без подводных камней, но эти операторы чистят код, делая его понятнее.&lt;br /&gt;&lt;br /&gt;PS: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators&quot; target=&quot;_blank&quot;&gt;там есть всякое&lt;/a&gt;, типа &lt;code&gt;&amp;gt;&amp;gt;=&lt;/code&gt; и &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;=&lt;/code&gt; но камон, когда ты последний раз использовал сдвиг влево? Ай не п**ди, не использовал ты его. &lt;/p&gt;
  &lt;p id=&quot;f88Q&quot;&gt;PPS: Если твой лид говорит тебе, что такое нельзя использовать в проде потому &amp;quot;ну там же эдж, там же сафары&amp;quot; - тыкни носом его в зеленые циферки &lt;/p&gt;
  &lt;figure id=&quot;zbbl&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/80/02/800222a6-e710-45bf-bb76-96ae7001e65f.png&quot; width=&quot;1622&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;CIu2&quot;&gt;Ну и еще, вот ссылка на &lt;a href=&quot;https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators&quot; target=&quot;_blank&quot;&gt;tc39&lt;/a&gt; - ты знаешь что делать (забить болт).&lt;/p&gt;

</content></entry><entry><id>cryptodev:depin-overview</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/depin-overview?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>Обзор dePIN (Decentralized Physical Infrastructure Networks) в контексте интеграции блокчейна и реального мира</title><published>2024-09-23T13:55:51.628Z</published><updated>2024-09-23T14:08:50.123Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img2.teletype.in/files/51/85/518525e7-73c3-4761-881f-77bc158dda40.png"></media:thumbnail><category term="crypto-development" label="Crypto Development"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d9/cb/d9cbaad9-a035-470b-860b-8a8053cda403.jpeg&quot;&gt;What is dePIN</summary><content type="html">
  &lt;p id=&quot;6umV&quot;&gt;&lt;strong&gt;Введение&lt;/strong&gt;&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;TJGK&quot;&gt;Индустрия блокчейна начала свой путь с децентрализованных цифровых решений: криптовалюты, децентрализованное финансирование (DeFi) и смарт-контракты изменили привычные нам подходы к финансовым и цифровым сервисам. Однако мир не ограничен только виртуальной реальностью. В последние годы концепция &lt;strong&gt;dePIN&lt;/strong&gt; (Decentralized Physical Infrastructure Networks) стремительно набирает популярность, обещая расширить влияние блокчейн-технологий на физический мир.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;1LNA&quot;&gt;Что такое dePIN?&lt;/h3&gt;
  &lt;p id=&quot;6GAP&quot;&gt;&lt;strong&gt;dePIN&lt;/strong&gt; — это новый класс децентрализованных сетей, которые выходят за рамки традиционных блокчейн-решений, фокусируясь на физической инфраструктуре и реальных сервисах. Это включает в себя телекоммуникационные сети, логистику, энергетику, транспорт и другие секторы, зависящие от физической инфраструктуры.&lt;/p&gt;
  &lt;p id=&quot;lFUd&quot;&gt;Ключевая идея dePIN заключается в децентрализации не только цифровых активов и данных, но и физической инфраструктуры, предоставляя пользователям возможность напрямую участвовать в построении и управлении этими системами через токены и смарт-контракты.&lt;/p&gt;
  &lt;h3 id=&quot;XkFU&quot;&gt;Как работает dePIN?&lt;/h3&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;AsrB&quot;&gt;В основе dePIN лежат те же принципы децентрализации и консенсуса, что и в традиционных блокчейн-сетях, но с одной важной особенностью: взаимодействие с реальным миром. Примеры реализации dePIN могут включать децентрализованные сети телекоммуникаций, где пользователи предоставляют оборудование (например, узлы сети или базовые станции) и получают вознаграждение в токенах за участие.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;efcG&quot;&gt;Ключевые элементы:&lt;/p&gt;
  &lt;ol id=&quot;Anre&quot;&gt;
    &lt;li id=&quot;O1lX&quot;&gt;&lt;strong&gt;Инфраструктурные провайдеры&lt;/strong&gt;: Физические лица или компании, которые предоставляют реальную инфраструктуру для сети (оборудование, пропускная способность, энергия и т.д.).&lt;/li&gt;
    &lt;li id=&quot;7jhM&quot;&gt;&lt;strong&gt;Токенизация&lt;/strong&gt;: Участники сети вознаграждаются токенами за поддержку сети (например, предоставление серверных мощностей, данных или доступа к сети).&lt;/li&gt;
    &lt;li id=&quot;fpMV&quot;&gt;&lt;strong&gt;Смарт-контракты&lt;/strong&gt;: Они обеспечивают автоматическое выполнение соглашений между участниками сети, что минимизирует необходимость в посредниках и гарантирует прозрачность транзакций.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;JLOe&quot;&gt;Примеры dePIN-проектов&lt;/h3&gt;
  &lt;ol id=&quot;cLuL&quot;&gt;
    &lt;li id=&quot;lIgg&quot;&gt;&lt;strong&gt;Helium&lt;/strong&gt; — децентрализованная сеть, предоставляющая доступ к беспроводной связи через физические узлы. Пользователи устанавливают &amp;quot;хоты&amp;quot; — физические устройства, которые обеспечивают локальное покрытие и зарабатывают токены за работу.&lt;br /&gt;&lt;a href=&quot;https://depinhub.io/projects/helium&quot; target=&quot;_blank&quot;&gt;https://depinhub.io/projects/helium&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.helium.com/&quot; target=&quot;_blank&quot;&gt;https://docs.helium.com/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;AOlL&quot;&gt;&lt;strong&gt;HiveMapper&lt;/strong&gt; — краудсорсинговая платформа для создания децентрализованных карт. Водители устанавливают специальные камеры в свои машины и зарабатывают токены, предоставляя обновленные географические данные.&lt;br /&gt;&lt;a href=&quot;https://depinhub.io/projects/hivemapper&quot; target=&quot;_blank&quot;&gt;https://depinhub.io/projects/hivemapper&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.hivemapper.com/&quot; target=&quot;_blank&quot;&gt;https://docs.hivemapper.com/&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;WkJ4&quot;&gt;&lt;strong&gt;Filecoin&lt;/strong&gt; — платформа для децентрализованного хранения данных, где пользователи предоставляют свободное место на своих серверах и получают вознаграждение за это.&lt;br /&gt;&lt;a href=&quot;https://depinhub.io/projects/filecoin&quot; target=&quot;_blank&quot;&gt;https://depinhub.io/projects/filecoin&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://filecoin.io/&quot; target=&quot;_blank&quot;&gt;https://filecoin.io/&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;W6Hz&quot;&gt;Преимущества и вызовы dePIN&lt;/h3&gt;
  &lt;p id=&quot;XVXx&quot;&gt;&lt;strong&gt;Преимущества&lt;/strong&gt;:&lt;/p&gt;
  &lt;ul id=&quot;wOOp&quot;&gt;
    &lt;li id=&quot;XUtG&quot;&gt;&lt;strong&gt;Прозрачность&lt;/strong&gt;: Благодаря блокчейн-технологиям, все транзакции в сети остаются прозрачными, что позволяет избегать манипуляций и повысить доверие между участниками.&lt;/li&gt;
    &lt;li id=&quot;9XCY&quot;&gt;&lt;strong&gt;Стимулы для участников&lt;/strong&gt;: Токенизация позволяет участникам напрямую получать вознаграждение за участие в физической инфраструктуре, что открывает новые модели бизнеса.&lt;/li&gt;
    &lt;li id=&quot;FMs1&quot;&gt;&lt;strong&gt;Снижение централизации&lt;/strong&gt;: dePIN снижает зависимость от централизованных поставщиков услуг и инфраструктуры, делая экосистему более устойчивой и независимой от корпоративных интересов.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;goak&quot;&gt;&lt;strong&gt;Вызовы&lt;/strong&gt;:&lt;/p&gt;
  &lt;ul id=&quot;rIx0&quot;&gt;
    &lt;li id=&quot;2gqS&quot;&gt;&lt;strong&gt;Юридические и регуляторные барьеры&lt;/strong&gt;: Взаимодействие с физическими активами в разных юрисдикциях вызывает сложности с регулированием, лицензированием и налогообложением.&lt;/li&gt;
    &lt;li id=&quot;ta9X&quot;&gt;&lt;strong&gt;Надежность инфраструктуры&lt;/strong&gt;: Поддержание высокой надежности физической инфраструктуры требует значительных ресурсов, что может усложнить масштабирование таких проектов.&lt;/li&gt;
    &lt;li id=&quot;rF0O&quot;&gt;&lt;strong&gt;Мотивированность участников&lt;/strong&gt;: Экономическая модель вознаграждений должна быть тщательно продумана, чтобы долгосрочно стимулировать участников к поддержке сети.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;O88Y&quot;&gt;Влияние на реальный мир&lt;/h3&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;EU5Z&quot;&gt;dePIN открывает возможности для более эффективного использования ресурсов и демократизации доступа к инфраструктуре. Это особенно важно в таких отраслях, как энергетика и связь, где традиционно преобладает небольшое число крупных игроков. Например, децентрализованные сети зарядных станций для электромобилей могут сделать этот рынок более доступным для независимых участников, что ускорит его развитие.&lt;/p&gt;
  &lt;/section&gt;
  &lt;h3 id=&quot;GPvg&quot;&gt;Заключение&lt;/h3&gt;
  &lt;p id=&quot;mmZL&quot;&gt;dePIN представляет собой шаг в сторону реальной интеграции блокчейна с физической инфраструктурой. Это не только расширяет возможности использования технологии, но и открывает путь к построению более децентрализованных, устойчивых и эффективных систем в реальном мире. Однако перед проектами dePIN стоят серьёзные вызовы, которые необходимо решать на пути к массовому внедрению.&lt;/p&gt;

</content></entry><entry><id>cryptodev:Solidity-Memory-Arrays</id><link rel="alternate" type="text/html" href="https://teletype.in/@cryptodev/Solidity-Memory-Arrays?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=cryptodev"></link><title>Неочевидные Solidity Memory Arrays</title><published>2022-12-19T14:39:18.341Z</published><updated>2022-12-19T14:51:58.292Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/aa/26/aa265725-187c-416a-88be-74164773928d.png"></media:thumbnail><category term="crypto-development" label="Crypto Development"></category><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/10/4b/104b1cc1-920b-46ba-b248-ac4ab05ac5d2.png&quot;&gt;Всем привет, я Дима из Sumsub. Год назад один очень хороший человек посоветовал мне начать вести блог, писать о том о сем... В основном, для того, чтобы не чувствовать, что &quot;дни проходят один за одним, и ничего не происходит&quot;, ведь происходит много всего, решается много задач, находится много решений, порой неочевидных.

Ведь действительно, порой нам приходится находить совсем неочевидное решение какой-либо проблемы, на это уходят силы, на это уходит время, а решение просто &quot;остается в памяти&quot;. А вдруг, кто-то столкнулся с подобной задачей, а вдруг, кто-то нашел более изящное решение...

И вот, наконец то я созрел. Была проблема, с которой я столкнулся, которую я решил (не факт, что изящно), и ей я хочу поделиться с вами!</summary><content type="html">
  &lt;blockquote id=&quot;C0b2&quot;&gt;Всем привет, я Дима из Sumsub. Год назад один очень хороший человек посоветовал мне начать вести блог, писать о том о сем... В основном, для того, чтобы не чувствовать, что &amp;quot;дни проходят один за одним, и ничего не происходит&amp;quot;, ведь происходит много всего, решается много задач, находится много решений, порой неочевидных.&lt;br /&gt;&lt;br /&gt;Ведь действительно, порой нам приходится находить совсем неочевидное решение какой-либо проблемы, на это уходят силы, на это уходит время, а решение просто &amp;quot;остается в памяти&amp;quot;. А вдруг, кто-то столкнулся с подобной задачей, а вдруг, кто-то нашел более изящное решение...&lt;br /&gt;&lt;br /&gt;И вот, наконец то я созрел. Была проблема, с которой я столкнулся, которую я решил (не факт, что изящно), и ей я хочу поделиться с вами!&lt;/blockquote&gt;
  &lt;h2 id=&quot;nKfI&quot;&gt;Контекст&lt;/h2&gt;
  &lt;p id=&quot;nBak&quot;&gt;Я писал код контракта, который отвечает за уровни доступа программ к данным пользователей. Контракт хранил список программы, в виде адресов, а так же уровни доступа и запросы на их получение в виде маппингов.&lt;/p&gt;
  &lt;pre id=&quot;N3qM&quot;&gt;mapping(uint256 =&amp;gt; mapping(address =&amp;gt; uint256)) private _accesses;&lt;/pre&gt;
  &lt;p id=&quot;4H8R&quot;&gt;В контракте был метод для получения списка программ, которые запросили доступ к данным пользователя, который возвращал массив структур с указанием адреса клиента и уровнем.&lt;/p&gt;
  &lt;pre id=&quot;ROAT&quot;&gt;struct AccessType {
  address client;
  uint256 level;
}

function getRequests(address account)
external view returns(AccessType[] memory) {
// code
}&lt;/pre&gt;
  &lt;p id=&quot;nnTb&quot;&gt;И вот тут-то я и столкнулся с проблемой.&lt;/p&gt;
  &lt;figure id=&quot;QyBA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/10/4b/104b1cc1-920b-46ba-b248-ac4ab05ac5d2.png&quot; width=&quot;1408&quot; /&gt;
    &lt;figcaption&gt;Remix IDE test&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;gNBS&quot;&gt;Проблема&lt;/h2&gt;
  &lt;p id=&quot;YkNX&quot;&gt;Поскольку мы не можем возвращать из метода в Solidity маппинги, мне нужно пройтись по массиву программ и проверить уровень доступа для каждой из них для указанного пользователя. Звучит крайне просто и логично.&lt;/p&gt;
  &lt;pre id=&quot;8fc5&quot;&gt;function getAccessLevel_WRONG(address user)
public view returns(AccessLevel[] memory) {
  AccessLevel[] memory result;
  uint256 j = 0;
  
  for(uint256 i = 0; i &amp;lt; programs.length; i++) {
    address program = programs[i];
    uint8 accessLevel = accessLevels[user][program];
    
    if (accessLevel &amp;gt; 0) {
      result[j] = AccessLevel(
        program,
        accessLevel
      );
      j++;
    }
  }
  
  return result;
}&lt;/pre&gt;
  &lt;p id=&quot;oE1Y&quot;&gt;И ведь самое интересное, что код компилируется, линтер не выдает ошибку! Вроде все чисто!&lt;/p&gt;
  &lt;figure id=&quot;u01u&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d2/0e/d20e8764-db43-4653-9238-fea96dcd763b.png&quot; width=&quot;738&quot; /&gt;
    &lt;figcaption&gt;Remix IDE - no linter errors&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;8Dpp&quot;&gt;А вот при исполнении вылетает Panic Exception!&lt;/p&gt;
  &lt;figure id=&quot;hyWA&quot; class=&quot;m_column&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/a2/24/a2243385-e7a6-42ae-bdd1-927cf6f6c91a.png&quot; width=&quot;772&quot; /&gt;
    &lt;figcaption&gt;Remix IDE debugger&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;UXUJ&quot;&gt;Решение&lt;/h2&gt;
  &lt;p id=&quot;kLdH&quot;&gt;Несколько раз проверив весь код, а так же логом пройдясь почти по каждой строчке, я смирился с тем, что мое первое подозрение верно - что-то не то с массивом. Если бы у меня был простой, не &lt;strong&gt;memory&lt;/strong&gt;, массив, я бы просто добавлял в него элементы через &lt;strong&gt;.push&lt;/strong&gt;, но тут так не прокатит. Так же я мог бы из функции вернуть 2 значения, в первом указывать адреса, а во втором - уровни, но это тоже как-то не тру. &lt;/p&gt;
  &lt;p id=&quot;E1bb&quot;&gt;Минимальные знания низкоуровневых языков говорят мне, что массивы без указанной длинны - не бро, а с указанной - бро. Ок, давайте проверим. Предположим, что у нас добавлена только одна программа и мы заменим:&lt;/p&gt;
  &lt;pre id=&quot;UKor&quot;&gt;-- 
function getAccessLevel_WRONG(address user)
public view returns(AccessLevel[] memory) {
  AccessLevel[] memory result;

++
function getAccessLevel_WRONG(address user)
public view returns(AccessLevel[1] memory) {
  AccessLevel[] memory result = new AccessLevel[](1);&lt;/pre&gt;
  &lt;p id=&quot;MPKw&quot;&gt;Вуаля! Все заработало... Штош, похоже, мне таки придется посчитать сначала длину всего массива. Meh... Мне кажется, что я пишу дополнительный код, который будет тратить процессорное время выполнения view функции, но на самом деле это просто необходимость, с которой мне надо смириться.&lt;/p&gt;
  &lt;pre id=&quot;HSsg&quot;&gt;function getAccessLevel_CORRECT(address user)
public view returns(AccessLevel[] memory) {
    uint256 length = 0;

    for(uint256 i = 0; i &amp;lt; programs.length; i++) {
        address program = programs[i];
        uint8 accessLevel = accessLevels[user][program];

        if (accessLevel &amp;gt; 0) {
            length++;
        }
    }

    AccessLevel[] memory result = new AccessLevel[](length);

    uint256 j = 0;

    for(uint256 i = 0; i &amp;lt; programs.length; i++) {
        address program = programs[i];
        uint8 accessLevel = accessLevels[user][program];

        if (accessLevel &amp;gt; 0) {
            result[j] = AccessLevel(
                program,
                accessLevel
            );
            j++;
        }
    }

    return result;
}&lt;/pre&gt;
  &lt;p id=&quot;JBI7&quot;&gt;Мне еще предстоит разобраться, с глубинным поведением этого кода, но смело можно сказать три вещи:&lt;/p&gt;
  &lt;ul id=&quot;snqY&quot;&gt;
    &lt;li id=&quot;yoPH&quot;&gt;Solidity плохо умеет в динамические массивы, с этим надо смириться, но это не проблема;&lt;/li&gt;
    &lt;li id=&quot;0AqR&quot;&gt;Линтер не отработал, а компилятор схавал - это уже плохо. Я привык доверять Remix IDE и её(его? IDE - это он или она?) инструментам. Вот по компилятору - да, тут еще есть вопросы, думаю что Remix IDE сможет мне помочь в этом своим дебаггером. Ну или придется погонять код в Ganache, посмотрим.&lt;/li&gt;
    &lt;li id=&quot;7Cfb&quot;&gt;TDD рулит. Ясен красен тесты я пошел писать не сразу! Да, сначала этот код оказался в сети, а уже потом я увидел ошибку, и если честно - пару недель игнорил ее, потому что у меня были еще и другие методы для получения чуть более конкретных данных. Но вот именно написание теста, параллельно с переписыванием кода помогло мне разобраться, что именно идет не так.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;ze6q&quot;&gt;Код с контрактом и тестами я залил на github, так что велкам &lt;a href=&quot;https://github.com/dmitrytarassov/Solidity-Memory-Arrays&quot; target=&quot;_blank&quot;&gt;посмотреть&lt;/a&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;8Eb5&quot;&gt;Послесловие&lt;/h3&gt;
  &lt;p id=&quot;Jn0M&quot;&gt;Мне еще предстоит разобраться, что именно идет не так в этом коде, вероятно кто-то из вас, читателей (ну я надеюсь кто-то это читает), знает в чем причина! дайте мне знать, я с радостью приму ваше решение. Не скажу, что потратил на решение проблемы много времени, наверное, потому что изначально предполагал проблему с длинной массива. Но вот сейчас я дописываю этот текст, и уже чувствую, что время не потрачено впустую, а значит все не напрасно. &lt;/p&gt;
  &lt;h3 id=&quot;pbHP&quot;&gt;После-послесловие&lt;/h3&gt;
  &lt;p id=&quot;gGii&quot;&gt;Есть идея: создать сообщество блокчейн разработчиков, где каждый сможет делиться своим опытом, в виде статей или в виде диалога. Чтобы каждый участник сообщества был значим, чтобы были общие проекты, чтобы совместно поднимать ноды. А поверх всего этого DAO, ну и шаттл на луну. Ну это все идеи, которые на данном этапе находятся на стадии зарождения. Нас пока 4 человека, и если честно - последнее сообщение в чате было неделю назад. Посмотрим, что из этого выйдет, но если я случайно заинтересовал тебя мой читатель - напиши &lt;a href=&quot;https://t.me/tarasov_d_a&quot; target=&quot;_blank&quot;&gt;мне&lt;/a&gt;!&lt;/p&gt;

</content></entry></feed>