There are various programming languages that can be compiled into EVM bytecode, there are high-level programming languages such as Solidity, Vyper, or Fe, but there’s also an intermediate programming language that’s often used within gas-optimized contracts called Yul, or as a developer, you can write EVM assembly by writing the EVM opcodes directly. A common technique for gas minimization is writing Solidity code looking at the resulting EVM assembly code and comparing the gas cost of different implementations in order to make the contract as gas-efficient as possible.
Yul is an intermediate-level programming language that can compile into EVM bytecode. From the Solidity documentation:
“The design of Yul tries to achieve several goals:
Programs written in Yul should be readable, even if the code is generated by a compiler from Solidity or another high-level language.
Control flow should be easy to understand to help in manual inspection, formal verification, and optimization.
The translation from Yul to bytecode should be as straightforward as possible.
Yul should be suitable for whole-program optimization.
In order to achieve the first and second goals, Yul provides high-level constructs like
switch statements and function calls. These should be sufficient for adequately representing the control flow for assembly programs. Therefore, no explicit statements for
JUMPI are provided, because the first two obfuscate the data flow and the last two obfuscate control flow. Furthermore, functional statements of the form
mul(add(x, y), 7) are preferred over pure opcode statements like
7 y x add mul because, in the first form, it is much easier to see which operand is used for which opcode.”
Yul also has an extension language called Yul+ which introduces a minimal transpiler and allows for more syntactic sugar when writing EVM Intermediate Language. Yul+ has now been archived, but for those looking to write optimized contracts Yul+ can help cut down on verbosity. Yul+ is notable for being the language in which the first Optimistic Rollup was written.
EVM Assembly can be written inside of inline Solidity statements with the
assembly keyword. It allows for more fine-grained control over the resulting bytecode. Oftentimes the compiler is unable to optimize Solidity code well and so it results in unnecessary gas costs.
There’s also an
unchecked keyword in Solidity which disables the overflow and underflow checks from the compiler which were introduced in Solidity 0.8.0 and before were part of the SafeMath library in OpenZeppelin libraries. The
unchecked keyword is oftentimes used.
Writing Yul or inline assembly can obfuscate the functionality of your code by making it less readable for other contributors/auditors and it can potentially introduce new risks as the Solidity compiler oftentimes performs various optimizations and security checks.
Good toolkits for writing EVM Assembly:
Huff is a low level language for the EVM. It’s as close as one can get to writing actual bytecode, but with syntatic sugar that greatly improves developer experience. Huff does not hide the inner workings of the EVM. Instead, Huff exposes its programming stack to the developer for manual manipulation. For that reason, it has a specially suited design for developing highly optimized smart contracts.
Huff was originally developed by the Aztec Protocol team to write Weierstrudel. Weierstrudel is an on-chain elliptical curve arithmetic library that requires incredibly optimized code that neither Solidity nor Yul could provide.
While EVM experts can use Huff to write highly-efficient smart contracts for use in production, it can also serve as a way for beginners to learn more about the EVM.
You can learn more about Huff from the official documentation.