Fuel Labs Inc. introduces Yul+ a new low-level language for Ethereum.

Today, Fuel Labs introduces Yul+, which adds various QoL features to Yul, a low-level intermediate language for the Ethereum Virtual Machine.

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 its initial 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 )

and ) 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!

https://yulp.fuel.sh

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