In our previous lesson, we explored how to structure relationships between smart contracts using inheritance, abstract contracts, and interfaces. This gave us the architectural tools to build modular and reusable systems. Now, we'll turn our focus inward to structuring the data within a single contract.
This lesson is about using structs and enums to model complex, domain-specific data. As someone with experience in business intelligence and data warehousing, you're familiar with the importance of well-defined data models. A struct in Solidity is much like a custom record type or a row definition in a database table, allowing you to group related fields together. An enum is like a controlled vocabulary or a dimension with a fixed set of values, ensuring data integrity and readability. Mastering these tools is essential for representing real-world assets, financial positions, or complex states, which are at the heart of the tokenized money use cases you're aiming to build.
By the end of this lesson, you will be able to define custom data types that make your contracts more organized, readable, and secure.
1. Grouping Data with Structs
In Solidity, primitive types like uint, bool, and address are the basic building blocks. However, real-world data is rarely that simple. For instance, representing a financial trade requires a ticker, quantity, price, and counterparty. Grouping these into a single logical unit is what structs are for. A struct is a user-defined type that can contain multiple variables of different types.
The video below offers a concise introduction to the syntax and concepts behind structs.
This video from Smart Contract Programmer clearly explains how to define, initialize, and interact with structs in Solidity.
Please watch the entire video, paying close attention to these key segments: Defining a Struct: See how a struct groups different data types. Initializing a Struct: Note the three different ways to create a struct instance. Storing Structs: Observe how to save struct instances into a state variable, like an array. Updating Structs: This is a crucial part. Focus on the distinction between the memory and storage keywords. Understanding this is vital for ensuring your changes are persistent on the blockchain. Deleting Struct Data: Learn how the delete keyword resets struct fields to their default values.
Now that you have a conceptual overview, let's walk through a practical example using the Remix IDE. The following reading will guide you through creating, deploying, and updating a struct that models a package shipment.
Understanding Structs and Enums in Solidity
This article provides a hands-on walkthrough of creating and manipulating structs. The example uses a Shipment struct, which is a great representation of a business process object.
Please read the first two parts of the article. You can even follow along in Remix if you like. Start at the top and read the section on declaration and initialization. Pay attention to the two different syntaxes for creating a struct variable. The image provided in the article shows exactly what this looks like in Remix. This image shows the Remix IDE with the Shipment struct defined and initialized as a public state variable purchase. Next, read the section on Updating Struct variables. This demonstrates how to write a function that takes new values and updates the state of your struct. This is a fundamental pattern for managing on-chain data.
The key takeaway is that when you want to modify a struct that is already saved in a contract's state (i.e., in storage), you need to load it into a storage pointer within your function. If you load it into memory, you're only changing a temporary copy, and your updates won't be saved.
2. Defining States with Enums
While structs group data, enums (enumerations) restrict a variable to a predefined set of named constants. This is incredibly useful for modeling states. For example, a loan can be Pending, Active, Repaid, or Defaulted. Using an enum instead of a uint (e.g., 0, 1, 2, 3) makes your code far more readable and less error-prone, as it prevents assigning invalid state values.
This video provides a quick and clear guide to using enums.
This video from the same channel explains how to declare enums and use them to manage state within a contract.
Watch these parts to understand the core functionality: Declaring an Enum: See how to define a set of named states. Using Enums in Functions: Learn how to set, get, and reset enum values. Demonstration: This shows how enums are represented as integers (starting from 0) when you interact with them in Remix.
3. Combining Structs, Enums, and Mappings
The true power of these features becomes apparent when you combine them. You can embed an enum within a struct to give your custom data type its own state machine. For instance, our Shipment struct can have a status field of type OrderStatus.
Furthermore, you can store these complex data structures in a mapping. A mapping(address => YourStruct) is one of the most common and powerful patterns in Solidity. It allows you to associate a complex data object with a specific user address, essentially creating a key-value database on-chain where the keys are addresses. This is the standard way to manage user-specific data, such as token balances, loan positions, or compliance status.
Let's return to our Shipment example to see how this works in practice.
Understanding Structs and Enums in Solidity
This guide continues by showing how to integrate enums and then store the combined struct in a mapping.
Please read the rest of the article, starting from the "How to declare an Enum" section: Read the sections on declaring and <tf start="Updating Enum variables" end="which is "Shipped".">updating enums. This reinforces what you saw in the video. Next, study the section Using an Enum in a Struct. This is the key integration step, where the bool delivered field is replaced with a more descriptive Status enum. Finally, read Storing in a mapping. This section puts everything together. It creates a mapping(address => Shipment) and modifies the update function to save data for msg.sender. This is a critical pattern you will use constantly. The image below, also from the article, shows the final result: interacting with the update function that now modifies a Shipment struct (containing an enum) stored in a mapping. The Remix interface for the final contract. The update function now takes inputs for a Shipment struct, which will be stored in a mapping keyed by the sender's address. Notice the status is represented by an integer (3), corresponding to an enum value.
4. Application in Tokenized Money
These data modeling tools are not just academic; they are fundamental to building the tokenization systems you're interested in. For example, in a crypto-collateralized stablecoin, the system must track each user's assets. A struct is the perfect tool for this.
The code snippet below is from a contract for a crypto-collateralized stablecoin. It defines a Vault struct to track each user's deposited collateral and outstanding debt. It then uses a mapping, vaults, to associate each user's address with their specific vault.
struct Vault {
uint256 collateralAmount;
uint256 debtAmount;
}
mapping(address => Vault) public vaults;
This is precisely the pattern you just learned. When a user deposits collateral or mints stablecoins, the contract's functions would update the collateralAmount and debtAmount fields within the Vault struct associated with their msg.sender address in the vaults mapping. This small example from a real-world use case shows how a clean data model using structs and mappings forms the backbone of a sophisticated DeFi protocol.
How to Build a Stablecoin in 2026 - Alchemy
This is a small section from a larger article on building stablecoins, but it contains a perfect example of a struct used to model a user's financial position.
Find the code block in the section "Oracle integration for crypto-collateralized systems". You don't need to worry about the Chainlink price feed parts for now. Focus on these two lines: the Vault struct definition and the vaults mapping. This demonstrates the exact pattern we've been discussing, applied directly to a tokenization use case.
Conclusion
In this lesson, you've learned how to create robust and readable data models inside your smart contracts. This moves you beyond simple variables to representing complex, real-world objects and states.
Here are the key takeaways:
- Structs are custom types used to group related variables, creating a logical data record (e.g., a
Shipment, aTrade, aVault). - Enums are used to define a finite set of named states, making your code more readable and preventing invalid state transitions (e.g.,
Pending,Shipped,Completed). - The pattern
mapping(address => YourStruct)is a cornerstone of Solidity development for managing user-specific data in a scalable and efficient way.
You now know how to structure your contracts (from the last lesson) and how to structure the data within them. The next logical step is to ensure the integrity of that data. In our next lesson, we will cover how to handle errors effectively using require, revert, and custom errors, which are the tools you'll use to validate data and enforce the rules of your contract.