Introducing: Yul+ — A new low-level language for Ethereum

Yul is an incredible little language written by the Solidity Developers as a compilation target for further optimizations. It features a simplistic and functional low-level grammar. It allows the developer to get much closer to raw EVM than Solidity, and with that comes the promise to drastically improved gas usage.

Fuel Labs has implemented itsinitial open-beta optimistic rollup contract largely with Yul, but we noticed that with the addition of even a tiny number of basic language additions, our code could become more legible and efficient.

Yul+ can be looked at as an experimental upgrade to Yul, and Yul might aim to integrate some of its features natively at a later time.

Some Yul Basics

A basic Yul contract with a constructor and runtime

object "EmptyContract" {
  code {
  
    // Your constructor code
    
    datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
    return(0, datasize("Runtime"))
  }
  object "Runtime" {
    code {
    
       // Your runtime code
       
    }
  }
}

Handling calldata

// copy calldata to memory
// this copies 36 bytes of transaction calldata to memory position 0

calldatacopy(0, 0, 36)

Managing memory

// store and read memory
// store 0xaa at memory position 100

mstore(100, 0xaa)

// load 32 byte chunk from memory position 100 and assign to someVar

let someVar := mload(100)

Hashing

// hash memory position 0 to 0+32, assign result to someHash

let someHash := keccak256(0, 32)

State storage

// store value 0xaa in state storage slot 3

sstore(3, 0xaa)

// get value from state storage 3 and assign to someVar

let someVar := sload(3)

Functions, conditions, loops, and switches

// Functions and conditions

function someMethod(someVar, someOther) -> someResult {
   if eq(someVar, someOther) {
      someResult := 0x45
   }
}

// Loops

for { let i := 0 } lt(i, 100) { i := add(i, 1) } {
   // some loop code
}

// Switches

switch someVar
case 0 {
   // when someVar == 0
}

case 1 {
   // when someVar == 1
}

default {
   // default
}

Yul+ Features

  • All existing Yul language features
  • Enums (enum)
  • Constants (const)
  • Ethereum standard ABI signature generation (sig”function …”)
  • Booleans (true, false)
  • Safe math by default (i.e. over/under flow protection for addition, subtraction, multiplication)
  • Injected methods (mslice and require)
  • Memory structures (mstruct)

Usage

Enums, constants, and Booleans

enum Colors (
   Red, // 0
   Blue, // 1
   Green // 2
)

// Constant someConst will equal 1

const someColor := Colors.Blue

// Constant someBool will equal 0x1

const someBool := true

Ethereum standard ABI signature generation for method sigs and topics:

// someVar will equal 4 byte method signature 0x6057361d

let someVar := sig”function store(uint256 val)”

// someTopic will equal 32 byte topic hash 0x69404ebde4a368ae324ed310becfefc3edfe9e5ebca74464e37ffffd8309a3c1

let someTopic := topic”event Store(uint256 val)”

All maths are now safe by default, which can be disabled in the compiler if desired.

let someVar := add(3, sub(4, 2))

// will compile to this, with safeAdd, safeSub methods injected

let someVar := safeAdd(3, safeSub(4, 2))

We add for convenience a memory slice mslice and require if true

mstore(300, 0xaabbccdd) // note, mstore left pads zeros by 28 bytes

let someVal := mslice(328, 3) // will return 0xaabbcc

require(gt(someVal, 0)) // someVal > 0 or revert(0, 0) nicely

Lastly, we enable memory structures. These are used to describe already-existing structures in memory, such as calldata, hash data, or any data with structure written to memory.

It offers a wide range of positioning, offset, hashing, indexing, and organizational features to better handle memory with neat efficient pre-made functions injected on-demand. We still keep to using a functional notation of injected functions, which doesn’t break existing Yul grammer style.

// Let’s assume we assign some calldata to memory position 0

// this describes an abstract memory construction:

mstruct SomeCalldata(
   signature: 4,
   value: 32,
)

let methodSig := SomeCalldata.signature(0) // slices out sig
let someVal := SomeCalldata.value(0) // slices out value

// we also get some nice indexing and offset features

SomeCalldata.value.position(0) // equals 4 (i.e. 0 + 4)

// Index ordering values as well

SomeCalldata.signature.index() // equals 0

SomeCalldata.value.index() // equal 1

// Keccak hashing

SomeCalldata.value.keccak256(0) // equals 32 byte hash of value

// Calculate entire size of calldata structure

SomeCalldata.size(0) // equals 36 (i.e. 4 + 32)

Example: Yul+ SimpleStore Contract

object “SimpleStore” {
   code {
      datacopy(0, dataoffset(“Runtime”), datasize(“Runtime”))
      return(0, datasize(“Runtime”))
   }
   object “Runtime” {
      code {
         calldatacopy(0, 0, 36) // copy calldata into memory
         
         mstruct Calldata( // mstruct describes calldata
            sig: 4,
            val: 32
         )
         
         switch Calldata.sig(0) // get signature at positive zero
         
         case sig”function store(uint256 val)” { // store method
            sstore(0, Calldata.val(0))
         }
         
         case sig”function get() returns (uint256)” { // get method
            mstore(100, sload(0))
            
            return (100, 32)
         }
      }
   }
}

Try it Now in Your Browser!

Yul+ - Low-Level Ethereum Devepment
Fuel is a trustless scalable Ethereum side-chain implimentation which can quadratically scale to 2 million TPS.

Wrapping Up

In conclusion, the Fuel Labs team hopes to expand the possibilities for the Ethereum Virtual Machine by creating more low-level alternatives which we use everyday to build high-performance optimistic rollup scalability for the ecosystem.

In the meantime, for more info and to keep up to date with our work:

Website: https://fuel.sh

Twitter: https://twitter.com/FuelLabs_

GitHub: https://github.com/FuelLabs/yulp

Gitcoin: https://gitcoin.co/grants/199/fuel-labs