[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-8-token-swap-pure-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-000000000108","a0000000-0000-0000-0000-000000000002","Deep EVM #8: Building a Token Swap in Pure Yul","deep-evm-8-token-swap-pure-yul","Build a complete Uniswap V2 token swap contract in pure Yul: calldata parsing, external calls, return data handling, and a line-by-line gas comparison with the Solidity equivalent.","## From Theory to Practice\n\nOver the past seven articles, we have built a comprehensive understanding of the EVM: opcodes and the stack machine, the memory model, gas mechanics, security primitives, Yul syntax, memory management, and loop optimization. Now we put it all together.\n\nIn this article, we build a complete token swap contract in pure Yul that executes a Uniswap V2 swap. Then we compare it line-by-line with the Solidity equivalent to quantify exactly where the gas savings come from.\n\n## The Contract: SimpleSwap\n\nOur contract does one thing: swap an exact amount of token A for token B through a Uniswap V2 pair. It exposes two functions:\n\n- `swap(address pair, address tokenIn, uint256 amountIn, uint256 amountOutMin, address to)` — Execute the swap\n- `owner()` — Return the contract owner (for access control)\n\n## The Solidity Version (Reference)\n\n```solidity\n\u002F\u002F SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ninterface IUniswapV2Pair {\n    function getReserves() external view returns (\n        uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast\n    );\n    function token0() external view returns (address);\n    function swap(\n        uint256 amount0Out, uint256 amount1Out,\n        address to, bytes calldata data\n    ) external;\n}\n\ninterface IERC20 {\n    function transfer(address to, uint256 amount) external returns (bool);\n}\n\ncontract SimpleSwapSolidity {\n    address public immutable owner;\n\n    constructor() {\n        owner = msg.sender;\n    }\n\n    function swap(\n        address pair,\n        address tokenIn,\n        uint256 amountIn,\n        uint256 amountOutMin,\n        address to\n    ) external {\n        require(msg.sender == owner, \"Not owner\");\n\n        \u002F\u002F Transfer tokens to the pair\n        IERC20(tokenIn).transfer(pair, amountIn);\n\n        \u002F\u002F Get reserves\n        (uint112 reserve0, uint112 reserve1,) =\n            IUniswapV2Pair(pair).getReserves();\n\n        \u002F\u002F Determine swap direction\n        address token0 = IUniswapV2Pair(pair).token0();\n        bool isToken0 = tokenIn == token0;\n\n        (uint256 reserveIn, uint256 reserveOut) = isToken0\n            ? (uint256(reserve0), uint256(reserve1))\n            : (uint256(reserve1), uint256(reserve0));\n\n        \u002F\u002F Calculate output amount (Uniswap V2 formula)\n        uint256 amountInWithFee = amountIn * 997;\n        uint256 numerator = amountInWithFee * reserveOut;\n        uint256 denominator = reserveIn * 1000 + amountInWithFee;\n        uint256 amountOut = numerator \u002F denominator;\n\n        require(amountOut >= amountOutMin, \"Insufficient output\");\n\n        \u002F\u002F Execute swap\n        (uint256 amount0Out, uint256 amount1Out) = isToken0\n            ? (uint256(0), amountOut)\n            : (amountOut, uint256(0));\n\n        IUniswapV2Pair(pair).swap(\n            amount0Out, amount1Out, to, \"\"\n        );\n    }\n}\n```\n\nThis is clean, readable Solidity. Now let us see the Yul version.\n\n## The Pure Yul Version\n\n```yul\nobject \"SimpleSwap\" {\n    code {\n        \u002F\u002F Constructor: store owner in slot 0\n        sstore(0, caller())\n\n        \u002F\u002F Deploy runtime code\n        let size := datasize(\"runtime\")\n        let offset := dataoffset(\"runtime\")\n        datacopy(0, offset, size)\n        return(0, size)\n    }\n\n    object \"runtime\" {\n        code {\n            \u002F\u002F No calldata = receive ETH (reject)\n            if iszero(calldatasize()) { revert(0, 0) }\n\n            \u002F\u002F Extract function selector (first 4 bytes)\n            let selector := shr(224, calldataload(0))\n\n            switch selector\n\n            \u002F\u002F owner() -> address\n            case 0x8da5cb5b {\n                mstore(0x00, sload(0))\n                return(0x00, 0x20)\n            }\n\n            \u002F\u002F swap(address,address,uint256,uint256,address)\n            \u002F\u002F Selector: 0x6d7e80b7\n            case 0x6d7e80b7 {\n                \u002F\u002F Access control: only owner\n                if iszero(eq(caller(), sload(0))) {\n                    mstore(0x00, 0x08c379a0)  \u002F\u002F Error(string) selector\n                    mstore(0x04, 0x20)\n                    mstore(0x24, 9)\n                    mstore(0x44, \"Not owner\")\n                    revert(0x00, 0x64)\n                }\n\n                \u002F\u002F Parse calldata\n                let pair := and(calldataload(0x04), 0xffffffffffffffffffffffffffffffffffffffff)\n                let tokenIn := and(calldataload(0x24), 0xffffffffffffffffffffffffffffffffffffffff)\n                let amountIn := calldataload(0x44)\n                let amountOutMin := calldataload(0x64)\n                let to := and(calldataload(0x84), 0xffffffffffffffffffffffffffffffffffffffff)\n\n                \u002F\u002F === Step 1: Transfer tokens to pair ===\n                \u002F\u002F transfer(address,uint256) selector: 0xa9059cbb\n                mstore(0x00, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n                mstore(0x04, pair)\n                mstore(0x24, amountIn)\n\n                let success := call(gas(), tokenIn, 0, 0x00, 0x44, 0x00, 0x20)\n\n                \u002F\u002F Handle non-standard ERC20 (USDT returns nothing)\n                if iszero(and(\n                    success,\n                    or(\n                        iszero(returndatasize()),\n                        and(gt(returndatasize(), 31), mload(0x00))\n                    )\n                )) {\n                    revert(0, 0)\n                }\n\n                \u002F\u002F === Step 2: Get reserves ===\n                \u002F\u002F getReserves() selector: 0x0902f1ac\n                mstore(0x00, 0x0902f1ac00000000000000000000000000000000000000000000000000000000)\n\n                success := staticcall(gas(), pair, 0x00, 0x04, 0x00, 0x60)\n                if iszero(success) { revert(0, 0) }\n\n                let reserve0 := mload(0x00)\n                let reserve1 := mload(0x20)\n\n                \u002F\u002F === Step 3: Get token0 for swap direction ===\n                \u002F\u002F token0() selector: 0x0dfe1681\n                mstore(0x00, 0x0dfe168100000000000000000000000000000000000000000000000000000000)\n\n                success := staticcall(gas(), pair, 0x00, 0x04, 0x00, 0x20)\n                if iszero(success) { revert(0, 0) }\n\n                let token0 := and(mload(0x00), 0xffffffffffffffffffffffffffffffffffffffff)\n                let isToken0 := eq(tokenIn, token0)\n\n                \u002F\u002F Determine reserveIn and reserveOut\n                let reserveIn := reserve1\n                let reserveOut := reserve0\n                if isToken0 {\n                    reserveIn := reserve0\n                    reserveOut := reserve1\n                }\n\n                \u002F\u002F === Step 4: Calculate amountOut (Uniswap V2 formula) ===\n                \u002F\u002F amountOut = (amountIn * 997 * reserveOut) \u002F\n                \u002F\u002F             (reserveIn * 1000 + amountIn * 997)\n                let amountInWithFee := mul(amountIn, 997)\n                let numerator := mul(amountInWithFee, reserveOut)\n                let denominator := add(mul(reserveIn, 1000), amountInWithFee)\n                let amountOut := div(numerator, denominator)\n\n                \u002F\u002F Slippage check\n                if lt(amountOut, amountOutMin) {\n                    mstore(0x00, 0x08c379a0)\n                    mstore(0x04, 0x20)\n                    mstore(0x24, 19)\n                    mstore(0x44, \"Insufficient output\")\n                    revert(0x00, 0x64)\n                }\n\n                \u002F\u002F === Step 5: Execute swap ===\n                \u002F\u002F swap(uint256,uint256,address,bytes)\n                \u002F\u002F Selector: 0x022c0d9f\n                let amount0Out := 0\n                let amount1Out := amountOut\n                if isToken0 {\n                    amount0Out := 0\n                    amount1Out := amountOut\n                }\n                if iszero(isToken0) {\n                    amount0Out := amountOut\n                    amount1Out := 0\n                }\n\n                mstore(0x00, 0x022c0d9f00000000000000000000000000000000000000000000000000000000)\n                mstore(0x04, amount0Out)      \u002F\u002F amount0Out\n                mstore(0x24, amount1Out)      \u002F\u002F amount1Out\n                mstore(0x44, to)              \u002F\u002F to address\n                mstore(0x64, 0x80)            \u002F\u002F offset to bytes data\n                mstore(0x84, 0x00)            \u002F\u002F bytes length = 0 (empty)\n\n                success := call(gas(), pair, 0, 0x00, 0xa4, 0x00, 0x00)\n                if iszero(success) {\n                    \u002F\u002F Forward revert data\n                    returndatacopy(0x00, 0x00, returndatasize())\n                    revert(0x00, returndatasize())\n                }\n\n                \u002F\u002F Return amountOut\n                mstore(0x00, amountOut)\n                return(0x00, 0x20)\n            }\n\n            \u002F\u002F Unknown function selector: revert\n            default { revert(0, 0) }\n        }\n    }\n}\n```\n\n## Line-by-Line Gas Comparison\n\nLet us break down where the Yul version saves gas compared to Solidity:\n\n### 1. Function Dispatch\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| Selector extraction | ~30 gas (compiler overhead) | ~13 gas (shr + calldataload) | 17 gas |\n| Function matching | ~44 gas (2 comparisons with binary search) | ~22 gas (direct switch) | 22 gas |\n\nSolidity generates additional code for function dispatch: argument decoding, validation, and routing. The Yul version goes straight from selector to implementation.\n\n### 2. Access Control Check\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| require(msg.sender == owner) | ~2220 gas (SLOAD + comparison + revert string) | ~2115 gas (SLOAD + eq + iszero) | 105 gas |\n\nThe Solidity version ABI-encodes the revert reason string at runtime. The Yul version stores the raw bytes directly.\n\n### 3. Token Transfer (ERC-20 call)\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| ABI encoding | ~120 gas | ~30 gas (3x mstore) | 90 gas |\n| Call + return check | ~200 gas | ~80 gas | 120 gas |\n| Memory allocation | ~30 gas (free pointer) | 0 gas (scratch space) | 30 gas |\n\nSolidity uses `abi.encodeWithSelector`, which involves free memory pointer reads\u002Fwrites, bounds checking, and dynamic encoding. The Yul version writes directly to scratch space.\n\n### 4. getReserves() Staticcall\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| ABI encoding | ~80 gas | ~9 gas (1x mstore) | 71 gas |\n| Return data decoding | ~100 gas | ~12 gas (2x mload) | 88 gas |\n| Memory management | ~30 gas | 0 gas | 30 gas |\n\n### 5. Uniswap V2 Math\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| amountIn * 997 | ~40 gas (checked mul) | ~8 gas (mul) | 32 gas |\n| numerator calculation | ~40 gas (checked mul) | ~8 gas (mul) | 32 gas |\n| denominator calculation | ~80 gas (checked mul + add) | ~14 gas (mul + add) | 66 gas |\n| Division | ~20 gas (checked div) | ~8 gas (div) | 12 gas |\n\nSolidity 0.8+ overflow checks add ~30 gas per arithmetic operation. In this context, overflow is impossible (reserves are uint112, amounts are bounded by token supply), so the checks are pure overhead.\n\n### 6. Swap Call\n\n| Component | Solidity | Yul | Savings |\n|-----------|----------|-----|---------|\n| ABI encoding (with dynamic bytes) | ~250 gas | ~45 gas (5x mstore) | 205 gas |\n| Memory allocation | ~30 gas | 0 gas | 30 gas |\n| Revert forwarding | ~60 gas | ~20 gas | 40 gas |\n\n### Total Gas Savings\n\n| Category | Savings |\n|----------|--------|\n| Function dispatch | 39 gas |\n| Access control | 105 gas |\n| ERC-20 transfer | 240 gas |\n| getReserves call | 189 gas |\n| token0 call | ~120 gas |\n| Arithmetic | 142 gas |\n| Swap call | 275 gas |\n| Deployment size | ~60% smaller bytecode |\n| **Total runtime savings** | **~1,110 gas** |\n\nFor a typical Uniswap V2 swap that costs ~110,000 gas total, saving 1,110 gas is approximately a 1% improvement. That might not sound like much, but for an MEV bot executing thousands of swaps per day, at an average gas price of 30 gwei and ETH at $3,000:\n\n```\n1,110 gas * 30 gwei * $3,000\u002FETH = $0.0999 per swap\n$0.0999 * 1,000 swaps\u002Fday = $99.90\u002Fday\n$99.90 * 365 = $36,463\u002Fyear\n```\n\nAnd that is for a single swap function. An MEV bot with multiple strategies, each saving 1-5% on gas, can save hundreds of thousands of dollars annually.\n\n## Deployment Cost\n\nThe Yul version has a significantly smaller bytecode:\n\n| Metric | Solidity | Yul |\n|--------|----------|-----|\n| Runtime bytecode | ~1,200 bytes | ~480 bytes |\n| Deployment gas | ~240,000 | ~96,000 |\n| Savings | — | 60% smaller, 60% cheaper to deploy |\n\nSmaller bytecode also means lower intrinsic gas for EXTCODESIZE and EXTCODECOPY operations when other contracts interact with yours.\n\n## Testing the Yul Contract\n\nYou can compile and test Yul contracts with Foundry:\n\n```bash\n# Compile Yul to bytecode\nsolc --strict-assembly --optimize --optimize-runs 200 SimpleSwap.yul --bin\n\n# Or use Foundry's Yul support in tests\n```\n\n```solidity\n\u002F\u002F test\u002FSimpleSwap.t.sol\ncontract SimpleSwapTest is Test {\n    address swap;\n\n    function setUp() public {\n        \u002F\u002F Deploy Yul contract from bytecode\n        bytes memory bytecode = hex\"...\";  \u002F\u002F compiled bytecode\n        assembly {\n            swap := create(0, add(bytecode, 0x20), mload(bytecode))\n        }\n    }\n\n    function testOwner() public {\n        (bool s, bytes memory data) = swap.staticcall(\n            abi.encodeWithSignature(\"owner()\")\n        );\n        assertTrue(s);\n        assertEq(abi.decode(data, (address)), address(this));\n    }\n\n    function testSwap() public {\n        \u002F\u002F Fork mainnet, use real Uniswap V2 pair\n        \u002F\u002F ...\n    }\n}\n```\n\n## Security Considerations for Pure Yul Contracts\n\nPure Yul contracts bypass all of Solidity's safety features. Before deploying:\n\n1. **Validate all calldata** — Mask addresses to 20 bytes, check array bounds.\n2. **Check all return values** — Every CALL\u002FSTATICCALL return value must be checked.\n3. **Handle non-standard tokens** — USDT returns nothing on transfer; your code must handle both cases.\n4. **Reentrancy** — Without Solidity's built-in modifiers, you must implement guards manually.\n5. **Arithmetic overflow** — Yul does not check. Verify that your input domain makes overflow impossible.\n6. **Memory corruption** — Track your memory layout carefully. Overlapping writes are silent bugs.\n7. **Formal verification** — Consider using tools like Certora or Halmos to verify critical invariants.\n\n## When Pure Yul Is Worth It\n\nPure Yul contracts make sense for:\n\n- **MEV bot execution contracts** — Every gas unit is profit margin\n- **Minimal proxies** — EIP-1167 is pure bytecode by design\n- **Precompile wrappers** — Thin wrappers around ecrecover, modexp, etc.\n- **Gas-golfing competitions** — Pushing the EVM to its limits\n\nPure Yul is NOT worth it for:\n\n- **Application contracts** — The audit cost exceeds the gas savings\n- **Governance contracts** — Readability and correctness matter more than gas\n- **Any contract handling user deposits** — The risk of a subtle bug losing funds is too high\n\n## Beyond Yul: The Huff Frontier\n\nIf Yul gives you 80-90% of maximum gas efficiency, Huff gives you 95-100%. Huff removes even Yul's abstractions — no variables, no functions, just raw stack manipulation and macros. The SimpleSwap contract in Huff would save an additional 200-500 gas by eliminating Yul's variable management overhead.\n\nBut that is a topic for another series. For now, you have the complete toolkit: EVM fundamentals, memory management, gas optimization, security awareness, and practical Yul skills to build gas-efficient contracts.\n\n## Conclusion\n\nThis series has taken you from EVM basics to building production-grade Yul code. The key takeaways:\n\n1. **The EVM is a stack machine** with 256-bit words, ~140 opcodes, and gas metering.\n2. **Storage is expensive** — 20000 gas for new entries, 2100 for cold reads. Minimize state access.\n3. **Gas optimization is architectural** — Storage packing, calldata over memory, access lists.\n4. **Security comes from understanding execution flow** — CEI pattern, reentrancy guards, return value checks.\n5. **Yul gives you opcode-level control** with readable syntax.\n6. **Memory management is manual** — Free memory pointer, scratch space, expansion costs.\n7. **Loops multiply everything** — Optimize the loop body first, then the loop structure.\n8. **Pure Yul saves 1-5% gas** over optimized Solidity — worth millions for high-volume operations.\n\nThe EVM is a simple machine. Master its rules, and you master on-chain computation.","\u003Ch2 id=\"from-theory-to-practice\">From Theory to Practice\u003C\u002Fh2>\n\u003Cp>Over the past seven articles, we have built a comprehensive understanding of the EVM: opcodes and the stack machine, the memory model, gas mechanics, security primitives, Yul syntax, memory management, and loop optimization. Now we put it all together.\u003C\u002Fp>\n\u003Cp>In this article, we build a complete token swap contract in pure Yul that executes a Uniswap V2 swap. Then we compare it line-by-line with the Solidity equivalent to quantify exactly where the gas savings come from.\u003C\u002Fp>\n\u003Ch2 id=\"the-contract-simpleswap\">The Contract: SimpleSwap\u003C\u002Fh2>\n\u003Cp>Our contract does one thing: swap an exact amount of token A for token B through a Uniswap V2 pair. It exposes two functions:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>swap(address pair, address tokenIn, uint256 amountIn, uint256 amountOutMin, address to)\u003C\u002Fcode> — Execute the swap\u003C\u002Fli>\n\u003Cli>\u003Ccode>owner()\u003C\u002Fcode> — Return the contract owner (for access control)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"the-solidity-version-reference\">The Solidity Version (Reference)\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ninterface IUniswapV2Pair {\n    function getReserves() external view returns (\n        uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast\n    );\n    function token0() external view returns (address);\n    function swap(\n        uint256 amount0Out, uint256 amount1Out,\n        address to, bytes calldata data\n    ) external;\n}\n\ninterface IERC20 {\n    function transfer(address to, uint256 amount) external returns (bool);\n}\n\ncontract SimpleSwapSolidity {\n    address public immutable owner;\n\n    constructor() {\n        owner = msg.sender;\n    }\n\n    function swap(\n        address pair,\n        address tokenIn,\n        uint256 amountIn,\n        uint256 amountOutMin,\n        address to\n    ) external {\n        require(msg.sender == owner, \"Not owner\");\n\n        \u002F\u002F Transfer tokens to the pair\n        IERC20(tokenIn).transfer(pair, amountIn);\n\n        \u002F\u002F Get reserves\n        (uint112 reserve0, uint112 reserve1,) =\n            IUniswapV2Pair(pair).getReserves();\n\n        \u002F\u002F Determine swap direction\n        address token0 = IUniswapV2Pair(pair).token0();\n        bool isToken0 = tokenIn == token0;\n\n        (uint256 reserveIn, uint256 reserveOut) = isToken0\n            ? (uint256(reserve0), uint256(reserve1))\n            : (uint256(reserve1), uint256(reserve0));\n\n        \u002F\u002F Calculate output amount (Uniswap V2 formula)\n        uint256 amountInWithFee = amountIn * 997;\n        uint256 numerator = amountInWithFee * reserveOut;\n        uint256 denominator = reserveIn * 1000 + amountInWithFee;\n        uint256 amountOut = numerator \u002F denominator;\n\n        require(amountOut &gt;= amountOutMin, \"Insufficient output\");\n\n        \u002F\u002F Execute swap\n        (uint256 amount0Out, uint256 amount1Out) = isToken0\n            ? (uint256(0), amountOut)\n            : (amountOut, uint256(0));\n\n        IUniswapV2Pair(pair).swap(\n            amount0Out, amount1Out, to, \"\"\n        );\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This is clean, readable Solidity. Now let us see the Yul version.\u003C\u002Fp>\n\u003Ch2 id=\"the-pure-yul-version\">The Pure Yul Version\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-yul\">object \"SimpleSwap\" {\n    code {\n        \u002F\u002F Constructor: store owner in slot 0\n        sstore(0, caller())\n\n        \u002F\u002F Deploy runtime code\n        let size := datasize(\"runtime\")\n        let offset := dataoffset(\"runtime\")\n        datacopy(0, offset, size)\n        return(0, size)\n    }\n\n    object \"runtime\" {\n        code {\n            \u002F\u002F No calldata = receive ETH (reject)\n            if iszero(calldatasize()) { revert(0, 0) }\n\n            \u002F\u002F Extract function selector (first 4 bytes)\n            let selector := shr(224, calldataload(0))\n\n            switch selector\n\n            \u002F\u002F owner() -&gt; address\n            case 0x8da5cb5b {\n                mstore(0x00, sload(0))\n                return(0x00, 0x20)\n            }\n\n            \u002F\u002F swap(address,address,uint256,uint256,address)\n            \u002F\u002F Selector: 0x6d7e80b7\n            case 0x6d7e80b7 {\n                \u002F\u002F Access control: only owner\n                if iszero(eq(caller(), sload(0))) {\n                    mstore(0x00, 0x08c379a0)  \u002F\u002F Error(string) selector\n                    mstore(0x04, 0x20)\n                    mstore(0x24, 9)\n                    mstore(0x44, \"Not owner\")\n                    revert(0x00, 0x64)\n                }\n\n                \u002F\u002F Parse calldata\n                let pair := and(calldataload(0x04), 0xffffffffffffffffffffffffffffffffffffffff)\n                let tokenIn := and(calldataload(0x24), 0xffffffffffffffffffffffffffffffffffffffff)\n                let amountIn := calldataload(0x44)\n                let amountOutMin := calldataload(0x64)\n                let to := and(calldataload(0x84), 0xffffffffffffffffffffffffffffffffffffffff)\n\n                \u002F\u002F === Step 1: Transfer tokens to pair ===\n                \u002F\u002F transfer(address,uint256) selector: 0xa9059cbb\n                mstore(0x00, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n                mstore(0x04, pair)\n                mstore(0x24, amountIn)\n\n                let success := call(gas(), tokenIn, 0, 0x00, 0x44, 0x00, 0x20)\n\n                \u002F\u002F Handle non-standard ERC20 (USDT returns nothing)\n                if iszero(and(\n                    success,\n                    or(\n                        iszero(returndatasize()),\n                        and(gt(returndatasize(), 31), mload(0x00))\n                    )\n                )) {\n                    revert(0, 0)\n                }\n\n                \u002F\u002F === Step 2: Get reserves ===\n                \u002F\u002F getReserves() selector: 0x0902f1ac\n                mstore(0x00, 0x0902f1ac00000000000000000000000000000000000000000000000000000000)\n\n                success := staticcall(gas(), pair, 0x00, 0x04, 0x00, 0x60)\n                if iszero(success) { revert(0, 0) }\n\n                let reserve0 := mload(0x00)\n                let reserve1 := mload(0x20)\n\n                \u002F\u002F === Step 3: Get token0 for swap direction ===\n                \u002F\u002F token0() selector: 0x0dfe1681\n                mstore(0x00, 0x0dfe168100000000000000000000000000000000000000000000000000000000)\n\n                success := staticcall(gas(), pair, 0x00, 0x04, 0x00, 0x20)\n                if iszero(success) { revert(0, 0) }\n\n                let token0 := and(mload(0x00), 0xffffffffffffffffffffffffffffffffffffffff)\n                let isToken0 := eq(tokenIn, token0)\n\n                \u002F\u002F Determine reserveIn and reserveOut\n                let reserveIn := reserve1\n                let reserveOut := reserve0\n                if isToken0 {\n                    reserveIn := reserve0\n                    reserveOut := reserve1\n                }\n\n                \u002F\u002F === Step 4: Calculate amountOut (Uniswap V2 formula) ===\n                \u002F\u002F amountOut = (amountIn * 997 * reserveOut) \u002F\n                \u002F\u002F             (reserveIn * 1000 + amountIn * 997)\n                let amountInWithFee := mul(amountIn, 997)\n                let numerator := mul(amountInWithFee, reserveOut)\n                let denominator := add(mul(reserveIn, 1000), amountInWithFee)\n                let amountOut := div(numerator, denominator)\n\n                \u002F\u002F Slippage check\n                if lt(amountOut, amountOutMin) {\n                    mstore(0x00, 0x08c379a0)\n                    mstore(0x04, 0x20)\n                    mstore(0x24, 19)\n                    mstore(0x44, \"Insufficient output\")\n                    revert(0x00, 0x64)\n                }\n\n                \u002F\u002F === Step 5: Execute swap ===\n                \u002F\u002F swap(uint256,uint256,address,bytes)\n                \u002F\u002F Selector: 0x022c0d9f\n                let amount0Out := 0\n                let amount1Out := amountOut\n                if isToken0 {\n                    amount0Out := 0\n                    amount1Out := amountOut\n                }\n                if iszero(isToken0) {\n                    amount0Out := amountOut\n                    amount1Out := 0\n                }\n\n                mstore(0x00, 0x022c0d9f00000000000000000000000000000000000000000000000000000000)\n                mstore(0x04, amount0Out)      \u002F\u002F amount0Out\n                mstore(0x24, amount1Out)      \u002F\u002F amount1Out\n                mstore(0x44, to)              \u002F\u002F to address\n                mstore(0x64, 0x80)            \u002F\u002F offset to bytes data\n                mstore(0x84, 0x00)            \u002F\u002F bytes length = 0 (empty)\n\n                success := call(gas(), pair, 0, 0x00, 0xa4, 0x00, 0x00)\n                if iszero(success) {\n                    \u002F\u002F Forward revert data\n                    returndatacopy(0x00, 0x00, returndatasize())\n                    revert(0x00, returndatasize())\n                }\n\n                \u002F\u002F Return amountOut\n                mstore(0x00, amountOut)\n                return(0x00, 0x20)\n            }\n\n            \u002F\u002F Unknown function selector: revert\n            default { revert(0, 0) }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"line-by-line-gas-comparison\">Line-by-Line Gas Comparison\u003C\u002Fh2>\n\u003Cp>Let us break down where the Yul version saves gas compared to Solidity:\u003C\u002Fp>\n\u003Ch3>1. Function Dispatch\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Selector extraction\u003C\u002Ftd>\u003Ctd>~30 gas (compiler overhead)\u003C\u002Ftd>\u003Ctd>~13 gas (shr + calldataload)\u003C\u002Ftd>\u003Ctd>17 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Function matching\u003C\u002Ftd>\u003Ctd>~44 gas (2 comparisons with binary search)\u003C\u002Ftd>\u003Ctd>~22 gas (direct switch)\u003C\u002Ftd>\u003Ctd>22 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Solidity generates additional code for function dispatch: argument decoding, validation, and routing. The Yul version goes straight from selector to implementation.\u003C\u002Fp>\n\u003Ch3>2. Access Control Check\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>require(msg.sender == owner)\u003C\u002Ftd>\u003Ctd>~2220 gas (SLOAD + comparison + revert string)\u003C\u002Ftd>\u003Ctd>~2115 gas (SLOAD + eq + iszero)\u003C\u002Ftd>\u003Ctd>105 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>The Solidity version ABI-encodes the revert reason string at runtime. The Yul version stores the raw bytes directly.\u003C\u002Fp>\n\u003Ch3>3. Token Transfer (ERC-20 call)\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>ABI encoding\u003C\u002Ftd>\u003Ctd>~120 gas\u003C\u002Ftd>\u003Ctd>~30 gas (3x mstore)\u003C\u002Ftd>\u003Ctd>90 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Call + return check\u003C\u002Ftd>\u003Ctd>~200 gas\u003C\u002Ftd>\u003Ctd>~80 gas\u003C\u002Ftd>\u003Ctd>120 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Memory allocation\u003C\u002Ftd>\u003Ctd>~30 gas (free pointer)\u003C\u002Ftd>\u003Ctd>0 gas (scratch space)\u003C\u002Ftd>\u003Ctd>30 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Solidity uses \u003Ccode>abi.encodeWithSelector\u003C\u002Fcode>, which involves free memory pointer reads\u002Fwrites, bounds checking, and dynamic encoding. The Yul version writes directly to scratch space.\u003C\u002Fp>\n\u003Ch3>4. getReserves() Staticcall\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>ABI encoding\u003C\u002Ftd>\u003Ctd>~80 gas\u003C\u002Ftd>\u003Ctd>~9 gas (1x mstore)\u003C\u002Ftd>\u003Ctd>71 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Return data decoding\u003C\u002Ftd>\u003Ctd>~100 gas\u003C\u002Ftd>\u003Ctd>~12 gas (2x mload)\u003C\u002Ftd>\u003Ctd>88 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Memory management\u003C\u002Ftd>\u003Ctd>~30 gas\u003C\u002Ftd>\u003Ctd>0 gas\u003C\u002Ftd>\u003Ctd>30 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch3>5. Uniswap V2 Math\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>amountIn * 997\u003C\u002Ftd>\u003Ctd>~40 gas (checked mul)\u003C\u002Ftd>\u003Ctd>~8 gas (mul)\u003C\u002Ftd>\u003Ctd>32 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>numerator calculation\u003C\u002Ftd>\u003Ctd>~40 gas (checked mul)\u003C\u002Ftd>\u003Ctd>~8 gas (mul)\u003C\u002Ftd>\u003Ctd>32 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>denominator calculation\u003C\u002Ftd>\u003Ctd>~80 gas (checked mul + add)\u003C\u002Ftd>\u003Ctd>~14 gas (mul + add)\u003C\u002Ftd>\u003Ctd>66 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Division\u003C\u002Ftd>\u003Ctd>~20 gas (checked div)\u003C\u002Ftd>\u003Ctd>~8 gas (div)\u003C\u002Ftd>\u003Ctd>12 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Solidity 0.8+ overflow checks add ~30 gas per arithmetic operation. In this context, overflow is impossible (reserves are uint112, amounts are bounded by token supply), so the checks are pure overhead.\u003C\u002Fp>\n\u003Ch3>6. Swap Call\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Component\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>ABI encoding (with dynamic bytes)\u003C\u002Ftd>\u003Ctd>~250 gas\u003C\u002Ftd>\u003Ctd>~45 gas (5x mstore)\u003C\u002Ftd>\u003Ctd>205 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Memory allocation\u003C\u002Ftd>\u003Ctd>~30 gas\u003C\u002Ftd>\u003Ctd>0 gas\u003C\u002Ftd>\u003Ctd>30 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Revert forwarding\u003C\u002Ftd>\u003Ctd>~60 gas\u003C\u002Ftd>\u003Ctd>~20 gas\u003C\u002Ftd>\u003Ctd>40 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch3>Total Gas Savings\u003C\u002Fh3>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Category\u003C\u002Fth>\u003Cth>Savings\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Function dispatch\u003C\u002Ftd>\u003Ctd>39 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Access control\u003C\u002Ftd>\u003Ctd>105 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>ERC-20 transfer\u003C\u002Ftd>\u003Ctd>240 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>getReserves call\u003C\u002Ftd>\u003Ctd>189 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>token0 call\u003C\u002Ftd>\u003Ctd>~120 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Arithmetic\u003C\u002Ftd>\u003Ctd>142 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Swap call\u003C\u002Ftd>\u003Ctd>275 gas\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Deployment size\u003C\u002Ftd>\u003Ctd>~60% smaller bytecode\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Cstrong>Total runtime savings\u003C\u002Fstrong>\u003C\u002Ftd>\u003Ctd>\u003Cstrong>~1,110 gas\u003C\u002Fstrong>\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>For a typical Uniswap V2 swap that costs ~110,000 gas total, saving 1,110 gas is approximately a 1% improvement. That might not sound like much, but for an MEV bot executing thousands of swaps per day, at an average gas price of 30 gwei and ETH at $3,000:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>1,110 gas * 30 gwei * $3,000\u002FETH = $0.0999 per swap\n$0.0999 * 1,000 swaps\u002Fday = $99.90\u002Fday\n$99.90 * 365 = $36,463\u002Fyear\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>And that is for a single swap function. An MEV bot with multiple strategies, each saving 1-5% on gas, can save hundreds of thousands of dollars annually.\u003C\u002Fp>\n\u003Ch2 id=\"deployment-cost\">Deployment Cost\u003C\u002Fh2>\n\u003Cp>The Yul version has a significantly smaller bytecode:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Metric\u003C\u002Fth>\u003Cth>Solidity\u003C\u002Fth>\u003Cth>Yul\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Runtime bytecode\u003C\u002Ftd>\u003Ctd>~1,200 bytes\u003C\u002Ftd>\u003Ctd>~480 bytes\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Deployment gas\u003C\u002Ftd>\u003Ctd>~240,000\u003C\u002Ftd>\u003Ctd>~96,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Savings\u003C\u002Ftd>\u003Ctd>—\u003C\u002Ftd>\u003Ctd>60% smaller, 60% cheaper to deploy\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Smaller bytecode also means lower intrinsic gas for EXTCODESIZE and EXTCODECOPY operations when other contracts interact with yours.\u003C\u002Fp>\n\u003Ch2 id=\"testing-the-yul-contract\">Testing the Yul Contract\u003C\u002Fh2>\n\u003Cp>You can compile and test Yul contracts with Foundry:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Compile Yul to bytecode\nsolc --strict-assembly --optimize --optimize-runs 200 SimpleSwap.yul --bin\n\n# Or use Foundry's Yul support in tests\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F test\u002FSimpleSwap.t.sol\ncontract SimpleSwapTest is Test {\n    address swap;\n\n    function setUp() public {\n        \u002F\u002F Deploy Yul contract from bytecode\n        bytes memory bytecode = hex\"...\";  \u002F\u002F compiled bytecode\n        assembly {\n            swap := create(0, add(bytecode, 0x20), mload(bytecode))\n        }\n    }\n\n    function testOwner() public {\n        (bool s, bytes memory data) = swap.staticcall(\n            abi.encodeWithSignature(\"owner()\")\n        );\n        assertTrue(s);\n        assertEq(abi.decode(data, (address)), address(this));\n    }\n\n    function testSwap() public {\n        \u002F\u002F Fork mainnet, use real Uniswap V2 pair\n        \u002F\u002F ...\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"security-considerations-for-pure-yul-contracts\">Security Considerations for Pure Yul Contracts\u003C\u002Fh2>\n\u003Cp>Pure Yul contracts bypass all of Solidity’s safety features. Before deploying:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Validate all calldata\u003C\u002Fstrong> — Mask addresses to 20 bytes, check array bounds.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Check all return values\u003C\u002Fstrong> — Every CALL\u002FSTATICCALL return value must be checked.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Handle non-standard tokens\u003C\u002Fstrong> — USDT returns nothing on transfer; your code must handle both cases.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Reentrancy\u003C\u002Fstrong> — Without Solidity’s built-in modifiers, you must implement guards manually.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Arithmetic overflow\u003C\u002Fstrong> — Yul does not check. Verify that your input domain makes overflow impossible.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Memory corruption\u003C\u002Fstrong> — Track your memory layout carefully. Overlapping writes are silent bugs.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Formal verification\u003C\u002Fstrong> — Consider using tools like Certora or Halmos to verify critical invariants.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"when-pure-yul-is-worth-it\">When Pure Yul Is Worth It\u003C\u002Fh2>\n\u003Cp>Pure Yul contracts make sense for:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>MEV bot execution contracts\u003C\u002Fstrong> — Every gas unit is profit margin\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Minimal proxies\u003C\u002Fstrong> — EIP-1167 is pure bytecode by design\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Precompile wrappers\u003C\u002Fstrong> — Thin wrappers around ecrecover, modexp, etc.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Gas-golfing competitions\u003C\u002Fstrong> — Pushing the EVM to its limits\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Pure Yul is NOT worth it for:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Application contracts\u003C\u002Fstrong> — The audit cost exceeds the gas savings\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Governance contracts\u003C\u002Fstrong> — Readability and correctness matter more than gas\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Any contract handling user deposits\u003C\u002Fstrong> — The risk of a subtle bug losing funds is too high\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"beyond-yul-the-huff-frontier\">Beyond Yul: The Huff Frontier\u003C\u002Fh2>\n\u003Cp>If Yul gives you 80-90% of maximum gas efficiency, Huff gives you 95-100%. Huff removes even Yul’s abstractions — no variables, no functions, just raw stack manipulation and macros. The SimpleSwap contract in Huff would save an additional 200-500 gas by eliminating Yul’s variable management overhead.\u003C\u002Fp>\n\u003Cp>But that is a topic for another series. For now, you have the complete toolkit: EVM fundamentals, memory management, gas optimization, security awareness, and practical Yul skills to build gas-efficient contracts.\u003C\u002Fp>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>This series has taken you from EVM basics to building production-grade Yul code. The key takeaways:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>The EVM is a stack machine\u003C\u002Fstrong> with 256-bit words, ~140 opcodes, and gas metering.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Storage is expensive\u003C\u002Fstrong> — 20000 gas for new entries, 2100 for cold reads. Minimize state access.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Gas optimization is architectural\u003C\u002Fstrong> — Storage packing, calldata over memory, access lists.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Security comes from understanding execution flow\u003C\u002Fstrong> — CEI pattern, reentrancy guards, return value checks.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Yul gives you opcode-level control\u003C\u002Fstrong> with readable syntax.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Memory management is manual\u003C\u002Fstrong> — Free memory pointer, scratch space, expansion costs.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Loops multiply everything\u003C\u002Fstrong> — Optimize the loop body first, then the loop structure.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Pure Yul saves 1-5% gas\u003C\u002Fstrong> over optimized Solidity — worth millions for high-volume operations.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>The EVM is a simple machine. Master its rules, and you master on-chain computation.\u003C\u002Fp>\n","en","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:22.701953Z","Build a complete Uniswap V2 token swap in pure Yul: calldata parsing, external calls, return data handling, and a line-by-line gas comparison with Solidity.","Yul token swap",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-000000000017","Huff","huff",{"id":35,"name":36,"slug":37,"created_at":25},"c0000000-0000-0000-0000-000000000018","Yul","yul","Blockchain",[40,46,52],{"id":41,"title":42,"slug":43,"excerpt":44,"locale":12,"category_name":38,"published_at":45},"de000000-0000-0000-0000-000000000003","The Ethereum Interoperability Layer: How 55+ L2s Become One Chain","ethereum-interoperability-layer-how-55-l2s-become-one-chain","Ethereum has 55+ Layer 2 rollups, fragmenting liquidity and user experience. The Ethereum Interoperability Layer — combining cross-rollup messaging, shared sequencers, and based rollups — aims to unify them into a single composable network.","2026-03-28T10:44:35.632478Z",{"id":47,"title":48,"slug":49,"excerpt":50,"locale":12,"category_name":38,"published_at":51},"de000000-0000-0000-0000-000000000002","ZK Proofs Beyond Rollups: Verifiable AI Inference on Ethereum","zk-proofs-beyond-rollups-verifiable-ai-inference-ethereum","Zero-knowledge proofs are no longer just a scaling tool. In 2026, zkML enables verifiable AI inference on-chain, ZK coprocessors move heavy computation off-chain with on-chain verification, and new proving systems like SP1 and Jolt make it practical.","2026-03-28T10:44:35.618408Z",{"id":53,"title":54,"slug":55,"excerpt":56,"locale":12,"category_name":38,"published_at":57},"dd000000-0000-0000-0000-000000000003","EIP-7702 in Practice: Building Smart Account Flows After Pectra","eip-7702-in-practice-building-smart-account-flows-after-pectra","EIP-7702 lets any Ethereum EOA temporarily act as a smart contract within a single transaction. Here is how to implement batch transactions, gas sponsorship, and social recovery using the new account abstraction primitive.","2026-03-28T10:44:35.031290Z",{"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"]