Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 1

Solidity Value Types: uint, int, bool, address, and bytes

Recommended resources

Hello! Welcome to your first lesson in our course on building smart contracts for EVM-based chains. I'm excited to get started and guide you through your journey into the world of Decentralized Finance (DeFi).

Given your goal of digging deep into EVM internals and building robust DeFi applications, we'll start with the absolute fundamentals, but we'll immediately connect them to the low-level concepts that matter for security and gas optimization.

Introduction

In this lesson, you will learn to identify and use Solidity's core value types: uint, int, bool, address, and fixed-size bytes arrays.

These types are the atomic units of data you will manipulate in every smart contract. A solid understanding of how they behave is non-negotiable. We'll go beyond just what they are and explore how the Ethereum Virtual Machine (EVM) actually handles them. This will build a strong foundation for our later discussions on gas optimization and the EVM's execution model, which I know are key interests for you.

Since Solidity is a statically-typed language, the practice of declaring types for variables will be familiar from your software engineering background. Our focus will be on the specific types available in Solidity and their unique properties within the EVM.

Let's begin by understanding the distinction between value types and reference types.

Value Types | Solidity 0.8

This short video provides a clear overview of value types in Solidity, which are the main topic of our lesson today. It contrasts them with reference types, which we will cover in more detail later.

Please watch the first 51 seconds of this video from the 'Smart Contract Programmer' channel to get a concise definition of value types.

As the video explains, value types are variables that directly contain their data. When you pass them to a function or assign them to another variable, a copy of the data is created. This is in contrast to reference types (like arrays or structs), where a variable holds a reference (or pointer) to the data's location.

Now, let's explore the most common value types in detail.


Core Value Types in Solidity

We'll use the official Solidity documentation as our primary reference here. It's a resource you'll return to again and again as a smart contract developer.

Types — Solidity 0.8.34-develop documentation

The official Solidity documentation is the ultimate source of truth. We'll read the sections that define and explain booleans, integers, addresses, and fixed-size byte arrays.

Please read the following sections in the documentation. You can use the search function (Ctrl+F) on the page to find them easily: Booleans: Read the entire short section. Integers: Read this section to understand int and uint, their sizes, and available operators. Pay close attention to the part about checked arithmetic by default since Solidity 0.8. Address: Read about the address and address payable types. The details on transfer, send, and call are important, especially the warnings about the deprecation of transfer and send. Fixed-size byte arrays: Read this section to learn about bytes1 through bytes32.

Let's summarize and contextualize what you've just read.

Booleans (bool)

This is the most straightforward type, representing true or false. They are fundamental for control flow logic (e.g., if statements) and access control checks.

Integers (int and uint)

  • uint (unsigned integer): Can only hold non-negative values. This is the most common integer type in Solidity, used for token balances, timestamps, and counters.
  • int (signed integer): Can hold both positive and negative values.
  • Sizing: Both types come in various sizes, from uint8/int8 up to uint256/int256, in increments of 8 bits. If you just write uint or int, it's an alias for uint256 or int256, respectively. This is a crucial detail because the EVM is a 256-bit (32-byte) machine.
  • Checked Arithmetic: As of Solidity version 0.8.0, arithmetic operations are "checked" by default. This means if an operation results in an overflow (the value is too large for the type) or underflow (for uint, the value goes below zero), the transaction will automatically revert. This is a critical security feature that prevents a whole class of bugs common in older contracts.

Address (address)

  • The address type holds a 20-byte Ethereum account address.
  • There is a distinction between address and address payable. An address payable can receive Ether. You must explicitly convert an address to address payable using payable(address). This makes the intent to send funds clear in the code.
  • As noted in the documentation, the .transfer() and .send() methods for sending Ether are being deprecated. The modern, recommended approach is to use .call{value: amount}(""), which we will explore in detail in later lessons on contract interactions and security.

Fixed-size Byte Arrays (bytes1, bytes2, ..., bytes32)

These types are used to hold a sequence of bytes of a fixed length.

  • bytes1 holds one byte, bytes2 holds two, and so on, up to bytes32.
  • bytes32 is particularly useful and common. It's often used to store cryptographic hashes (like Keccak-256), which are 32 bytes long, or for efficient storage of short, ASCII-based strings.

Under the Hood: Value Types and EVM Storage

Now for the part that directly relates to your interest in EVM internals and gas optimization. To write efficient contracts, you need to understand how these types are physically laid out in the EVM's storage.

The EVM operates with a word size of 32 bytes (256 bits). Contract state variables are stored in a persistent key-value store called storage, which consists of numbered slots, each 32 bytes wide.

EVM 32-byte Word Representation
This image illustrates the EVM's 32-byte word size. Even a small number like `1` is stored in a full 32-byte slot when declared as a `uint256`.

This has a profound impact on gas costs. Writing to a storage slot is one of the most expensive operations in the EVM. A uint256 variable fits perfectly into one slot. But what about smaller types like uint128, bool, or bytes1? Using a full 32-byte slot for each would be incredibly wasteful.

This is where variable packing comes in.

Ethereum Virtual Machine Internals – Part 2

This article from NetSPI provides an excellent, detailed look at how the EVM handles contract storage. It's a perfect resource for digging into the low-level details you're interested in.

Please read the following sections: Start at Static Variable Storage and read through the subsection Simple Variable Storage & Packing. Continue with the next subsection, Integers. Focus on how the order and size of variables affect which storage slot they occupy. The concept of 'packing' is the key takeaway here.

As the article explains, Solidity is smart enough to pack multiple smaller variables into a single 32-byte storage slot if they are declared consecutively.

Consider this example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Unpacked {
    uint256 a; // Occupies slot 0 (32 bytes)
    uint256 b; // Occupies slot 1 (32 bytes)
}

contract Packed {
    uint128 a; // Occupies the first 16 bytes of slot 0
    uint128 b; // Occupies the second 16 bytes of slot 0
    bool c;    // Occupies 1 byte in slot 1
}
  • The Unpacked contract requires two full storage slots, meaning at least two expensive SSTORE operations to set both a and b.
  • The Packed contract fits both a and b into a single slot. If they are updated together, this can be done with a single SSTORE operation, saving a significant amount of gas. Notice that c starts a new slot because a and b have already filled slot 0.

This is your first and one of the most fundamental gas optimization techniques. By carefully ordering your state variables and choosing the smallest integer/bytes type that safely fits your data, you can dramatically reduce the storage footprint and gas costs of your contracts.


Quick Exercise

Let's put this knowledge to the test. Imagine you are defining the state variables for a simple contract. How would you order the following declarations to be as gas-efficient as possible?

  • uint256 mainId;
  • uint64 timestamp;
  • address owner;
  • bool isActive;

Think about the size of each type and how they might fit together in 32-byte slots. An address is 20 bytes, uint64 is 8 bytes, and bool is 1 byte.

Click for the answer and explanation

An optimal ordering would be:

address owner;    // 20 bytes
uint64 timestamp; // 8 bytes
bool isActive;    // 1 byte
uint256 mainId;   // 32 bytes

Explanation:

  • owner (20 bytes), timestamp (8 bytes), and isActive (1 byte) have a combined size of 29 bytes.
  • Solidity can pack all three into a single 32-byte storage slot (slot 0).
  • mainId is a full 32 bytes, so it will occupy its own slot (slot 1).
  • This layout uses only two storage slots, whereas a less optimal ordering might have used three or even four.

Conclusion

In this lesson, we introduced the fundamental value types in Solidity: bool, uint, int, address, and bytesN.

Here are the key takeaways:

  • Value types store their data directly and are passed by copy.
  • Solidity is statically typed, and uint256 is the standard integer size, aligning with the EVM's 32-byte word size.
  • Since version 0.8.0, Solidity provides default checked arithmetic to prevent integer overflows and underflows.
  • The EVM uses 32-byte storage slots to persist state variables.
  • Variable packing is a critical gas optimization technique where the compiler places multiple small variables into a single storage slot.

You've taken an important first step. You not only know what these types are but also have an initial understanding of how they are represented at a lower level, which is crucial for advanced development.

In the next lesson, we will continue building our foundation by exploring where these variables live. We will cover the difference between state variables, local variables, constants, and immutables, and what that means for gas costs and contract logic.