Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #5: Pengantar Yul — Bahasa Assembly Rahasia Solidity

OS
Open Soft Team

Engineering Team

Apa Itu Yul?

Yul adalah bahasa assembly intermediate yang dirancang untuk EVM. Ia berada di antara Solidity tingkat tinggi dan opcode EVM mentah. Anda bisa menggunakan Yul dalam dua cara:

  1. Inline assembly — Blok assembly { } di dalam Solidity
  2. Standalone Yul — File .yul yang dikompilasi langsung ke bytecode

Yul memberikan kontrol langsung atas opcode EVM sambil mempertahankan sintaks yang dapat dibaca. Tidak seperti Huff yang benar-benar mentah, Yul memiliki variabel, fungsi, dan kontrol alur terstruktur.

Sintaks Dasar Yul

assembly {
    // Variabel
    let x := 42
    let y := add(x, 1)  // y = 43
    
    // Memory
    mstore(0x00, x)     // Simpan x di memory[0x00]
    let z := mload(0x00) // Baca dari memory[0x00]
    
    // Storage
    sstore(0, x)        // Simpan x di storage slot 0
    let w := sload(0)   // Baca dari storage slot 0
    
    // Kontrol alur
    if gt(x, 0) {
        // x > 0
    }
    
    switch x
    case 0 { /* x == 0 */ }
    case 1 { /* x == 1 */ }
    default { /* lainnya */ }
    
    // Loop
    for { let i := 0 } lt(i, 10) { i := add(i, 1) } {
        // badan loop
    }
}

Variabel dan Tipe

Yul hanya memiliki satu tipe: u256 (unsigned 256-bit integer). Semua nilai adalah 32-byte word. Tidak ada string, array, struct, atau tipe kompleks lainnya.

assembly {
    let a := 1          // u256
    let b := 0xff       // u256
    let c := true       // 1 (u256)
    let d := false      // 0 (u256)
    // let s := "hello" // TIDAK VALID di Yul EVM
}

Boolean direpresentasikan sebagai 0 (false) dan non-zero (true). Alamat adalah u256 dengan 12 byte atas nol.

Mengakses Data Solidity dari Yul

Ketika menggunakan inline assembly di Solidity, Anda bisa mengakses variabel Solidity:

function example(uint256 input) external pure returns (uint256) {
    uint256 result;
    assembly {
        // Akses parameter fungsi
        result := add(input, 42)
    }
    return result;
}

Untuk variabel state (storage), Anda mengakses slot-nya:

uint256 public myValue;  // slot 0

function readValue() external view returns (uint256) {
    uint256 val;
    assembly {
        val := sload(0)  // Baca dari slot 0
    }
    return val;
}

Untuk mapping, Anda harus menghitung slot secara manual:

mapping(address => uint256) public balances;  // slot 0

function getBalance(address user) external view returns (uint256) {
    uint256 bal;
    assembly {
        // slot = keccak256(user . 0)
        mstore(0x00, user)
        mstore(0x20, 0)  // slot dasar mapping
        let slot := keccak256(0x00, 0x40)
        bal := sload(slot)
    }
    return bal;
}

Fungsi Yul

assembly {
    function safeAdd(a, b) -> result {
        result := add(a, b)
        if lt(result, a) {
            revert(0, 0)  // overflow
        }
    }
    
    function max(a, b) -> m {
        m := a
        if gt(b, a) {
            m := b
        }
    }
    
    let sum := safeAdd(100, 200)
    let bigger := max(sum, 500)
}

Fungsi Yul dikompilasi menjadi JUMP/JUMPDEST di bytecode — mirip dengan fungsi di Huff. Tidak ada overhead pemanggilan fungsi yang signifikan (8 gas untuk JUMP + 1 untuk JUMPDEST).

Kapan Menggunakan Inline Assembly

Inline assembly berguna ketika:

  1. Optimasi gas kritis — Hot path yang dipanggil jutaan kali
  2. Operasi yang tidak tersedia di SolidityRETURNDATASIZE, CHAINID, COINBASE
  3. Packing/unpacking data — Manipulasi bit yang lebih efisien
  4. Komputasi hash kustom — Keccak256 dengan layout memory manual
  5. Interaksi tingkat rendah — DELEGATECALL, CREATE2 dengan parameter spesifik

Contoh: Penghematan Gas Nyata

// Solidity murni: ~800 gas
function transferSolidity(address to, uint256 amount) external {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;
    balances[to] += amount;
}

// Dengan assembly: ~600 gas
function transferAssembly(address to, uint256 amount) external {
    assembly {
        // Hitung slot pengirim
        mstore(0x00, caller())
        mstore(0x20, 0)  // slot dasar balances
        let senderSlot := keccak256(0x00, 0x40)
        let senderBal := sload(senderSlot)
        
        // Check
        if lt(senderBal, amount) { revert(0, 0) }
        
        // Update pengirim
        sstore(senderSlot, sub(senderBal, amount))
        
        // Hitung slot penerima
        mstore(0x00, to)
        let recipientSlot := keccak256(0x00, 0x40)
        let recipientBal := sload(recipientSlot)
        
        // Update penerima
        sstore(recipientSlot, add(recipientBal, amount))
    }
}

Keselamatan dan Risiko Assembly

Inline assembly melewati banyak pemeriksaan keselamatan Solidity:

  • Tidak ada pemeriksaan overflow — Anda harus menambahkan sendiri
  • Tidak ada pemeriksaan tipe — Semua 32-byte word
  • Akses memory langsung — Bisa menimpa free memory pointer
  • Tidak ada reentrancy guard otomatis — Harus ditangani manual

Aturan emas: gunakan assembly hanya ketika penghematan gas terbukti signifikan melalui benchmarking.

Debugging Yul

Foundry mendukung debugging assembly:

# Trace eksekusi dengan opcode detail:
forge test -vvvv --match-test testTransferAssembly

# Gas snapshot:
forge snapshot

Anda juga bisa menggunakan forge debug untuk stepping melalui opcode satu per satu.

Kesimpulan

Yul adalah jembatan antara Solidity dan opcode EVM mentah. Ia memberikan kontrol yang cukup untuk optimasi gas serius sambil mempertahankan readability yang lebih baik dari Huff. Untuk sebagian besar kasus penggunaan optimasi, inline assembly Yul adalah pilihan yang tepat — cukup dekat dengan metal untuk menghemat gas, cukup tinggi levelnya untuk tetap maintainable.