Jeancvllr - EVM Assembly

Namespace

Quote from Solidity doc:

Different inline assembly blocks share no namespace, i.e. it is not possible to call a Yul function or access a Yul variable defined in a different inline assembly block.

assembly { 
    let x := 2
}
        
assembly {
    let y := x          // Error
}// DeclarationError: identifier not found
// let y := x
// ^

Basic Example

function addition(uint x, uint y) public pure returns (uint) {
     
    assembly {
        // Create a new variable `result`
        //     -> calculate the sum of `x + y` with the `add` opcode
        //     -> assign the value to `result`
        
        let result := add(x, y)   // x + y
        // Use the `mstore` opcode, to:
        //     -> store `result` in memory
        //     -> at memory address 0x0
        
        mstore(0x0, result)       // store result in memory
         
        // return 32 bytes from memory address 0x0
        
        return(0x0, 32)          
    
    }
}

In the inner working of the EVM, let performs the following:

  1. Creates a new stack slot

  2. The new slot is reserved for the variable.

  3. The slot is then automatically removed again when the end of the block is reached.

Variables declared with the let keyword are not visible outside the assembly block.

Literals

Literals are also written in the same way than in Solidity. However, string literals can be up to 32 characters:

assembly {
    let a := 0x123             // Hexadecimal
    let b := 42                // Decimal
    let c := "hello world"     // String
    
    let d := "very long string more than 32 bytes" // Error
} 
// TypeError: String literal too long (35 < 32)
// let d := "really long string more than 32 bytes"
//           ^------------------------------------^

Blocks and Scope

Variables follow the standard rule of block scoping. A block scope is defined by adding code between two curly braces { ... }.

In the example below, y and z are only visible in the scope they are defined in. So Scope 1 for y and Scope 2 for z:

assembly { 
    
    let x := 3          // x is visible everywhere
      
    // Scope 1           
    { 
        let y := x     // ok
    }  // y is "deallocated" here
    
    // Scope 2    
    {
        let z := y     // Error
    } // x is "deallocated" here
        
}
// DeclarationError: identifier not found
// let x:= y
// ^

Loops

This is a for loop in Solidity:

function for_loop_solidity(uint n, uint value) public pure returns(uint) {
         
    for ( uint i = 0; i < n; i++ ) {
        value = 2 * value;
    }
    return value;
}

This is its assembly equivalent:

function for_loop_assembly(uint n, uint value) public pure returns (uint) {
         
     assembly {
             
       for { let i := 0 } lt(i, n) { i := add(i, 1) } { 
           value := mul(2, value) 
       }
           
       mstore(0x0, value)
       return(0x0, 32)
           
   }
         
}

Note that lt(i, n) is a functional-style expression. In fact, everything in Solidity assembly must be written in functional.

There is no while loop in Solidity assembly, but similar effect can be achieved by for loop:

assembly {
    let x := 0
    let i := 0
    for { } lt(i, 0x100) { } {   // while(i < 256), 100 (hex) = 256
        x := add(x, mload(i))
        i := add(i, 0x20)
    }
}

Conditional Statements

Solidity inline assembly supports if statement to check conditions before executing code. However, there is no else part:

assembly {
    if slt(x, 0) { x := sub(0, x) }  // Ok
            
    if eq(value, 0) revert(0, 0)    // Error, curly braces needed
}

Curly braces for the body of the if statement are required.

EVM Assembly also includes switch statement. A switch statement takes the value of an expression and compares it to several constants. The branch corresponding to the matching constant is taken. You can also have a default case if none of the cases match:

assembly {
    let x := 0
    switch calldataload(4)
    case 0 {
        x := calldataload(0x24)
    }
    default {
        x := calldataload(0x44)
    }
    sstore(0, div(x, 2))
}

Functions

Last updated