Huff vs Yul for EVM Smart Contracts

Jtriley.eth
4 min readJun 1, 2022

--

You might have heard of the infamous “Yul” assembly language for the Ethereum Virtual Machine (EVM). But what if I told you there was a language even lower level than that?

Overview

Yul is the assembly language of choice for many smart contract engineers looking for lower level control in the EVM. You can even write it in line with Solidity code using assembly { } code blocks!

Use cases for this include extreme gas optimizations, proxy contract patterns, and deterministic address contract deployments.

Huff, on the other hand, originated from the Aztec team where contracts needed extreme efficiency, even more than what Yul could provide. The Huff language offers the most gas efficient and optimized smart contracts that can be written without just writing raw EVM bytecode. This has limited use in production, but is a fantastic tool for learning the EVM on a low level.

Read and Write

While “hello world” is a timeless nerd’s classic, it is not the most practical for EVM development, so instead, we’ll create a simple program to read and write a single uint256 to storage.

The interface of the contract will be as follows.

function set(uint256 value) external;
function get() external view returns (uint256 value);

Yul

The following is an example of an entire contract written in Yul that can read from and write to storage.

Juicy.

In short, this runtime code extracts the 4 byte selector from the call data, checks it against two functions. If set is called, then we store the first argument in storage slot 0x00. If get is called, then we load from slot 0x00 the value, store it in memory, and return it.

Huff

Note that the // [values] comments are not necessary, they just indicate what the stack looks like after each instruction for readability. This makes things easier to reason about and it appears in Huff codebases often.

In this code, we define GET and SET macros, which can be accessed in the MAIN macro, which is our program’s entry point. We do the same as in the Yul code, extracting the selector from the call data, then jumping to the appropriate macro if the selector matches.

Bytecode

Now for the compiler output comparison. This examines only the size of the output. The gas costs will be analyzed in the next section.

Yul

This is the deployment bytecode from the Yul program (no optimizations).

6053600d60003960536000f3fe60056041565b6360fe47b18114601e57636d4ce63c8114602c57600080fd5b6004356027604d565b55603c565b6032604d565b5460005260206000f35b506052565b6000803560e01c905090565b600090565b

96 bytes!

Huff

This is the deployment bytecode from the Huff program (no optimizations).

61002f8061000d6000396000f360003560e01c806360fe47b11461001c5780636d4ce63c14610023575b6004356000555b60005460005260206000f3

Only 60 bytes!

Gas Testing

The gas tests will be run using the following template with Foundry.

This code asserts the read and write functions work on both contracts and it logs the gas consumed by each when the test is run with verbosity level two. The following is the command.

forge test -vv

Here is the output.

Running 4 tests for test/MyToken.t.sol:ReadWriteTest
[PASS] testHuffGet() (gas: 9430)
Logs:
huff.get(): 7202
[PASS] testHuffSet() (gas: 30120)
Logs:
huff.set(uint256): 27178
[PASS] testYulGet() (gas: 9537)
Logs:
yul.get(): 7286
[PASS] testYulSet() (gas: 30230)
Logs:
yul.set(uint256): 27247
Test result: ok. 4 passed; 0 failed; finished in 4.49ms

Ignore the lines such as [PASS] testHuffget() (gas: 9452), these are the full gas consumption of the test itself. Instead, what we’re interested in is these values.

huff.get(): 7202
huff.set(uint256): 27178
yul.get(): 7286
yul.set(uint256): 27247

The Huff contract consumes 84 less gas on a read and 69 less gas on an empty storage slot write!

Conclusion

To summarize, while comparing Huff and Yul seems to have minimal effects on such a simple contract, the extra control Huff gives the developer opens the door to extreme optimization at the expense of managing jumps and the stack without a high level syntax.

Huff may very well be used in production for contracts demanding the most extreme optimizations, but it is best used as a tool for learning low level EVM programming. These optimizations will help to understand more deeply what the EVM does, what abstractions Yul brings, and what ergonomics and safety features Solidity brings.

If you liked this and want to learn more about the Huff language, check out the Twitter and Discord server!

Good hacking 🤘

--

--