Diving Deeper Into Solidity

Ifeoluwaolubo
Coinmonks
10 min readOct 14, 2023

--

Deeper into solidity

Before we actually start writing smart contracts that simulates the different applications we might want to build in the real world, there are still some fundamental ideas in solidity that we’ll be needing to understand.

In my previous article, we started from having no knowledge of solidity whatsoever, to actually writing some solidity codes, understanding some basic types in solidity, using an online editor (remix) to quickly and easily write and test our solidity codes and then ultimately deploying our smart contracts and interacting with them, but there are still some concepts we need to know before we start working with solidity in the real world. You can find the link to the previous article here ⬇️:

We’ll still stick to using remix editor in this article cause I’ll prefer us understanding just the new concepts rather than trying to grasp a new environment, and with that said, Let’s get started.

More solidity types

We are already familiar with some solidity types (eg uint, bool, string and addresses) but there are some more advanced types that we must know and what other way to learn new things than building an application with it. Thats what we intend to do, we’ll be building a SimpleStorage contract to get us familiar with these advanced types.

Once again head over to your remix editor at https://remix.ethereum.org/ if you want to follow along, then create a new file called SimpleStorage.sol inside of your contracts folder. You can create a new file by clicking on the contracts folder and clicking the create new file icon like below.

Create a new solidity file

Inside the file, we would just have some boiler plate code for our Simple Storage contract. We created a contract called SimpleStorage and it currently has one public variable which stores whatever number was inputted by whoever deployed the contract.

Simple storage boiler plate

Arrays:
Supposed our contract was opened to the outside world in the sense that other people can change the value stored in the owner number and lets say we want to have a way to store the addresses of people that have changed the owner number, then one way to do that is by creating a store function which takes a number as an argument, sets it to our ownerNumber variable and then we also store the address of whoever called the function in a type called an Array.

Here we created a dynamic array called storedAddresses that adds the address of the user calling the store function into it. Like Javascript, we can add elements into solidity dynamic arrays using the push method like we see in the code above. There are 2 types of arrays in solidity, the dynamic array and the static arrays. The number of elements in a static array is fixed while there is no limit to the number of elements we can add into the dynamic array. Arrays can be used to store a collection of elements that are of the same type. In our case, we’re storing a collection of addresses.

In Solidity, you cannot push or add elements to a static array because a static array has a fixed size defined at compile time, and this size cannot be changed. Static arrays are meant to have a fixed, predetermined number of elements that you specify at the time of declaration, and you cannot modify this size after deployment.

Let’s test our contract. Click on the deploy and run transactions button and pass an initial value of 675 as the _ownerNumber

Once our contract is deployed, we can see that the value of our _ownerNumber is 675

Select a different account and call the store function with this account. This should store the address and change the value of _ownerNumber variable.

As you can see above, we can access the elements in an array by using its index. Just like Javascript, the indexing starts at 0.

Maps:
Our contract isn’t doing much right now cause it doesn’t really make sense that anyone should have the ability to change the number set by whoever deployed the contract. Let’s modify our contract so each user has their own number stored. We could create a new array that stores their numbers, then whenever we want to find the number for a particular address we’ll have to loop through the array and get the value.

Although this could potential work, it’s a really bad idea because of the gas that would be spent for processing that transaction and we’ll talk more about this soon, but for right now, we want to try as much as possible to avoid an computationally heavy transactions, hence “a for” is a very bad approach.

Time complexity for searching in an array is O(n)
What this means is for the worst case, we’ll have to loop through every element in the array before finding the value we’re interested in.

This problem is easily solved by another data type called the Map. A map is basically a key value store (similar to javascript), but solidity maps uses the hashmap datatype. What this means is that we cannot get all the keys of a Map and we cannot get all the values of a map. A key only points to the value stored and that’s it.

Time complexity for searching in a map is O(1) or constant time

Let’s now change our code to use maps instead

Mapping in solidity‘

We create maps by using the mapping keyword. Notice the 2 variations of maps in the code. Essentially they do the same thing, but the second variation is what I prefer since it gives me more details on what the type holds (address stores the user’s address and the uint stores the user’s number). Lets deploy our contract again

We set the value of 89766 to the ownerNumber when deploying the contract, then stored a new number into the addressToNumber variable. We get the user’s address then use it to find their number.

Structs:
What if we want to make our contract more robust so that instead of storing just the user’s address, we would store their name, number and address. Solidity provides a way for us to define custom types and use in our contract, so that’s exactly what we’ll be doing.

Struct type definition

Here we created our custom type called Person using the struct keyword. Our Person type has a name, number and _address. Then on line 16, we are basically saying we want an array of elements called people where each element is a person type. Let’s see how we would go about using this new type.

We created a public addPerson function that takes two arguments, name and number, creates the person using these arguments (with the address) then pushes them to the people array. We might be unfamiliar with the memory keyword as of now, but we’ll get to that in a bit. Once again, lets test our code. Deploy your contract and set any number to the ownerNumber variable, I’ll be using 123.

After deploying the contract, I added a new person with the name Ifeoluwa (in quotes) and a number 43, then the people array at index 0 now returns the appropriate results as expected.

We’re about done with the basic functionalities of our contract, but there are still a few concepts I want to talk about.

Memory, Storage and Calldata

Earlier in our addPerson function, we used the memory keyword in the string name and in the Person type. Just what exactly is this doing?

First let’s talk about the storage keyword. A storage variable is a variable that’s stored on the blockchain itself (they cause the state of the blockchain to change). Every variable defined in the top level of our contract is a storage variable, so in essence ownerNumber, storedAddresses, addressToNumbers and people variables are all storage variables.

The memory and calldata keyword works almost the same way. The big difference between them and the storage keyword is that, they are used as a short term storage and only exist for the duration of a function call, ie they only store data temporarily. Inside a function, most variables are memory variables.

In Solidity, when passing array or struct types as function parameters, you need to specify the data location as either “memory” or “calldata” to indicate where the data is stored. Under the hood, string variables uses array of bytes (byte is another data type) to store its values, which is why we specified the data location as memory. We could have equally specified it as calldata and it would have worked fine like below:

The major difference between calldata and memory is that calldata is only used when we pass parameters to a function.

Storage variables are stored on the blockchain.
Memory and calldata variables are stored temporarily.
Calldata is only used when passing parameters.
We need to specify the data location when passing parameters of type Array or Struct to a function.

I hope we can now easily differentiate the different use cases for storage, memory and calldata variables.

Gas and Transactions

Transactions are exactly what they are in the crypto world, processing some sort of payments. When we deploy our contracts, or call some function that changes the state of some storage variable, we are submitting a transaction. If we look closely at the 2 functions store and addPerson that we have in our contract, we’ll realise that they both mutate (change the state) of some storage variable, hence a transaction is submitted for them to be mutated.

For transaction to be processed, it has to be mined (I talked about this in my earlier article) and that is where the concept of gas comes about. For our transaction to be mined by the miners, we need to pay gas (think of it as a transaction fee) so that our transaction can be processed. The amount of gas that’s paid depends on the computation that the function computes. The more the computation, the more the gas. This was the reason why we changed our data type to a mapping when working on our contract because to read a value for a mapping is less expensive than looping through an array to find a value.

View vs Pure functions

A function that only reads values does not involve a transaction hence no gas is needed to be paid. A function that reads the value of a storage variable is specified as view and a function that reads some constant (for instance) is specified as pure. Lets take a look at the code below

View and Pure functions

Notice how we specified memory as the data location to the returned person array in the getPeople function?

In the code above, the getPeople function reads the value of our people storage variable, while the getNumber just returns some constant. Suppose we mark the function as pure and still try to return a storage variable, we get the error like below

Also if we try to mutate the values of a storage variable in a function that is marked as pure or view, we get errors

So we use view and pure functions for only reading values.
A pure function cannot be used to read a storage variable.

Alright guys, that would be about it for now. In this article, we have been able to dive deeper into solidity to understand some more advanced types, some concepts like gas and transactions, memory, calldata and storage, view and pure functions etc. In upcoming articles, we’ll take what we’ve learnt so far and start applying them to building some real world applications.

I hope you found this article helpful in some way. Please give a clap, comment and follow, and I’ll see you in the next article. 🚀🚀🤩

Find the next step below 👇🏻👇🏻

--

--

Ifeoluwaolubo
Coinmonks

A software developer with love for the blockchain