← Index

Introduction to Solidity inline assembly (Yul)

Yul is the low-level language used in Solidity inline assembly. (official documentation)

In a Solidity smart contract, Yul is always written within an inline assembly block.

contract YulExample {
    function add() external {
        assembly {
            let x := 1
            let y := 2
            
            let result := add(x,y)
        }
    }
}

You can reference variables declared in the function's arguments, return values and body.

contract YulExample {
    function add(uint256 value) external view returns (uint256 ret) {
        assembly {
            let x := 1
            ret := add(x,value)
        }
    }
}

You can reference variables declared in the contract storage with the .slot or .offset suffixes. These are used to determine the storage location of the value.

contract YulExample {
    // Slot-0: 0x-[A-32-bytes]
    uint256 public A;
    
    // Slot-1: 0x-[empty-1-byte][E-1-byte][D-2-bytes][C-12-bytes][B-16-bytes]
    uint128 public B;
    uint96 public C;
    uint16 public D;
    uint8 public E;
    
    function add_D(uint256 val) external returns (uint256 ret) {
        assembly {
            let slot := D.slot // slot-1 packs B,C,D,E
            
            // 0x-[empty-1-byte][E-1-byte][D-2-bytes][C-12-bytes][B-16-bytes]
            let current := sload(slot)
            
            // \`D.offset\` is 28-bytes (B.len + C.len)
            let dOffsetBits := mul(D.offset, 8)
            
            // 0x-[empty-2-bytes][full-2-bytes][empty-28-bytes]
            let dMask := shl(dOffsetBits, 0xffff)
            // 0x-[empty-1-byte][full-1-byte][empty-2-bytes][full-28-bytes]
            let notDMask := not(dMask)
            
            // 0x-[empty-2-bytes][D-2-bytes][empty-28-bytes]
            let d := and(dMask, current)
            // 0x-[empty-1-byte][E-1-byte][empty-2-bytes][C-12-bytes][B-16-bytes]
            let notd := and(notDMask, current)
            
            // 0x-[empty-30-bytes][New-D-2-bytes]
            ret := and(add(shr(dOffsetBits, d), val), 0xffff)
            
            // 0x-[empty-1-byte][E-1-byte][New-D-2-bytes][C-12-bytes][B-16-bytes]
            let newVal := or(notd, shl(dOffsetBits, ret))
            
            sstore(slot, newVal)
        }
    }
}

You can write if-statements (no else-statements) and for-loops.

contract YulExample {    
    function isPrime(uint256 num) external pure returns (bool ret) {
        assert(num > 0);
        
        ret = true;
        assembly {
            let x := num
            let halfX := add(div(x, 2), 1)
            
            for { let i := 2 } lt(i, halfX) { i := add(i,1) }
            {
                if iszero(mod(x, i)) {
                    ret := 0
                    break
                }
            }
        }
    }
}

Within assembly blocks, value assignments via := colon-equals manipulate the stack, and you manipulate memory and storage by invoking opcodes directly.