[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-7-gazoeffektivnye-tsikly-usloviya-yul":3},{"article":4,"author":58},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":38,"related_articles":39},"d0000000-0000-0000-0000-000000000207","a0000000-0000-0000-0000-000000000012","Deep EVM #7: Газоэффективные циклы и условия в Yul","deep-evm-7-gazoeffektivnye-tsikly-usloviya-yul","Как писать газоэффективные циклы, условия и алгоритмы обработки данных в Yul. Сравнение Solidity и Yul по газу, оптимизация итераций и реальные паттерны из DeFi-протоколов.","## Почему циклы в Yul эффективнее\n\nКогда Solidity компилирует цикл `for`, он добавляет множество проверок безопасности: верификацию границ массива, проверку переполнения счётчика, безопасный доступ к элементам. Каждая из этих проверок стоит газа. В Yul вы контролируете каждый аспект цикла и можете убрать ненужные проверки, сохраняя только необходимые.\n\n## Анатомия цикла в Yul\n\nЦикл `for` в Yul имеет три блока:\n\n```yul\nassembly {\n    \u002F\u002F for { инициализация } условие { пост-итерация } { тело }\n    for { let i := 0 } lt(i, 10) { i := add(i, 1) } {\n        \u002F\u002F тело цикла\n    }\n}\n```\n\nНа уровне байткода это компилируется в:\n```\n\u002F\u002F Инициализация:\nPUSH1 0      \u002F\u002F i = 0\n\n\u002F\u002F Условие (начало итерации):\nJUMPDEST     \u002F\u002F метка начала цикла\nDUP1         \u002F\u002F копируем i\nPUSH1 10     \u002F\u002F предел\nLT           \u002F\u002F i \u003C 10?\nISZERO       \u002F\u002F инвертируем\nPUSH2 end    \u002F\u002F адрес конца\nJUMPI        \u002F\u002F если i >= 10, выходим\n\n\u002F\u002F Тело цикла:\n...          \u002F\u002F ваш код\n\n\u002F\u002F Пост-итерация:\nPUSH1 1\nADD          \u002F\u002F i = i + 1\nPUSH2 start  \u002F\u002F адрес начала\nJUMP         \u002F\u002F обратно к условию\n\n\u002F\u002F Конец:\nJUMPDEST\nPOP          \u002F\u002F убираем i со стека\n```\n\n### Газ одной итерации\n\nСтоимость «инфраструктуры» одной итерации цикла:\n- JUMPDEST: 1 газ\n- DUP1: 3 газа\n- PUSH: 3 газа\n- LT: 3 газа\n- ISZERO: 3 газа\n- PUSH: 3 газа\n- JUMPI: 10 газа\n- ADD (инкремент): 3 газа\n- PUSH: 3 газа\n- JUMP: 8 газа\n- **Итого: ~40 газа за итерацию** (только инфраструктура, без тела)\n\n## Сравнение: Solidity vs Yul\n\n### Суммирование массива\n\n```solidity\n\u002F\u002F Solidity — ~800 газа за итерацию\nfunction sumSolidity(uint256[] calldata arr) external pure returns (uint256 total) {\n    for (uint256 i = 0; i \u003C arr.length; i++) {\n        total += arr[i];\n    }\n}\n\n\u002F\u002F Yul — ~200 газа за итерацию\nfunction sumYul(uint256[] calldata arr) external pure returns (uint256 total) {\n    assembly {\n        let length := arr.length\n        let offset := arr.offset\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            total := add(total, calldataload(add(offset, mul(i, 32))))\n        }\n    }\n}\n```\n\nРазница объясняется тем, что Solidity добавляет:\n1. Проверку переполнения `i++` (~100 газа)\n2. Проверку границ массива `arr[i]` (~200 газа)\n3. Проверку переполнения `total += arr[i]` (~100 газа)\n4. Безопасный доступ к calldata через ABI-декодирование (~200 газа)\n\n### Оптимизация: декремент вместо инкремента\n\nОбратный цикл (от length к 0) чуть эффективнее, потому что `ISZERO` дешевле `LT`:\n\n```yul\nassembly {\n    let length := arr.length\n    let offset := arr.offset\n\n    \u002F\u002F Обратный цикл\n    for { let i := length } i { i := sub(i, 1) } {\n        let idx := sub(i, 1)\n        total := add(total, calldataload(add(offset, mul(idx, 32))))\n    }\n}\n\u002F\u002F Экономия: ~2 газа за итерацию (ISZERO vs LT)\n```\n\n## Условные конструкции\n\n### if в Yul\n\nYul не имеет `else`. Для условий с двумя ветвями используйте `switch`:\n\n```yul\nassembly {\n    \u002F\u002F if — только одна ветвь\n    if iszero(value) {\n        revert(0, 0)\n    }\n\n    \u002F\u002F switch — несколько ветвей\n    switch selector\n    case 0xa9059cbb { \u002F\u002F transfer\n        \u002F\u002F обработка transfer\n    }\n    case 0x70a08231 { \u002F\u002F balanceOf\n        \u002F\u002F обработка balanceOf\n    }\n    case 0x095ea7b3 { \u002F\u002F approve\n        \u002F\u002F обработка approve\n    }\n    default {\n        revert(0, 0)\n    }\n}\n```\n\n### Тернарный оператор через Yul\n\nYul не имеет тернарного оператора, но его можно эмулировать:\n\n```yul\nassembly {\n    \u002F\u002F result = condition ? valueIfTrue : valueIfFalse\n    \u002F\u002F Способ 1: через mul + sub (branchless)\n    let result := add(mul(condition, valueIfTrue), mul(sub(1, condition), valueIfFalse))\n\n    \u002F\u002F Способ 2: через xor (если значения — 0\u002F1)\n    let result2 := xor(valueIfFalse, mul(xor(valueIfTrue, valueIfFalse), condition))\n}\n```\n\n«Безветвевой» (branchless) код часто эффективнее, потому что избегает JUMPI (10 газа) и связанных с ним опкодов.\n\n## Реальные паттерны из DeFi\n\n### Паттерн 1: Поиск в отсортированном массиве (бинарный поиск)\n\n```yul\nassembly {\n    function binarySearch(arrSlot, length, target) -> index, found {\n        let low := 0\n        let high := length\n\n        for {} lt(low, high) {} {\n            let mid := shr(1, add(low, high)) \u002F\u002F (low + high) \u002F 2\n            let midVal := sload(add(arrSlot, mid))\n\n            switch gt(target, midVal)\n            case 1 {\n                low := add(mid, 1)\n            }\n            default {\n                high := mid\n            }\n        }\n\n        if lt(low, length) {\n            let val := sload(add(arrSlot, low))\n            if eq(val, target) {\n                index := low\n                found := 1\n            }\n        }\n    }\n}\n```\n\nБинарный поиск в хранилище: O(log n) SLOAD вместо O(n). Для массива из 1000 элементов: ~10 SLOAD (21000 газа при холодном доступе) вместо ~500 SLOAD (1 050 000 газа).\n\n### Паттерн 2: Пакетная обработка трансферов\n\n```yul\nassembly {\n    \u002F\u002F Calldata: selector(4) + offset(32) + length(32) + [to(32) + amount(32)] * n\n    function batchTransfer(token) {\n        let length := calldataload(36)\n        let dataStart := 68 \u002F\u002F 4 + 32 + 32\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            let offset := add(dataStart, mul(i, 64))\n            let to := calldataload(offset)\n            let amount := calldataload(add(offset, 32))\n\n            \u002F\u002F Маскируем адрес\n            to := and(to, 0xffffffffffffffffffffffffffffffffffffffff)\n\n            \u002F\u002F Формируем calldata для transfer(address,uint256)\n            mstore(0x00, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n            mstore(0x04, to)\n            mstore(0x24, amount)\n\n            let success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n            if iszero(success) {\n                revert(0, 0)\n            }\n        }\n    }\n}\n```\n\nЭтот паттерн позволяет выполнить N трансферов в одной транзакции, экономя 21000 газа (внутренний газ транзакции) за каждый дополнительный трансфер.\n\n### Паттерн 3: Эффективный маппинг-итератор\n\nМаппинги в Solidity не итерируемы. Но если вы поддерживаете параллельный массив ключей, можно эффективно итерировать:\n\n```yul\nassembly {\n    \u002F\u002F Предполагаем: slot 0 = length, slot keccak256(0) + i = keys[i]\n    \u002F\u002F Маппинг balances: slot 1, balances[key] = keccak256(key . 1)\n\n    function sumAllBalances() -> total {\n        let length := sload(0)\n        let keysBase := keccak256(0x00, 0x20) \u002F\u002F Нужно записать 0 в 0x00 перед keccak\n\n        \u002F\u002F Записываем базовый слот массива ключей\n        mstore(0x00, 0)\n        let keysSlot := keccak256(0x00, 0x20)\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            \u002F\u002F Загружаем ключ\n            let key := sload(add(keysSlot, i))\n\n            \u002F\u002F Вычисляем слот баланса\n            mstore(0x00, key)\n            mstore(0x20, 1) \u002F\u002F слот маппинга\n            let balanceSlot := keccak256(0x00, 0x40)\n\n            \u002F\u002F Суммируем\n            total := add(total, sload(balanceSlot))\n        }\n    }\n}\n```\n\n### Паттерн 4: Развёрнутый цикл (Loop Unrolling)\n\nДля фиксированного числа итераций развёрнутый цикл убирает накладные расходы JUMP\u002FJUMPI:\n\n```yul\nassembly {\n    \u002F\u002F Вместо цикла на 4 итерации (~160 газа инфраструктуры)\n    \u002F\u002F Развёрнутый код (0 газа инфраструктуры)\n    let a := calldataload(0x04)\n    let b := calldataload(0x24)\n    let c := calldataload(0x44)\n    let d := calldataload(0x64)\n    let total := add(add(a, b), add(c, d))\n}\n```\n\nЭто экономит ~40 газа за «убранную» итерацию. Для 4 итераций: 160 газа экономии. Для MEV-ботов, обрабатывающих фиксированное число пулов, это реальная оптимизация.\n\n## Обработка динамических данных\n\n### Обработка bytes и string\n\n```yul\nassembly {\n    \u002F\u002F Копируем bytes из calldata в память\n    let dataOffset := calldataload(4) \u002F\u002F смещение до данных\n    let dataLength := calldataload(add(4, dataOffset)) \u002F\u002F длина данных\n    let dataStart := add(add(4, dataOffset), 32) \u002F\u002F начало данных\n\n    let ptr := mload(0x40)\n    calldatacopy(ptr, dataStart, dataLength)\n    mstore(0x40, add(ptr, dataLength))\n\n    \u002F\u002F Обработка побайтно\n    for { let i := 0 } lt(i, dataLength) { i := add(i, 1) } {\n        let b := byte(0, mload(add(ptr, i)))\n        \u002F\u002F ... обработка байта ...\n    }\n}\n```\n\n### Обработка массива адресов\n\n```yul\nassembly {\n    \u002F\u002F Calldata: selector(4) + offset(32) + length(32) + address[](length * 32)\n    let length := calldataload(36)\n    let mask := 0xffffffffffffffffffffffffffffffffffffffff\n\n    for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n        let addr := and(calldataload(add(68, mul(i, 32))), mask)\n\n        \u002F\u002F Проверяем: адрес не нулевой\n        if iszero(addr) {\n            \u002F\u002F Записываем код ошибки и откатываем\n            mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n            mstore(0x04, 0x20)\n            mstore(0x24, 14) \u002F\u002F длина строки\n            mstore(0x44, \"Zero address\") \u002F\u002F текст ошибки\n            revert(0x00, 0x64)\n        }\n    }\n}\n```\n\n## Сравнительная таблица стоимости\n\n| Операция | Solidity | Yul | Экономия |\n|----------|----------|-----|----------|\n| Цикл (за итерацию) | ~800 газа | ~200 газа | 75% |\n| Сумма массива (100 элементов) | ~80000 газа | ~20000 газа | 75% |\n| ABI-декодирование | ~500 газа | ~50 газа | 90% |\n| Проверка переполнения | ~100 газа | 0 газа | 100% |\n| Чтение маппинга | ~2500 газа | ~2200 газа | 12% |\n\nОсновная экономия приходится на устранение проверок безопасности. Это значит, что вы должны быть абсолютно уверены в корректности своего кода.\n\n## Заключение\n\nЦиклы и условия в Yul — это фундаментальные строительные блоки газоэффективного кода. Ключевые принципы: минимизируйте число JUMP\u002FJUMPI, используйте безветвевой код где возможно, развёртывайте циклы с фиксированным числом итераций, и всегда помните о стоимости каждого опкода.\n\nВ финальной статье серии мы применим все изученные концепции для написания полноценного свопа токенов на чистом Yul — от чтения calldata до взаимодействия с Uniswap V2 пулом.","\u003Ch2 id=\"yul\">Почему циклы в Yul эффективнее\u003C\u002Fh2>\n\u003Cp>Когда Solidity компилирует цикл \u003Ccode>for\u003C\u002Fcode>, он добавляет множество проверок безопасности: верификацию границ массива, проверку переполнения счётчика, безопасный доступ к элементам. Каждая из этих проверок стоит газа. В Yul вы контролируете каждый аспект цикла и можете убрать ненужные проверки, сохраняя только необходимые.\u003C\u002Fp>\n\u003Ch2 id=\"yul\">Анатомия цикла в Yul\u003C\u002Fh2>\n\u003Cp>Цикл \u003Ccode>for\u003C\u002Fcode> в Yul имеет три блока:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F for { инициализация } условие { пост-итерация } { тело }\n    for { let i := 0 } lt(i, 10) { i := add(i, 1) } {\n        \u002F\u002F тело цикла\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>На уровне байткода это компилируется в:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Инициализация:\nPUSH1 0      \u002F\u002F i = 0\n\n\u002F\u002F Условие (начало итерации):\nJUMPDEST     \u002F\u002F метка начала цикла\nDUP1         \u002F\u002F копируем i\nPUSH1 10     \u002F\u002F предел\nLT           \u002F\u002F i &lt; 10?\nISZERO       \u002F\u002F инвертируем\nPUSH2 end    \u002F\u002F адрес конца\nJUMPI        \u002F\u002F если i &gt;= 10, выходим\n\n\u002F\u002F Тело цикла:\n...          \u002F\u002F ваш код\n\n\u002F\u002F Пост-итерация:\nPUSH1 1\nADD          \u002F\u002F i = i + 1\nPUSH2 start  \u002F\u002F адрес начала\nJUMP         \u002F\u002F обратно к условию\n\n\u002F\u002F Конец:\nJUMPDEST\nPOP          \u002F\u002F убираем i со стека\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Газ одной итерации\u003C\u002Fh3>\n\u003Cp>Стоимость «инфраструктуры» одной итерации цикла:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>JUMPDEST: 1 газ\u003C\u002Fli>\n\u003Cli>DUP1: 3 газа\u003C\u002Fli>\n\u003Cli>PUSH: 3 газа\u003C\u002Fli>\n\u003Cli>LT: 3 газа\u003C\u002Fli>\n\u003Cli>ISZERO: 3 газа\u003C\u002Fli>\n\u003Cli>PUSH: 3 газа\u003C\u002Fli>\n\u003Cli>JUMPI: 10 газа\u003C\u002Fli>\n\u003Cli>ADD (инкремент): 3 газа\u003C\u002Fli>\n\u003Cli>PUSH: 3 газа\u003C\u002Fli>\n\u003Cli>JUMP: 8 газа\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Итого: ~40 газа за итерацию\u003C\u002Fstrong> (только инфраструктура, без тела)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"solidity-vs-yul\">Сравнение: Solidity vs Yul\u003C\u002Fh2>\n\u003Ch3>Суммирование массива\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F Solidity — ~800 газа за итерацию\nfunction sumSolidity(uint256[] calldata arr) external pure returns (uint256 total) {\n    for (uint256 i = 0; i &lt; arr.length; i++) {\n        total += arr[i];\n    }\n}\n\n\u002F\u002F Yul — ~200 газа за итерацию\nfunction sumYul(uint256[] calldata arr) external pure returns (uint256 total) {\n    assembly {\n        let length := arr.length\n        let offset := arr.offset\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            total := add(total, calldataload(add(offset, mul(i, 32))))\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Разница объясняется тем, что Solidity добавляет:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Проверку переполнения \u003Ccode>i++\u003C\u002Fcode> (~100 газа)\u003C\u002Fli>\n\u003Cli>Проверку границ массива \u003Ccode>arr[i]\u003C\u002Fcode> (~200 газа)\u003C\u002Fli>\n\u003Cli>Проверку переполнения \u003Ccode>total += arr[i]\u003C\u002Fcode> (~100 газа)\u003C\u002Fli>\n\u003Cli>Безопасный доступ к calldata через ABI-декодирование (~200 газа)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Оптимизация: декремент вместо инкремента\u003C\u002Fh3>\n\u003Cp>Обратный цикл (от length к 0) чуть эффективнее, потому что \u003Ccode>ISZERO\u003C\u002Fcode> дешевле \u003Ccode>LT\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    let length := arr.length\n    let offset := arr.offset\n\n    \u002F\u002F Обратный цикл\n    for { let i := length } i { i := sub(i, 1) } {\n        let idx := sub(i, 1)\n        total := add(total, calldataload(add(offset, mul(idx, 32))))\n    }\n}\n\u002F\u002F Экономия: ~2 газа за итерацию (ISZERO vs LT)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Условные конструкции\u003C\u002Fh2>\n\u003Ch3>if в Yul\u003C\u002Fh3>\n\u003Cp>Yul не имеет \u003Ccode>else\u003C\u002Fcode>. Для условий с двумя ветвями используйте \u003Ccode>switch\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F if — только одна ветвь\n    if iszero(value) {\n        revert(0, 0)\n    }\n\n    \u002F\u002F switch — несколько ветвей\n    switch selector\n    case 0xa9059cbb { \u002F\u002F transfer\n        \u002F\u002F обработка transfer\n    }\n    case 0x70a08231 { \u002F\u002F balanceOf\n        \u002F\u002F обработка balanceOf\n    }\n    case 0x095ea7b3 { \u002F\u002F approve\n        \u002F\u002F обработка approve\n    }\n    default {\n        revert(0, 0)\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Тернарный оператор через Yul\u003C\u002Fh3>\n\u003Cp>Yul не имеет тернарного оператора, но его можно эмулировать:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F result = condition ? valueIfTrue : valueIfFalse\n    \u002F\u002F Способ 1: через mul + sub (branchless)\n    let result := add(mul(condition, valueIfTrue), mul(sub(1, condition), valueIfFalse))\n\n    \u002F\u002F Способ 2: через xor (если значения — 0\u002F1)\n    let result2 := xor(valueIfFalse, mul(xor(valueIfTrue, valueIfFalse), condition))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>«Безветвевой» (branchless) код часто эффективнее, потому что избегает JUMPI (10 газа) и связанных с ним опкодов.\u003C\u002Fp>\n\u003Ch2 id=\"defi\">Реальные паттерны из DeFi\u003C\u002Fh2>\n\u003Ch3>Паттерн 1: Поиск в отсортированном массиве (бинарный поиск)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    function binarySearch(arrSlot, length, target) -&gt; index, found {\n        let low := 0\n        let high := length\n\n        for {} lt(low, high) {} {\n            let mid := shr(1, add(low, high)) \u002F\u002F (low + high) \u002F 2\n            let midVal := sload(add(arrSlot, mid))\n\n            switch gt(target, midVal)\n            case 1 {\n                low := add(mid, 1)\n            }\n            default {\n                high := mid\n            }\n        }\n\n        if lt(low, length) {\n            let val := sload(add(arrSlot, low))\n            if eq(val, target) {\n                index := low\n                found := 1\n            }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Бинарный поиск в хранилище: O(log n) SLOAD вместо O(n). Для массива из 1000 элементов: ~10 SLOAD (21000 газа при холодном доступе) вместо ~500 SLOAD (1 050 000 газа).\u003C\u002Fp>\n\u003Ch3>Паттерн 2: Пакетная обработка трансферов\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Calldata: selector(4) + offset(32) + length(32) + [to(32) + amount(32)] * n\n    function batchTransfer(token) {\n        let length := calldataload(36)\n        let dataStart := 68 \u002F\u002F 4 + 32 + 32\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            let offset := add(dataStart, mul(i, 64))\n            let to := calldataload(offset)\n            let amount := calldataload(add(offset, 32))\n\n            \u002F\u002F Маскируем адрес\n            to := and(to, 0xffffffffffffffffffffffffffffffffffffffff)\n\n            \u002F\u002F Формируем calldata для transfer(address,uint256)\n            mstore(0x00, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n            mstore(0x04, to)\n            mstore(0x24, amount)\n\n            let success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)\n            if iszero(success) {\n                revert(0, 0)\n            }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Этот паттерн позволяет выполнить N трансферов в одной транзакции, экономя 21000 газа (внутренний газ транзакции) за каждый дополнительный трансфер.\u003C\u002Fp>\n\u003Ch3>Паттерн 3: Эффективный маппинг-итератор\u003C\u002Fh3>\n\u003Cp>Маппинги в Solidity не итерируемы. Но если вы поддерживаете параллельный массив ключей, можно эффективно итерировать:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Предполагаем: slot 0 = length, slot keccak256(0) + i = keys[i]\n    \u002F\u002F Маппинг balances: slot 1, balances[key] = keccak256(key . 1)\n\n    function sumAllBalances() -&gt; total {\n        let length := sload(0)\n        let keysBase := keccak256(0x00, 0x20) \u002F\u002F Нужно записать 0 в 0x00 перед keccak\n\n        \u002F\u002F Записываем базовый слот массива ключей\n        mstore(0x00, 0)\n        let keysSlot := keccak256(0x00, 0x20)\n\n        for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n            \u002F\u002F Загружаем ключ\n            let key := sload(add(keysSlot, i))\n\n            \u002F\u002F Вычисляем слот баланса\n            mstore(0x00, key)\n            mstore(0x20, 1) \u002F\u002F слот маппинга\n            let balanceSlot := keccak256(0x00, 0x40)\n\n            \u002F\u002F Суммируем\n            total := add(total, sload(balanceSlot))\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Паттерн 4: Развёрнутый цикл (Loop Unrolling)\u003C\u002Fh3>\n\u003Cp>Для фиксированного числа итераций развёрнутый цикл убирает накладные расходы JUMP\u002FJUMPI:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Вместо цикла на 4 итерации (~160 газа инфраструктуры)\n    \u002F\u002F Развёрнутый код (0 газа инфраструктуры)\n    let a := calldataload(0x04)\n    let b := calldataload(0x24)\n    let c := calldataload(0x44)\n    let d := calldataload(0x64)\n    let total := add(add(a, b), add(c, d))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Это экономит ~40 газа за «убранную» итерацию. Для 4 итераций: 160 газа экономии. Для MEV-ботов, обрабатывающих фиксированное число пулов, это реальная оптимизация.\u003C\u002Fp>\n\u003Ch2 id=\"\">Обработка динамических данных\u003C\u002Fh2>\n\u003Ch3>Обработка bytes и string\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Копируем bytes из calldata в память\n    let dataOffset := calldataload(4) \u002F\u002F смещение до данных\n    let dataLength := calldataload(add(4, dataOffset)) \u002F\u002F длина данных\n    let dataStart := add(add(4, dataOffset), 32) \u002F\u002F начало данных\n\n    let ptr := mload(0x40)\n    calldatacopy(ptr, dataStart, dataLength)\n    mstore(0x40, add(ptr, dataLength))\n\n    \u002F\u002F Обработка побайтно\n    for { let i := 0 } lt(i, dataLength) { i := add(i, 1) } {\n        let b := byte(0, mload(add(ptr, i)))\n        \u002F\u002F ... обработка байта ...\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Обработка массива адресов\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Calldata: selector(4) + offset(32) + length(32) + address[](length * 32)\n    let length := calldataload(36)\n    let mask := 0xffffffffffffffffffffffffffffffffffffffff\n\n    for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n        let addr := and(calldataload(add(68, mul(i, 32))), mask)\n\n        \u002F\u002F Проверяем: адрес не нулевой\n        if iszero(addr) {\n            \u002F\u002F Записываем код ошибки и откатываем\n            mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n            mstore(0x04, 0x20)\n            mstore(0x24, 14) \u002F\u002F длина строки\n            mstore(0x44, \"Zero address\") \u002F\u002F текст ошибки\n            revert(0x00, 0x64)\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Сравнительная таблица стоимости\u003C\u002Fh2>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Операция\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Экономия\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Цикл (за итерацию)\u003C\u002Ftd>\u003Ctd>~800 газа\u003C\u002Ftd>\u003Ctd>~200 газа\u003C\u002Ftd>\u003Ctd>75%\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Сумма массива (100 элементов)\u003C\u002Ftd>\u003Ctd>~80000 газа\u003C\u002Ftd>\u003Ctd>~20000 газа\u003C\u002Ftd>\u003Ctd>75%\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>ABI-декодирование\u003C\u002Ftd>\u003Ctd>~500 газа\u003C\u002Ftd>\u003Ctd>~50 газа\u003C\u002Ftd>\u003Ctd>90%\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Проверка переполнения\u003C\u002Ftd>\u003Ctd>~100 газа\u003C\u002Ftd>\u003Ctd>0 газа\u003C\u002Ftd>\u003Ctd>100%\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Чтение маппинга\u003C\u002Ftd>\u003Ctd>~2500 газа\u003C\u002Ftd>\u003Ctd>~2200 газа\u003C\u002Ftd>\u003Ctd>12%\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Основная экономия приходится на устранение проверок безопасности. Это значит, что вы должны быть абсолютно уверены в корректности своего кода.\u003C\u002Fp>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>Циклы и условия в Yul — это фундаментальные строительные блоки газоэффективного кода. Ключевые принципы: минимизируйте число JUMP\u002FJUMPI, используйте безветвевой код где возможно, развёртывайте циклы с фиксированным числом итераций, и всегда помните о стоимости каждого опкода.\u003C\u002Fp>\n\u003Cp>В финальной статье серии мы применим все изученные концепции для написания полноценного свопа токенов на чистом Yul — от чтения calldata до взаимодействия с Uniswap V2 пулом.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.427816Z","Как писать газоэффективные циклы и условия в Yul: сравнение с Solidity, паттерны из DeFi, развёртка циклов и безветвевой код.","Yul циклы оптимизация газа",null,"index, follow",[21,26,30,34],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000014","Solidity","solidity",{"id":35,"name":36,"slug":37,"created_at":25},"c0000000-0000-0000-0000-000000000018","Yul","yul","Блокчейн",[40,46,52],{"id":41,"title":42,"slug":43,"excerpt":44,"locale":12,"category_name":38,"published_at":45},"de000000-0000-0000-0000-000000000013","Уровень интероперабельности Ethereum: как 55+ L2 становятся одной сетью","uroven-interoperabelnosti-ethereum-kak-55-l2-stanovyatsya-odnoj-setyu","У Ethereum 55+ роллапов Layer 2, фрагментирующих ликвидность и пользовательский опыт. Уровень интероперабельности Ethereum — объединяющий кросс-роллап-мессаджинг, общие секвенсоры и based-роллапы — призван объединить их в единую компонуемую сеть.","2026-03-28T10:44:36.068675Z",{"id":47,"title":48,"slug":49,"excerpt":50,"locale":12,"category_name":38,"published_at":51},"de000000-0000-0000-0000-000000000012","ZK-доказательства за пределами роллапов: верифицируемый AI-инференс на Ethereum","zk-dokazatelstva-za-predelami-rollapov-verificiruemyj-ai-inferens-ethereum","Доказательства с нулевым разглашением — это уже не только инструмент масштабирования. В 2026 году zkML обеспечивает верифицируемый AI-инференс в блокчейне, ZK-копроцессоры переносят тяжёлые вычисления оффчейн с ончейн-верификацией, а новые системы доказательств SP1 и Jolt делают это практичным.","2026-03-28T10:44:36.026310Z",{"id":53,"title":54,"slug":55,"excerpt":56,"locale":12,"category_name":38,"published_at":57},"dd000000-0000-0000-0000-000000000013","EIP-7702 на практике: создание потоков смарт-аккаунтов после Pectra","eip-7702-na-praktike-sozdanie-potokov-smart-akkauntov-posle-pectra","EIP-7702 позволяет любому EOA Ethereum временно действовать как смарт-контракт в рамках одной транзакции. Вот как реализовать пакетные транзакции, спонсирование газа и социальное восстановление с помощью нового примитива абстракции аккаунтов.","2026-03-28T10:44:35.357227Z",{"id":13,"name":59,"slug":60,"bio":61,"photo_url":18,"linkedin":18,"role":62,"created_at":63,"updated_at":63},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]