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

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

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

contract Ownable {
    address public owner;

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

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

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

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

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

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

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

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

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

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

        // Загружаем и развертываем контракт airdrop
        EigenAirdrop0y = await ethers.getContractFactory(&quot;EigenAirdrop0y&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(&quot;1.0&quot;));
    });

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

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

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

    it(&quot;Should distribute tokens correctly&quot;, async function () {
        await airdrop.addToAirdropList(addr1.address, ethers.utils.parseEther(&quot;0.1&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(&quot;0.1&quot;));
    });

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

import &quot;./EigenAirdrop0y.sol&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;
            // Повторная попытка вызова &#x60;airdrop&#x60; в ходе первой попытки
            airdrop.airdrop();
        }
    }
}</pre>
  <p id="Pw9m">И сам тест:</p>
  <pre id="Z2Wk">const { expect } = require(&quot;chai&quot;);

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

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

        // Загружаем и развертываем контракт airdrop
        EigenAirdrop0y = await ethers.getContractFactory(&quot;EigenAirdrop0y&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(&quot;1.0&quot;));

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

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

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

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

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

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

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

  const stakers: Map&lt;string, OperatorReStakerAction[]&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) =&gt; {
    const inc = increasingActions.get(staker) || [];
    const dec = decreasingActions.get(staker) || [];

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

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

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

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

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

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

  return async (block) =&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());</pre>
  <p id="7A2t">Далее идем по массиву действий и проводим вычисления: определяем дату, определяем дату следующего события, потому что считать нам надо только в рамках одного события, ведь дальше стейк изменится. Если нет следующей даты - берем дату окончания кампании (мы зафинализировали на 20871841 блоке). Изи катка.</p>
  <pre id="vZFZ">_accountStakes.forEach((stake, index) =&gt; {
      if (!strategies.includes(stake.strategy.toLowerCase())) {
        return;
      }

      // https://etherscan.io/block/20871841
      const finalBlockTimestamp = 1727800151000;
      const nextDate = _accountStakes[index + 1] ? +&#x60;${_accountStakes[index + 1].timestamp}000&#x60; : finalBlockTimestamp;
      const distance = nextDate - +&#x60;${stake.timestamp}000&#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) =&gt; acc += value, BigInt(0)))</pre>
  <p id="MTLs">А, ну да, так как мы работаем с эфиром, то у нас тут числа без плавающей точки, но с 18 нулями, а мне бы в простом виде, так что дергаем <code>ethers.utils.formatEther</code>. И еще важный момент: формат таймстемпа, чтоб в js работать с ним - добавляем 3 нуля в конец.</p>
  <h3 id="dayr">Что-то пошло не так</h3>
  <p id="UXHI">Помнишь, я писал про то что <a href="#DJ4R">выцепляю</a> какую-то &quot;стратегию&quot; из лога. Дак вот, когда EigenLayer выдали первый дроп, его можно (и нужно) было запихнуть в стейкинг, чтоб еще немного подзаработать. Блок был примерно на 3 месяца, я запихнул свои 100 EIGEN, получил примерно 4.5. APR 18% - найс. Дак вот, стратегия - это как раз то, какой рестейкнутый токен ты запихиваешь в оператора: stETH, wBETH, ankrETH... или Eigen. И вот тут то и случилась жопа, потому что под каждый токен деплоился отдельный управляющий контракт, и их пришлось глазами выискивать.  Заняло час рутинной работы, но все получилось.</p>
  <pre id="Qrdv">const __data = &#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&#x60;;

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

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptodev/js-new-operators</guid><link>https://teletype.in/@cryptodev/js-new-operators?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev</link><comments>https://teletype.in/@cryptodev/js-new-operators?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev#comments</comments><dc:creator>cryptodev</dc:creator><title>??= ||= &amp;&amp;= WTF?</title><pubDate>Tue, 29 Oct 2024 10:16:27 GMT</pubDate><category>TypeScript</category><description><![CDATA[<img src="https://img3.teletype.in/files/2c/ba/2cbae8fb-1aba-4a60-9fab-b4666c4c1f79.png"></img>Ок, с ходу - три новых оператора в JS, которые проскочили мимо большинства. С виду просто «какая-то ерунда», но не тут-то было! Давайте разберёмся.]]></description><content:encoded><![CDATA[
  <p id="7rr6">Ок, с ходу - три новых оператора в JS, которые проскочили мимо большинства. С виду просто «какая-то ерунда», но не тут-то было! Давайте разберёмся.</p>
  <h2 id="AbO6"><strong><code>??=</code></strong> — Нулевое присваивание</h2>
  <p id="iHqe">Начнём с того, что <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment" target="_blank">этот оператор</a> работает (если честно так и подмывало тут и закончить предложение) только, если переменная <code>null</code> или <code>undefined</code>. Это как сказать: «Если ничего не задано, тогда бери вот это». Пример:</p>
  <pre id="5taR">let foo;
foo ??= 42;
console.log(foo); // 42 — подхватил значение, потому что foo был undefined

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

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

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

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptodev/depin-overview</guid><link>https://teletype.in/@cryptodev/depin-overview?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev</link><comments>https://teletype.in/@cryptodev/depin-overview?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev#comments</comments><dc:creator>cryptodev</dc:creator><title>Обзор dePIN (Decentralized Physical Infrastructure Networks) в контексте интеграции блокчейна и реального мира</title><pubDate>Mon, 23 Sep 2024 13:55:51 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/51/85/518525e7-73c3-4761-881f-77bc158dda40.png"></media:content><category>Crypto Development</category><description><![CDATA[<img src="https://img2.teletype.in/files/d9/cb/d9cbaad9-a035-470b-860b-8a8053cda403.jpeg"></img>What is dePIN]]></description><content:encoded><![CDATA[
  <p id="6umV"><strong>Введение</strong></p>
  <section style="background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="TJGK">Индустрия блокчейна начала свой путь с децентрализованных цифровых решений: криптовалюты, децентрализованное финансирование (DeFi) и смарт-контракты изменили привычные нам подходы к финансовым и цифровым сервисам. Однако мир не ограничен только виртуальной реальностью. В последние годы концепция <strong>dePIN</strong> (Decentralized Physical Infrastructure Networks) стремительно набирает популярность, обещая расширить влияние блокчейн-технологий на физический мир.</p>
  </section>
  <h3 id="1LNA">Что такое dePIN?</h3>
  <p id="6GAP"><strong>dePIN</strong> — это новый класс децентрализованных сетей, которые выходят за рамки традиционных блокчейн-решений, фокусируясь на физической инфраструктуре и реальных сервисах. Это включает в себя телекоммуникационные сети, логистику, энергетику, транспорт и другие секторы, зависящие от физической инфраструктуры.</p>
  <p id="lFUd">Ключевая идея dePIN заключается в децентрализации не только цифровых активов и данных, но и физической инфраструктуры, предоставляя пользователям возможность напрямую участвовать в построении и управлении этими системами через токены и смарт-контракты.</p>
  <h3 id="XkFU">Как работает dePIN?</h3>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="AsrB">В основе dePIN лежат те же принципы децентрализации и консенсуса, что и в традиционных блокчейн-сетях, но с одной важной особенностью: взаимодействие с реальным миром. Примеры реализации dePIN могут включать децентрализованные сети телекоммуникаций, где пользователи предоставляют оборудование (например, узлы сети или базовые станции) и получают вознаграждение в токенах за участие.</p>
  </section>
  <p id="efcG">Ключевые элементы:</p>
  <ol id="Anre">
    <li id="O1lX"><strong>Инфраструктурные провайдеры</strong>: Физические лица или компании, которые предоставляют реальную инфраструктуру для сети (оборудование, пропускная способность, энергия и т.д.).</li>
    <li id="7jhM"><strong>Токенизация</strong>: Участники сети вознаграждаются токенами за поддержку сети (например, предоставление серверных мощностей, данных или доступа к сети).</li>
    <li id="fpMV"><strong>Смарт-контракты</strong>: Они обеспечивают автоматическое выполнение соглашений между участниками сети, что минимизирует необходимость в посредниках и гарантирует прозрачность транзакций.</li>
  </ol>
  <h3 id="JLOe">Примеры dePIN-проектов</h3>
  <ol id="cLuL">
    <li id="lIgg"><strong>Helium</strong> — децентрализованная сеть, предоставляющая доступ к беспроводной связи через физические узлы. Пользователи устанавливают &quot;хоты&quot; — физические устройства, которые обеспечивают локальное покрытие и зарабатывают токены за работу.<br /><a href="https://depinhub.io/projects/helium" target="_blank">https://depinhub.io/projects/helium</a><br /><a href="https://docs.helium.com/" target="_blank">https://docs.helium.com/</a></li>
    <li id="AOlL"><strong>HiveMapper</strong> — краудсорсинговая платформа для создания децентрализованных карт. Водители устанавливают специальные камеры в свои машины и зарабатывают токены, предоставляя обновленные географические данные.<br /><a href="https://depinhub.io/projects/hivemapper" target="_blank">https://depinhub.io/projects/hivemapper</a><br /><a href="https://docs.hivemapper.com/" target="_blank">https://docs.hivemapper.com/</a></li>
    <li id="WkJ4"><strong>Filecoin</strong> — платформа для децентрализованного хранения данных, где пользователи предоставляют свободное место на своих серверах и получают вознаграждение за это.<br /><a href="https://depinhub.io/projects/filecoin" target="_blank">https://depinhub.io/projects/filecoin</a><br /><a href="https://filecoin.io/" target="_blank">https://filecoin.io/</a></li>
  </ol>
  <h3 id="W6Hz">Преимущества и вызовы dePIN</h3>
  <p id="XVXx"><strong>Преимущества</strong>:</p>
  <ul id="wOOp">
    <li id="XUtG"><strong>Прозрачность</strong>: Благодаря блокчейн-технологиям, все транзакции в сети остаются прозрачными, что позволяет избегать манипуляций и повысить доверие между участниками.</li>
    <li id="9XCY"><strong>Стимулы для участников</strong>: Токенизация позволяет участникам напрямую получать вознаграждение за участие в физической инфраструктуре, что открывает новые модели бизнеса.</li>
    <li id="FMs1"><strong>Снижение централизации</strong>: dePIN снижает зависимость от централизованных поставщиков услуг и инфраструктуры, делая экосистему более устойчивой и независимой от корпоративных интересов.</li>
  </ul>
  <p id="goak"><strong>Вызовы</strong>:</p>
  <ul id="rIx0">
    <li id="2gqS"><strong>Юридические и регуляторные барьеры</strong>: Взаимодействие с физическими активами в разных юрисдикциях вызывает сложности с регулированием, лицензированием и налогообложением.</li>
    <li id="ta9X"><strong>Надежность инфраструктуры</strong>: Поддержание высокой надежности физической инфраструктуры требует значительных ресурсов, что может усложнить масштабирование таких проектов.</li>
    <li id="rF0O"><strong>Мотивированность участников</strong>: Экономическая модель вознаграждений должна быть тщательно продумана, чтобы долгосрочно стимулировать участников к поддержке сети.</li>
  </ul>
  <h3 id="O88Y">Влияние на реальный мир</h3>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="EU5Z">dePIN открывает возможности для более эффективного использования ресурсов и демократизации доступа к инфраструктуре. Это особенно важно в таких отраслях, как энергетика и связь, где традиционно преобладает небольшое число крупных игроков. Например, децентрализованные сети зарядных станций для электромобилей могут сделать этот рынок более доступным для независимых участников, что ускорит его развитие.</p>
  </section>
  <h3 id="GPvg">Заключение</h3>
  <p id="mmZL">dePIN представляет собой шаг в сторону реальной интеграции блокчейна с физической инфраструктурой. Это не только расширяет возможности использования технологии, но и открывает путь к построению более децентрализованных, устойчивых и эффективных систем в реальном мире. Однако перед проектами dePIN стоят серьёзные вызовы, которые необходимо решать на пути к массовому внедрению.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptodev/Solidity-Memory-Arrays</guid><link>https://teletype.in/@cryptodev/Solidity-Memory-Arrays?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev</link><comments>https://teletype.in/@cryptodev/Solidity-Memory-Arrays?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptodev#comments</comments><dc:creator>cryptodev</dc:creator><title>Неочевидные Solidity Memory Arrays</title><pubDate>Mon, 19 Dec 2022 14:39:18 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/aa/26/aa265725-187c-416a-88be-74164773928d.png"></media:content><category>Crypto Development</category><description><![CDATA[<img src="https://img2.teletype.in/files/10/4b/104b1cc1-920b-46ba-b248-ac4ab05ac5d2.png"></img>Всем привет, я Дима из Sumsub. Год назад один очень хороший человек посоветовал мне начать вести блог, писать о том о сем... В основном, для того, чтобы не чувствовать, что &quot;дни проходят один за одним, и ничего не происходит&quot;, ведь происходит много всего, решается много задач, находится много решений, порой неочевидных.

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

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

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

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

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

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

    uint256 j = 0;

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

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

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

]]></content:encoded></item></channel></rss>