Skip to main content

Command Palette

Search for a command to run...

Simple Storage | Solidity Fundamentals

Updated
7 min readView as Markdown
Simple Storage | Solidity Fundamentals

Setting up first Contract

boiler plate code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18; // stated our version

contract SimpleStorage {

}

Pointers :

  • // SPDX-License-Identifier: MIT to let people and automated tools know the code is free to use, copy, modify, and share.

  • pragma solidity 0.8.18; says our code needs to be exactly 0.8.18 version, pragma solidity ^0.8.18; our code can be compatible with anything 0.8.18 or above, pragma solidity >=0.8.18 <0.9.0; tells compiler that any version within the range works.

  • contract is like a Class in other languages.


What does compiling a contract mean?

It converts the Solidity code into bytecode and ABI that can be understood and executed by the Ethereum Virtual Machine (EVM). ABI stands for Application Binary Interface. It is a standard JSON file that acts as a translator between human-readable programming and the Ethereum Virtual Machine (EVM).


Basic Variable Types

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18; // stated our version

contract SimpleStorage {
    // Basic types : boolean, uint, int, address, bytes
    bool gay = true;
    uint256 favouriteNumber = 42;
    string name = "0xgambit";
    int256 credit = -250;
    address myAddress = 0xF1Efb0e72A0fCf91D168790b8cD4C97614550A30;
    bytes32 favouriteBytes32 = "cat";
}

Pointers :

  • boolean is a true / false value, uint is a un-signed integer aka a positive whole number, int is a positive or negative whole number, address will be like a wallet address

  • uint and int are special as we can specify how many bits we want to assign to them while creating a variable, eg : uint256 (256 bits / 32 bytes) of storage, can store [0, 2^256-1], if you do not specify how many bits then unit defaults to uint256. We may also have 8 bits, 16 bits, 56 bits, etc..

  • string is essentially a bytes array wrapper, you can also specify bits for bytes like bytes32 , but remember unlike uint bytes32 and bytes are different (will learn later)

  • The default values for these types are:

    • bool: false

    • uint (all sizes like uint8 to uint256): 0

    • int (all sizes like int8 to int256): 0

    • address (and address payable): 0x0000000000000000000000000000000000000000 (often referred to as address(0) or the zero address)

    • bytes (dynamically-sized): 0x (an empty byte array)

    • bytes1 to bytes32 (fixed-size): 0x00... (padded with zeros to match the specified number of bytes)

Functions

Functions execute a code block and may or may not return a value, just like other languages.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18; // stated our version

contract SimpleStorage {
    uint256 public favoriteNumber;

    function updateFavorite(uint256 _favoriteNumber) public{
        favoriteNumber = _favoriteNumber;
    }
}

We can compile and deploy the contract in remix test environment now.

if we call the favouriteNumber (doesn't cost gas as we aren't changing blockchain data) we get the value 0.

If we call the updateFavourite function with a number of our own (costs gas) it will update the variable value.

Now if we call the data we will get the value 69, as updateFavourite changed the state of blockchain.

Function Visibility Specifiers

Visibility keywords dictate where and who can call the function. You must choose one for every function:

  • public: Callable internally by the contract and externally by users or other contracts, it also creates a getter function for storage/state variables.

  • external: Only callable from outside the contract. (Saves gas for large array inputs).

  • internal (default): Only accessible inside the current contract or contracts inheriting it.

  • private: Only accessible inside the exact contract where it is defined.

scope of variables inside or outside functions are just like other languages, follow the curly brackets {}

// getter function

function retrieve() public view returns(uint256){
    return favoriteNumber;
}

We are saying that we wanna return favouriteNumber of type uint256.

State Mutability Modifiers

These keywords notify the Ethereum Virtual Machine (EVM) how the function interacts with the blockchain state, directly impacting gas fees:

  • Default (State-changing): Modifies state variables and requires a paid transaction fee (gas).

  • view: Reads data from the blockchain but cannot modify it. Free to call externally.

  • pure: Neither reads nor modifies blockchain state. Used for isolated math or helper logic.


Arrays and Structs

Struct basics

A struct allows us to create custom data structure with multiple fields.

struct User {
    uint256 id;
    string name;
    bool isActive;
}

// Creating and storing a struct instance
User public newUser = User(1, "Alice", true);

// Accessing fields using dot notation
string memory userName = newUser.name; // Returns "Alice"

Array basics

Arrays in solidity are collection of elements of the exact same data type (including structs).

They can be fixed(decided at compile time) or dynamic(can grow/shrink during run time).

// Example syntax of fixed size array
uint256[5] public numbers;

// Example syntax of dynamic size array
uint256[] public IDs;

Common Array Operations (Dynamic Arrays Only):

  • .push(element): Adds an item to the end of the array. Increases length by 1.

  • .pop(): Removes the last item from the array. Decreases length by 1.

  • .length: Returns the current number of elements in the array.

Combined Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18; // stated our version

contract SimpleStorage {

    // custom data structure called Person
    struct Person{
        uint256 favouriteNumber;
        string name;
    }

    // dynamic array of type Person
    Person[] public listOfpeople;

    function addPerson(string memory _name, uint256 _favouriteNumber) public {
        // Person memory newPerson = Person(_favouriteNumber, _name);
        // listOfpeople.push(newPerson);

        listOfpeople.push(Person(_favouriteNumber, _name));
    }

}

example Person added after deploying contract.

Since listOfpeople is public we can check it by proving the index number.

Memory Storage and Calldata

Key distinctions:

  • storage changes are saved on the blockchain and cost gas. A storage reference points to the actual state variable. If you make a variable outside of function inside a contract it defaults to storage variable.

  • memory is temporary workspace. It disappears when the transaction/call finishes. Generally used as parameter defining in functions. You usually write memory for temporary arrays, strings, structs, mapping, or bytes, simple values don't need memory .

  • calldata , read-only delivery package , holds externally supplied arguments without copying them into memory. It is generally cheaper than memory, but immutable. Using calldata avoids making an unnecessary temporary copy, so it is usually cheaper than using memory for input you only read.

Example code :

contract Notes {
    string[] public savedNotes; // storage: stays on the blockchain

    function save(string calldata newNote) external {
        string memory editableNote = newNote;

        // We can change the temporary copy if needed
        // editableNote = "Updated note";

        savedNotes.push(editableNote); // copied into permanent storage
    }
}

When save() finishes:

  • editableNote (in memory) disappears.

  • newNote (in calldata) also disappears.

  • savedNotes (in storage) remains permanently on the blockchain.

Mapping

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18; // stated our version

contract SimpleStorage {

    // custom data structure called Person
    struct Person{
        uint256 favouriteNumber;
        string name;
    }

    // dynamic array of type Person
    Person[] public listOfpeople;

    // Mapping, name => favouriteNumber
    mapping(string => uint256) public nameToFavouriteNumber;

    function addPerson(string memory _name, uint256 _favouriteNumber) public {
        // Person memory newPerson = Person(_favouriteNumber, _name);
        // listOfpeople.push(newPerson);

        listOfpeople.push(Person(_favouriteNumber, _name));
        nameToFavouriteNumber[_name] = _favouriteNumber;
    }

}

If we don't give a key for a value it give the default value of it's data type.