Tutorial·10 min का पठन·Solingo द्वारा

Yul Assembly — कब और क्यों Use करें

Solidity में inline assembly डराता है। यहाँ देखें कब ये सही tool है और कब premature।

# Yul Assembly — कब और क्यों Use करें

Solidity में assembly { } blocks देखकर डर लगता है — लेकिन कभी-कभी ये सही tool है।

यहाँ समझें Yul क्या है, कब use करें, और कब avoid करें।

Yul क्या है?

Yul = intermediate language — Solidity और EVM opcodes के बीच।

Solidity → Yul → Bytecode → EVM

Example

Solidity:

function add(uint a, uint b) public pure returns (uint) {

return a + b;

}

Equivalent Yul:

function add(uint a, uint b) public pure returns (uint result) {

assembly {

result := add(a, b)

}

}

Same result — लेकिन Yul gives fine control

Yul Syntax Basics

1. Variables

assembly {

let x := 5

let y := add(x, 10) // y = 15

}

2. Memory

assembly {

// Store at memory position 0x80

mstore(0x80, 42)

// Load from 0x80

let val := mload(0x80) // val = 42

}

3. Storage

assembly {

// Store value at slot 0

sstore(0, 100)

// Load from slot 0

let val := sload(0) // val = 100

}

4. Calldata

assembly {

// First 4 bytes = function selector

let selector := shr(224, calldataload(0))

// Next 32 bytes = first argument

let arg1 := calldataload(4)

}

कब Use करें

1. Gas-Critical Paths

Example: Array sum (thousands of elements)।

Solidity:

function sum(uint[] memory arr) public pure returns (uint total) {

for (uint i = 0; i < arr.length; i++) {

total += arr[i];

}

}

Yul (optimized):

function sum(uint[] memory arr) public pure returns (uint total) {

assembly {

let len := mload(arr)

let data := add(arr, 0x20)

for { let i := 0 } lt(i, len) { i := add(i, 1) } {

total := add(total, mload(add(data, mul(i, 0x20))))

}

}

}

Gas savings: ~20-30% for large arrays।

2. Opcodes Not Exposed

Solidity doesn't expose all opcodes — Yul does।

Example: extcodehash

Check if address is contract:

function isContract(address addr) public view returns (bool) {

bytes32 codehash;

assembly {

codehash := extcodehash(addr)

}

// EOA = 0x0, Contract = hash of bytecode

return codehash != 0x0 && codehash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

}

Example: staticcall with custom gas

function safeCall(address target, bytes memory data) public view returns (bool success, bytes memory result) {

assembly {

let freeMemPtr := mload(0x40)

success := staticcall(

5000, // Gas limit

target,

add(data, 0x20), // Data pointer

mload(data), // Data length

freeMemPtr, // Output pointer

0 // Output size (unknown)

)

let size := returndatasize()

result := mload(0x40)

mstore(result, size)

returndatacopy(add(result, 0x20), 0, size)

mstore(0x40, add(result, add(0x20, size)))

}

}

3. Precompiles

Calling precompiled contracts directly।

// ecrecover via precompile (address 0x1)

function recoverSigner(bytes32 hash, bytes memory sig) public view returns (address) {

bytes32 r; bytes32 s; uint8 v;

assembly {

r := mload(add(sig, 0x20))

s := mload(add(sig, 0x40))

v := byte(0, mload(add(sig, 0x60)))

}

address signer;

assembly {

let freeMemPtr := mload(0x40)

mstore(freeMemPtr, hash)

mstore(add(freeMemPtr, 0x20), v)

mstore(add(freeMemPtr, 0x40), r)

mstore(add(freeMemPtr, 0x60), s)

let success := staticcall(3000, 0x01, freeMemPtr, 0x80, freeMemPtr, 0x20)

signer := mload(freeMemPtr)

}

return signer;

}

कब NOT Use करें

1. Premature Optimization

// ❌ Don't do this

function transfer(address to, uint amount) external {

assembly {

// 50 lines of assembly for 2% gas savings

}

}

Rule: Optimize after profiling shows bottleneck।

2. Readability Matters

Protocol जो 10+ devs maintain करते हैं:

// ❌ Assembly everywhere = maintenance nightmare

Trade-off: 5% gas savings vs 50% slower reviews।

3. Complex Logic

// ❌ Don't implement AMM math in assembly

function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) {

assembly {

// 100 lines of assembly = bug-prone

}

}

Solidity compiler अच्छी तरह optimize करता है — trust it।

Common Patterns

1. Efficient Keccak256

function hashPair(bytes32 a, bytes32 b) public pure returns (bytes32) {

assembly {

mstore(0x00, a)

mstore(0x20, b)

return(keccak256(0x00, 0x40), 0x20)

}

}

2. Returndata Forwarding

Proxy pattern:

fallback() external payable {

address impl = implementation;

assembly {

calldatacopy(0, 0, calldatasize())

let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

returndatacopy(0, 0, returndatasize())

switch result

case 0 { revert(0, returndatasize()) }

default { return(0, returndatasize()) }

}

}

3. Packed Storage Read

// Slot 0: [uint128 a][uint128 b]

function readPacked() public view returns (uint128 a, uint128 b) {

assembly {

let packed := sload(0)

a := shr(128, packed)

b := and(packed, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)

}

}

Pitfalls

1. Memory Safety

// ❌ Overwrites free memory pointer

assembly {

mstore(0x40, 0x80) // Danger!

}

// ✅ Respect free memory pointer

assembly {

let freeMemPtr := mload(0x40)

// Use freeMemPtr, then update:

mstore(0x40, add(freeMemPtr, 0x20))

}

2. Stack Too Deep

// ❌ Yul has 16-variable stack limit

assembly {

let a := 1

let b := 2

// ... 14 more variables

let p := 16 // Stack too deep!

}

// ✅ Use memory

assembly {

mstore(0x80, 1)

mstore(0xa0, 2)

}

3. No Type Safety

// ❌ Yul doesn't check types

assembly {

let addr := 999999999999 // Invalid address, no error!

}

Tools

1. Foundry Gas Snapshots

forge snapshot --diff

Compare Solidity vs Yul implementations।

2. EVM Codes

https://evm.codes — opcode reference।

3. Yul Optimizer

// solc --optimize --ir-optimized

See how Solidity compiles to Yul।

Real Example: Low-Level Delegatecall

Uniswap V4 hook dispatcher:

function callHook(address hook, bytes memory data) internal returns (bytes memory) {

assembly {

let freeMemPtr := mload(0x40)

let success := delegatecall(

gas(),

hook,

add(data, 0x20),

mload(data),

0,

0

)

let size := returndatasize()

returndatacopy(freeMemPtr, 0, size)

switch success

case 0 { revert(freeMemPtr, size) }

default {

mstore(0x40, add(freeMemPtr, size))

return(freeMemPtr, size)

}

}

}

Why Yul: Fine control over gas forwarding, returndata handling।

Decision Tree

Need assembly?

├─ Gas bottleneck? (>10% of total gas)

│ └─ YES → Profile, then consider Yul

├─ Opcode not in Solidity?

│ └─ YES → Yul (extcodehash, mcopy, etc)

├─ Complex proxy logic?

│ └─ YES → Yul (delegatecall forwarding)

└─ Else → NO, stick to Solidity

Conclusion

Yul power tool है — सही use case में 20-40% gas savings possible।

लेकिन trade-offs हैं:

  • Readability down
  • Audit time up
  • Bug surface area bigger

Rule of thumb:

  • Library code (OpenZeppelin) → Yul OK
  • App logic → Solidity preferred
  • Gas-critical paths only → Profile first, then Yul

2026 में compiler improvements से Solidity-only code भी fast हो रहा है — premature assembly avoid करें।

लेकिन जब actual bottleneck मिले, Yul आपका best friend है। 🔧

Practice में लगाने के लिए तैयार हैं?

Solingo पर interactive exercises के साथ इन concepts को apply करें।

मुफ्त में शुरू करें