# 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 है। 🔧