Advanced Solidity Datatypes Explained with Code Examples

Advanced Solidity Datatypes Explained with Code Examples

Let's Get Technical

🛑This is a long technical session, but if you manage to go through it you will come out a bad-ass solidity datatypes veteran!

Solidity is a statically typed language used for writing smart contracts and building decentralized applications (DApps) on the Ethereum blockchain. It offers a wide range of data types to handle different kinds of data. Understanding these data types is crucial for writing efficient and secure smart contracts. Here, we will explore the advanced datatypes in Solidity and provide code examples to illustrate their usage.

Let's start getting technical!

Value Types

Value types in Solidity store data directly in memory and are passed by value. But wait what is this memory thing?

In technical terms, memory in Solidity refers to a temporary storage area where variables are stored during the execution of a function or a contract. It is a part of the Ethereum Virtual Machine (EVM) and is separate from the storage area where variables are permanently stored on the blockchain.

Here are some key points about memory in Solidity:

  • Memory is used to store variables that are needed temporarily during the execution of a function or a contract.

  • When you define a local variable in Solidity, it is stored in memory and then pushed to the stack for execution.

  • Memory code is executed on the stack, which is a temporary storage area used by the EVM to bring data from other storage areas to work on them.

  • The stack has a maximum depth of 1024 elements and supports the word size of 256 bits.

  • Variables stored in memory are not stored simultaneously in memory and stack. Instead, they are moved from memory to the stack when needed for execution.

  • This separation between memory and stack is cost-efficient, as it allows the EVM to optimize the usage of resources.

It is important to note that memory is different from storage in Solidity:

  • Storage refers to variables that are permanently stored on the blockchain. State variables, which are variables declared outside of functions, are by default storage and written permanently to the blockchain.

  • Memory, on the other hand, is a temporary storage area used during the execution of a function or a contract.

  • Variables stored in storage can be accessed and modified by other contracts or external entities, while variables stored in memory are local to the function or contract and are not accessible from outside.

By understanding the concept of memory in Solidity, you can effectively manage the storage and manipulation of variables in your smart contracts.

So back to value types:

A. Integers: Solidity provides both signed and unsigned integers of various sizes. The uint type is used for unsigned integers, while the int type is used for signed integers. Examples:

uint256 public myUint = 100;
int8 public myInt = -10;

So if you are curios enough you probably starting to thing why do we need these typings for just a number variable? Perfect her is why,
In Solidity, the availability of different-sized integers, such as uint8, uint16, uint32, int8, int16, int32, etc., serves several important purposes:

  1. Efficient memory usage: By allowing developers to specify the size of integers, Solidity enables more efficient memory usage. For example, if you know that a variable will never exceed a certain range of values, you can use a smaller-sized integer to conserve memory. This can be particularly important in resource-constrained environments like blockchain networks.

  2. Optimization of gas costs: Gas is the measure of computational effort required to execute operations on the Ethereum network. By using integers with smaller sizes, you can reduce the gas costs associated with storing and manipulating these variables. This optimization can lead to more cost-effective smart contracts.

  3. Data validation and range restrictions: Different-sized integers allow developers to enforce specific range restrictions on variables. For example, if you have a variable that should only store values between 0 and 255, you can use uint8 to limit the range and prevent unintended values from being assigned.

  4. Interoperability and integration: Solidity is designed to interact with other programming languages and systems. By providing different-sized integers, Solidity aligns with the capabilities and conventions of other languages, making it easier to integrate with existing systems and ensure compatibility.

So You can now see the availability of different-sized integers in Solidity offers flexibility, efficiency, and control over memory usage and gas costs. It allows developers to optimize their smart contracts and tailor the variables to specific requirements, leading to more efficient and secure blockchain applications.

B. Boolean: The bool type represents a boolean value, which can be either true or false. I like to think that this is the most simple type to go by.

bool public isReady = true;

C. Addresses: The address type represents Ethereum account addresses. It can hold a 20-byte value and is used for interacting with other contracts or transferring balances.

address public myAddress = 0x1234567890123456789012345678901234567890;

Right, the address type serves as a key cornerstone in Solidity and the Ethereum Virtual Machine (EVM) overall. Here are some important points to understand about the address type:

  1. Functionality: The address type provides several built-in functions and members that allow you to interact with the account at a specific address. These functions include retrieving the balance of the address, sending funds to the address, and executing low-level calls to the address. These functionalities enable smart contracts to interact with other contracts or transfer balances securely.

  2. Readability and type safety: Using the address type instead of a generic uint160 or bytes20 provides improved readability and type safety in your smart contracts. It clearly communicates that the variable is intended to store an Ethereum address, making the code more understandable for other developers.

  3. Address types and inheritance: The address type serves as a base for all contracts. Contracts inherit certain members and functions from the address type, allowing you to access the functionality of the address type directly within your contracts. This inheritance simplifies the interaction with addresses and provides a consistent interface for address-related operations.

  4. Address vs. address payable: Solidity also provides the address payable type, which is identical to the address type but includes additional members such as transfer(...) and send(...). The address payable type is used specifically for addresses that can receive Ether, distinguishing them from plain addresses that are not designed to receive Ether. This differentiation ensures that Ether is sent only to addresses that are capable of handling it.

In short, the address type in Solidity is essential for interacting with Ethereum accounts, transferring balances, and executing low-level calls. It improves code readability, provides type safety, and enables powerful functionalities within smart contracts.

D. Enums: Enums in Solidity are user-defined data types that allow you to assign names to integral constants. They provide a way to define a set of related values and restrict a variable to have only one predefined value from that set. Enums are useful for creating custom data types with a limited set of options, making the code more readable, maintainable, and bug-resistant.

To define an enum in Solidity, you use the enum keyword followed by the enum name and a set of curly braces containing the values that the enum will contain. For example:

enum Color { RED, GREEN, BLUE }

In this example, the enum Color is defined with three possible values: RED, GREEN, and BLUE. Each value in the enum is automatically assigned an integer value starting from 0, so RED is assigned 0, GREEN is assigned 1, and BLUE is assigned 2.

You can declare variables of the enum type and assign them one of the predefined values. For example:

Color public myColor = Color.RED;

In this case, myColor is a variable of type Color and it is assigned the value RED from the Color enum.

Some key points to understand about enums in Solidity include:

  • Enums are treated as numbers internally, and Solidity automatically converts them to unsigned integers.

  • An enum should have at least one value in the enumerated list, and the values cannot be numbers or boolean values.

  • Enums can be declared at the file level, outside of contract or library definitions.

  • Enums can be used as function parameters and as keys in mappings.

Okay let's wrap it up, enums in Solidity provide a powerful and convenient way to define user-defined data types with a fixed set of values, making the code more expressive and reducing the chances of bugs. They are a fundamental tool in Solidity programming for creating custom data types and improving code organization and readability.

E. Bytes: The bytes type is used to store fixed-sized byte arrays, while the string type is used for dynamic-sized character arrays. Example:

bytes1 public myByte = 0x12;
string public myString = "Hello, World!";

Let's get a little bit technical with bytes.

  1. Representation at the low-level: In Solidity, bytes are represented as arrays of bytes, where each element of the array represents a single byte. The size of the byte array is fixed and specified by the bytes type declaration. For example, bytes1 represents a single byte, bytes2 represents two bytes, and so on.

  2. Storage and manipulation: Fixed-sized byte arrays, such as bytes1, bytes2, etc., are useful when you know the exact size of the data you want to store. We have talked about this earlier if you remember! Bytes typings provide efficient storage and manipulation of binary data. You can access individual bytes in the array using indexing, and you can perform bitwise operations, such as shifting and bitwise AND/OR, on the byte array.

  3. Conversion between bytes and string: Solidity provides built-in functions to convert between bytes and string. The bytes type can be converted to a string using the string typecast, and a string can be converted to bytes using the bytes typecast. However, it's important to note that the conversion from bytes to string and vice versa can be expensive in terms of gas costs, especially for large data sizes. Therefore, it's recommended to use bytes for binary data and string for textual data whenever possible.

So that was a bit of Value Types, now let us see how we can apply these types with more advanced typing cases. Yes, we are not done🥹.

  • -Let's get technical!*

Reference Types

Reference types in Solidity store reference to data and are passed by reference. They include:

  1. Arrays: Solidity supports both fixed-sized and dynamic-sized arrays. Arrays are used to store multiple values of the same type. Example:
uint256[] public myArray;

In the code snippet uint256[] public myArray; the myArray variable is declared as an array of type uint256. This means that myArray can store multiple values of type uint256.

To relate this to the section we saw above about the uint type, uint256 is a specific subtype of the uint type in Solidity. The uint type is used to represent unsigned integers, which are non-negative whole numbers. The uint256 subtype specifically represents unsigned integers with a size of 256 bits, allowing for a wider range of values to be stored.

By declaring myArray as an array of uint256, we are specifying that each element in the array can hold a value of type uint256. This allows us to store and manipulate multiple unsigned integer values within the array.

  1. Strings: Strings are used to store dynamic-sized character arrays. Example:
string[] public myDynamicArray;

In the code snippet string[] public myDynamicArray;, the myDynamicArray variable is declared as an array of type string. This means that myDynamicArray can store multiple values of type string.

To relate this to the section we saw earlier about bytes, the string type in Solidity is used to store dynamic-sized character arrays, similar to how the bytes type is used to store fixed-sized byte arrays. While bytes are used for binary data, string is specifically designed for storing textual data.

In the context of the code snippet, myDynamicArray is an array that can hold multiple string values. Each element in the array can store a dynamic-sized character array, allowing for flexibility in the length of the strings that can be stored.

It's important to note that strings in Solidity are stored as arrays of bytes internally. Each character in the string is represented by one or more bytes, depending on the character's encoding. Solidity provides built-in functions and operators to manipulate and access individual characters within a string.

  1. Structs: Structs allow you to define custom datatypes that can contain both value types and reference types. They are useful for organizing related data into a single unit. Example:
struct Person {
    string name;
    uint256 age;
}
Person public myPerson = Person("John", 30);

In the code snippet struct Person { string name; uint256 age; } Person public myPerson = Person("John", 30);, the Person struct is defined as a custom datatype that contains two fields: name of type string and age of type uint256.

Structs in Solidity allow you to create composite data types that can hold multiple values of different types. They are useful for organizing related data into a single unit. In this example, the Person struct represents a person's information, with the name field storing the person's name as a string and the age field storing the person's age as a uint256.

The myPerson variable is declared as a public variable of type Person. It is initialized with the values "John" for the name field and 30 for the age field. This allows you to create an instance of the Person struct and access its fields.

  1. Mappings: Mappings are key-value data structures similar to hash tables or dictionaries in other programming languages. They allow you to store and retrieve data based on a unique key. Example:
mapping(address => uint256) public balances;

In this example, the balances mapping is used to store and retrieve uint256 values based on unique address keys. Each address key is associated with a corresponding uint256 value. This allows you to keep track of balances or other data associated with specific addresses.

By declaring balances as a public mapping, it can be accessed and modified by other contracts or external entities. The public visibility modifier generates a getter function automatically, allowing other contracts or external entities to retrieve the values stored in the mapping.

Oh yeah, keep diving general🫡

Visibility Modifiers

In Solidity, visibility modifiers determine how functions and state variables can be accessed. They include:

  1. Public: Public functions and state variables can be accessed from any other contract or externally. Example:
uint256 public myVariable = 10;

function myFunction() public {
    // Function code here
}

First, the uint256 state variable myVariable is declared as public. This means that the value of myVariable can be accessed from any other contract or externally. The public visibility modifier automatically generates a getter function for myVariable, allowing other contracts or external entities to retrieve its value.

In this example, myVariable is initialized with the value 10. Since it is declared as public, its value can be read by other contracts or external entities using the generated getter function.

Next, the myFunction function is declared with the public visibility modifier. This means that the function can be called from any other contract or externally. The function code can be written within the function body.

By declaring a function as public, it can be accessed and called by other contracts or external entities. This allows for interaction with the contract and execution of the function's code.

  1. Private: Private functions and state variables can only be accessed from within the same contract. Example:
uint256 private myVariable = 10;

function myFunction() private {
    // Function code here
}

First, the uint256 state variable myVariable is declared as private. This means that the value of myVariable can only be accessed from within the same contract. Other contracts or external entities cannot directly access or read the value of myVariable.

In this example, myVariable is initialized with the value 10. Since it is declared as private, its value can only be accessed and modified within the same contract. This provides encapsulation and restricts direct access to the variable from external sources.

Next, the myFunction function is declared with the private visibility modifier. This means that the function can only be called from within the same contract. Other contracts or external entities cannot directly call or execute the myFunction function.

By declaring a function as private, it ensures that the function's code can only be executed within the same contract. This allows for encapsulation and restricts the visibility and accessibility of the function to the contract itself.

Are you getting the flow now right?

  1. Internal: Internal functions and state variables can only be accessed from within the same contract or contracts derived from it. Example:
uint256 internal myVariable = 10;

function myFunction() internal {
    // Function code here
}
  1. External: External functions can only be called from outside the contract. They cannot be accessed internally or from derived contracts. Example:
function myFunction() external {
    // Function code here
}

BONUS✨

Function Modifiers

Function modifiers are used to modify the behavior of functions. They include:

  1. View: The view modifier specifies that the function does not modify the contract's state. It is used for reading data from the blockchain. Example:
function myFunction() public view returns (uint256) {
    // Function code here
}
  1. Returns: The returns keyword is used to specify the return type of a function. Example:
function myFunction() public returns (uint256) {
    // Function code here
}

Okay, that's enough! , I am thinking of having a deep dive into the EVM and getting even more technical with it. See you in the next one🫡