EVM Memory and Storage Layout in Yul: If you’re diving into Ethereum Virtual Machine (EVM) development or looking to optimize your smart contracts, understanding memory and storage in Yul is crucial. Yul, a low-level programming language, provides developers with the tools to manipulate data directly within the Ethereum blockchain, helping to optimize gas costs and improve performance. In this article, we will explore the EVM memory and storage layout, the differences between memory and storage in EVM, working with memory pointers (mload
, mstore
, msize
), understanding storage access (sload
, sstore
), and how to optimize gas usage by making efficient use of memory and storage in Yul.
Whether you’re familiar with Yul Solidity, Yul language, or just starting out with Ethereum Virtual Machine concepts, this article will guide you through the essential elements and techniques to write efficient, low-cost smart contracts.
Table of Contents
What is Yul?
Yul is an intermediate language designed for Ethereum smart contracts. It is closely related to Solidity but operates at a lower level, giving developers more granular control over operations within the Ethereum Virtual Machine (EVM). Yul is especially powerful for gas optimization, providing advanced features for managing memory and storage that are not available in Solidity.
Although Yul is often compared to Huff and Solidity, it provides more control over execution, especially when working with memory management and EVM storage. Developers often use Yul code in advanced contract development, particularly when aiming to optimize gas costs.
Memory and Storage in the EVM: Core Concepts
Before diving into the specifics of memory and storage in Yul, it’s important to first understand the basic differences between memory and storage in the Ethereum Virtual Machine (EVM).
Memory
Memory in EVM is volatile and only exists during the execution of a transaction. Once the transaction ends, the memory is wiped clean. It’s cheaper to read and write to memory than to storage, making it ideal for temporary data storage, such as function arguments or intermediate results during contract execution.
Storage
Storage, on the other hand, is persistent and is used for long-term data storage. Data stored in EVM storage remains between transactions and across contract executions. Since storage is more expensive to interact with, it’s important to minimize the use of storage in smart contracts.
Key Differences Between Memory and Storage
Aspect | Memory | Storage |
---|---|---|
Persistence | Temporary (cleared after transaction) | Permanent (retained across transactions) |
Cost | Low Gas Usage | High Gas Usage |
Access Method | Linear and dynamic | Slot-based (32-byte slots) |
Typical Use | Intermediate data storage | State variables (long-term data) |
EVM Memory and Storage Layout
Understanding how EVM memory and EVM storage layout work is crucial for writing efficient smart contracts. In EVM, data is structured differently in memory and storage.
Memory Layout in EVM
Memory in the EVM is essentially a linear array of bytes. It is dynamic, meaning it can grow or shrink based on the needs of the contract. The free memory pointer tracks the next available memory slot, and memory is allocated based on function calls and temporary data.
In Yul, developers interact directly with memory through commands like mstore
, mload
, and msize
:
mstore(offset, value)
: Stores 32 bytes of data at the specified memory offset.mload(offset)
: Loads 32 bytes from the specified memory offset.msize()
: Returns the current size of allocated memory.
Memory operations are cheaper than storage operations, so it’s important to use memory for temporary variables and data during the execution of a contract.
Storage Layout in EVM
EVM storage is more permanent and is organized into 32-byte slots. Each contract has its own storage layout that consists of state variables and other persistent data. The key-value storage model ensures that the data is retained across transactions.
In Yul, developers interact with storage using the sstore
and sload
instructions:
sstore(slot, value)
: Stores a value at a specific storage slot.sload(slot)
: Retrieves a value from a specific storage slot.
Since EVM storage is more expensive to read from and write to than memory, efficient use of storage is vital to keep gas costs low.
Working with Memory Pointers in Yul
In Yul, manipulating memory pointers is a critical aspect of managing data efficiently. The free memory pointer helps keep track of where new data can be written in memory, while also ensuring that memory allocations are done correctly.
Practical Examples in Yul
Let’s explore some practical examples to understand how memory and storage work in Yul.
Example 1: Using Memory in Yul
function storeInMemory() -> result {
let freePointer := mload(0x40) // Load free memory pointer
mstore(freePointer, 42) // Store value 42 in memory
result := mload(freePointer) // Retrieve the value
}
Example 2: Efficient Storage Access in Yul
function storeInStorage(slot, value) {
sstore(slot, value) // Store the value in the specified storage slot
}
function loadFromStorage(slot) -> value {
value := sload(slot) // Load the value from the specified storage slot
}
Memory Pointer Operations in Yul
Using mload
and mstore
, developers can read from and write to specific memory addresses. Here’s how you can use these functions in Yul:
Example 1: Storing and Loading Data from Memory
function storeInMemory(offset, value) {
mstore(offset, value) // Store a value at the specified memory offset
let loadedValue := mload(offset) // Load the value from memory at the same offset
}
Example 2: Dynamic Memory Allocation
function allocateMemory(size) -> ptr {
ptr := msize() // Get the current memory size
mstore(ptr, size) // Store size in the allocated memory
mstore(add(ptr, 32), 0x1234) // Store additional data after the size
}
By using memory pointers efficiently, developers can create more gas-efficient Yul code, reducing unnecessary memory usage and making the contract more cost-effective.
Understanding Storage Access in Yul
While memory operations in Yul are relatively inexpensive, storage access is more costly. This is because EVM storage operations, such as reading and writing to storage, consume significantly more gas.
Storage Access Instructions in Yul
To interact with EVM storage in Yul, developers use sload
and sstore
. Here’s a breakdown of how these instructions work:
Example 1: Storing Data in Storage
function updateStorage(slot, value) {
sstore(slot, value) // Store the value in the specified storage slot
}
Example 2: Reading Data from Storage
function readStorage(slot) -> value {
value := sload(slot) // Retrieve the value from the specified storage slot
}
Minimizing Storage Writes
Because storage writes are so costly, it’s important to minimize unnecessary EVM storage interactions. Developers should store only necessary state variables in storage and use memory for transient data. This practice helps to reduce gas costs and optimize contract execution.
Gas Optimization: Efficient Memory and Storage Usage
Optimizing gas costs is one of the key reasons to use Yul. By carefully managing memory and storage, you can significantly reduce the cost of contract execution.
1. Use Memory for Temporary Data
Whenever possible, use memory to store intermediate variables and computation results. This will reduce the number of storage reads and writes.
function optimizedComputation(a, b) -> result {
let temp := add(a, b) // Perform addition in memory
result := temp // Return the result
}
2. Minimize Unnecessary Storage Updates
Avoid updating storage unless absolutely necessary. For instance, only update a storage slot if its value has changed, which can be done with a simple conditional check:
function conditionalUpdate(slot, newValue) {
let currentValue := sload(slot)
if iszero(eq(currentValue, newValue)) {
sstore(slot, newValue) // Update only if the value has changed
}
}
3. Batch Storage Operations
When multiple storage updates are necessary, batch them together to reduce gas usage.
function batchUpdate(startSlot, values) {
for { let i := 0 } lt(i, mload(values)) { i := add(i, 1) } {
sstore(add(startSlot, i), mload(add(values, mul(i, 32))))
}
}
By batching updates, you minimize the number of EVM storage transactions, saving gas.
Optimizing Gas by Efficient Memory and Storage Usage
Gas optimization is a critical aspect of Ethereum development, and Yul provides a robust framework to achieve this.
Tips for Efficient Memory Usage:
- Avoid Redundant Memory Allocation: Use the free memory pointer effectively to minimize wastage.
- Batch Operations: Group memory operations together to reduce gas overhead.
- Leverage Inline Assembly: Use
Yul Assembly in Solidity
for tasks requiring precision and speed.
Tips for Efficient Storage Usage:
- Pack Variables: Store multiple variables in a single slot when possible.
- Minimize Storage Access: Accessing storage is expensive; cache values in memory when possible.
- SLOAD and SSTORE: Use these instructions sparingly, as they are among the costliest operations in the EVM.
Advanced Yul Techniques
As you advance in your Yul development journey, you’ll encounter more sophisticated techniques to make your contracts even more gas-efficient.
Safely Reverting Transactions
In Yul, you can use the revert
instruction to safely revert a transaction when a certain condition is met.
function safeOperation(condition) {
if iszero(condition) {
revert(0, 0) // Revert the transaction if the condition is false
}
}
Working with Arrays
Arrays in Yul can be managed efficiently with dynamic memory allocations. Here’s how you can allocate memory for an array and store values dynamically:
function createArray(size) -> ptr {
ptr := msize() // Get the current memory size
for { let i := 0 } lt(i, size) { i := add(i, 1) } {
mstore(add(ptr, mul(i, 32)), i) // Store values in the array
}
}
Efficient array management helps you create contracts that handle large datasets without excessive gas costs.
Advanced Yul Programming Techniques
Yul offers several advanced features that allow developers to optimize their contracts further.
Playing with Yul Assembly
Writing assembly-safe code ensures that your contracts are both gas-efficient and secure. Utilize memory-safe patterns and double-check pointers for potential vulnerabilities.
Yul Programming Language Features:
- Intermediate Representation: Serves as a common ground for multiple high-level languages.
- Minimal Syntax: Simplifies the process of writing low-level code.
- EVM-Specific: Provides native support for EVM operations like
sstore
,sload
,mstore
, andmload
.
Key Applications of Yul in Ethereum Development
Smart Contract Storage Layout
Understanding the Smart Contract Storage Layout is essential for managing state variables efficiently. Tools like Hardhat-storage layout help visualize and debug storage allocations.
Gas Optimization Strategies
Use Yul for operations like:
- Efficient Memory Pointers: Reduce unnecessary memory usage.
- Packed Variables: Optimize how variables are stored in storage.
- Custom Load/Store Instructions: Implement tailored instructions to save gas.
FAQs
What is the difference between storage and memory in EVM?
Memory is temporary and cheaper, while storage is persistent and expensive.
What is EVM memory?
EVM memory is a volatile storage area used during contract execution.
What is the EVM storage model?
The storage model in the EVM uses 32-byte slots for storing contract state variables.
How is data stored in EVM?
Data is stored in a key-value format, with keys representing storage slots.
What is a free memory pointer in Solidity?
It’s a pointer that indicates the next available space in memory.
Conclusion
Mastering memory and storage in Yul is crucial for writing gas-efficient smart contracts on the Ethereum blockchain. By understanding the differences between memory and storage, utilizing memory pointers effectively, and optimizing gas usage through efficient storage layout and batch operations, developers can create contracts that are both cost-effective and high-performing.
Whether you’re just getting started with Yul language or you’re already familiar with Solidity, taking the time to learn and optimize your memory and storage usage in EVM can help you build more efficient and scalable contracts. By following the best practices outlined in this article, you’ll be well on your way to mastering Ethereum Virtual Machine development.