Exploring Solidity Data Types: A Beginner Deep Dive on Uint vs Int

bowtiedoffsec

BowTiedOffSec

Posted on August 12, 2024

Exploring Solidity Data Types: A Beginner Deep Dive on Uint vs Int

Good morning everyone! Welcome to my first post!

I have little experience with markdown, so forgive me if the formatting is less than expert but here we go.

This morning while working my way through some coding projects on Solidity-By-Example (solidity-by-example.org), I found myself confused about a couple of data types and their applications. I thought to myself "Self, if you aren't following this entirely, odds are someone else has the same question or issue" and proceeded to put my Google and Chat GPT skills to work all morning to learn more about the topic.

I figured I would share some of my study notes and research with the community to see if I can not only cement things further for myself but maybe save some people some time and research in their own studies. This will be fairly entry-level for many out there but I am sure there are a few people just starting out that hopefully can steal these notes and take something away from them.

Solidity Primitive Data Types: Uint Vs Int

Let's start with some basics:

‘int and ’uint’ are used to store numbers in Solidity

Unsigned integers (Uint): Can only store non-negative integers (i.e., zero and positive numbers).

Signed Integers (int): can store positive or negative numbers
Let's keep in mind the basic definition of an integer is a positive or negative whole number including 0.

Different Sizes:

When you use Uint or int to store numbers in Solidity, you can choose how big or small they can be by specifying the number of bits. The more bits, the bigger the number you can store.

What are "bits"?

Think of bits as tiny boxes that can hold a piece of information. The more bits you have, the more information (or a bigger number) you can store. There is some behind-the-scenes math involved here but to stay on track I am going to summarize it as follows for now:

Examples:

uint8:

This is like having 8 tiny boxes.
It can store numbers from 0 to 255.

uint16:

This has 16 tiny boxes.
It can store numbers from 0 to 65,535.

uint256:

This has 256 tiny boxes, so it can store huge numbers from 0 to a number with 78 digits.

If you write int or uint without specifying a size in Solidity you end up with the following default sizes:

uint: The default size is uint256. (Huge numbers)

int: The default size is int256. This can hold both large positive and negative numbers.

But how do I know when to use which variable?

Use uint when:

You know the number will always be positive or zero.

For example, when counting items, storing ages, or tracking balances, where negative values don't make sense.

Use uint if you're dealing with non-negative numbers (it can store bigger numbers since it doesn't need space for negatives).

Use int when:

The number can be positive or negative.

For example, when dealing with temperatures, profit/loss calculations, or positions in a game, where negative values might be needed.

Use int if you need to include negative numbers.

Why Use Different Sizes of uint and int?

Saving Space and Gas Costs:

Ethereum Costs Money: On Ethereum, every action costs "gas," which is a fee you pay to perform operations. Storing data is one of these actions. When there is a lot of on-chain activity, sometimes gas is expensive so optimizing for gas use is important.

Smaller Data Saves Money: Smaller numbers (like uint8 or int8) use less space and therefore cost less gas compared to bigger numbers (like uint256 or int256).

Putting Data Together: When you declare a simple variable (like uint256 or int256), Solidity assigns it to a “storage slot”. Solidity can fit multiple small variables into one storage slot if they are small enough (Up to 256 bits total). If you have more than one variable, they each get their own slot unless they can be packed together. This is called "storage packing" and can save a lot of gas. I will look more into this later so as not to let my ADHD run us down yet another rabbit hole for now.

When to Use Different Sizes?

Smaller Sizes (uint8, int8, uint16, int16, etc.):

Use when: You’re working with numbers that won’t get very big.

Example: If you’re storing a person’s age and it probably won’t go over 100 with the odd exception, regardless uint8 should be enough because it can hold numbers up to 255 as mentioned above. I will use a simple 1 or 0 voting system below as an example.

Larger Sizes (uint256, int256):

Use when: You need to handle very large numbers, or you just want to keep things simple and gas optimization isn't a factor.

Example: We could use this to track the crypto balance of a trader, using uint256 in case they successfully ape their life savings into a meme coin and the balance gets really large! Sometimes it's fun to dream...

Example 1: Voting System

Scenario: You’re building a voting app where users vote "Yes" (1) or "No" (0).

In this case, you could use uint8 since the numbers will only be 0 or 1 so you do not need room to store large sizes.

Posted from Remix

Image description

What’s happening here?

uint8 public vote: This creates a variable called vote that can only store numbers between 0 and 255. We use uint8 because we only need to store either 0 or 1.

function castVote(uint8 _vote) public {}: This function lets a user cast their vote.

The require statement ensures that the vote can only be 0 (No) or 1 (Yes). If the user tries to vote with any other number, the transaction will fail.

Example 2: Token Balances of Meme coin master

Scenario: You’re building a system that tracks cryptocurrency balances for successful traders who routinely manage to crush the meme coin market
In the rare case you find a successful meme trader, you might use uint256 because balances could get very large. Again, it is fun to dream!

Posted from Remix:

Image description

What’s happening here?

uint256 public balance: This creates a variable called balance that can store very large numbers. We use uint256 because if the trader does well, token balances can grow large over time.

function setBalance(uint256 _balance) public {}: This function allows setting the balance to a specific amount.

function increaseBalance(uint256 _amount) public {}: This function adds a certain amount to the balance.

function decreaseBalance(uint256 _amount) public {}: This function subtracts a certain amount from the balance, but only if there’s enough balance to cover the subtraction. If not, the transaction fails and our meme master goes home.

Let's do one more example where we might need to think about storing numbers that could be positive or negative:

Example 3: Temperature Data

Scenario: You’re storing temperature readings, which can be both below and above zero. Where I am from here in Canada, I will likely use the below zero part more than I would like to admit! Regardless, in a case like this, you might use the int variable to ensure you can cover both positive and negative integers.

Specifically, I would likely use int8 to give me a small range, save on storage, and thus gas fees.

The int8 type in Solidity can hold values from -128 to +127. Since I am in Canada and we use Celisius as our metric, which should give me more than enough room...Well, let us hope anyway!

Posted from Remix:

Image description

What’s happening here?

int8 public temp: In the provided contract, the maximum temperature range that can be stored using the int8 type is from -128 to 127 degrees Celsius. As we discussed, this is more than enough room for our needs here.

function setTemperature(int8 _temp) public {}: This function allows setting the temperature to any value within the int8 range.

Security Implications

As an up-and-coming security researcher, one thing I am always on the lookout for is how certain variables might be exploited or what vulnerabilities can be introduced to a contract in the event they are improperly used. In doing some homework, I found a few examples where incorrect use of int or uint might lead to vulnerabilities. For this, I turned to chat GPT and did some research on Ethereum stack exchange which provided some answers and a bit of clarity on what to look out for.

Integer overflow/Underflow

While there are checks and balances in the newer versions of solidity, over and under-flow is still something you should be aware of when working with your contracts.

Overflow occurs when an operation results in a value larger than the maximum value the integer type can hold.

Underflow happens when an operation results in a value smaller than the minimum value the integer type can hold.

For example, if you have a uint8 with a maximum value of 255, adding 1 to it causes an overflow, potentially wrapping the value back to 0.

Suppose you’re implementing a token contract where users can transfer tokens. If the token balance is stored as a uint256 and someone tries to transfer more tokens than they have, a bug in the contract could cause an underflow, potentially setting the user's balance to a very high number instead of a negative or zero balance.

Since Solidity 0.8.0, overflow and underflow are checked by default, and an error (revert) is thrown if they occur. However, careful management of these issues is still crucial, especially when using older versions of Solidity or interacting with other contracts.

Mismanagement of Signed Integers (int):

If you use int (signed integers) inappropriately, it can lead to logical errors. For instance, accidentally assigning a negative value where only positive values are expected can cause unexpected behavior.

Imagine a voting contract where a variable to track votes is accidentally set as int. If a bug or an exploit allows a negative number of votes, it could invalidate the voting process or give incorrect results.

It is important to carefully choose between int and uint based on the expected range of values. For instance, if the variable should never be negative, use uint.

Use of int Where Only Positive Values are Expected:

Using int when you expect only positive values might open up the contract to unexpected edge cases, where a negative value could cause unexpected results.

In a token balance scenario, using int256 could accidentally allow balances to go negative, potentially leading to logical errors or unintended behavior.

Use uint for variables that should always be non-negative, such as balances, counters, or any quantity that should not be negative.

These are just a few examples of how certain variables can introduce contract vulnerabilities and barely scratch the surface of the issues that can come up through improper use. Remember, it is always important to have your contracts audited before deploying on mainnet.

Researching this helped me start to solidify some of the concepts I am currently learning about and hopefully, this added some value to some people out there who are also just getting started on their journey.
I will leave this post as a work in progress and I am open to clarification or editing as I learn more. If any dev's out there see any corrections I should make or have a comment to help add some clarity, feel free to reach out.

Thanks for taking the time to read this. If you enjoy this style of content and education, drop me a follow or leave a comment below. It is my intention to use this platform to build my knowledge of these concepts in public here and do a deep dive on topics I am learning more about, so you can bet there will be more!

Cheers All.

BTO

💖 💪 🙅 🚩
bowtiedoffsec
BowTiedOffSec

Posted on August 12, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related