Solidity Data Types and Smart Contract Fundamentals


Compiler Directive

The pragma directive specifies the version of the Solidity compiler that you want to use to build your source file. When the compiler encounters this line, it will check its version against the one you specified here. If the compiler version is different, Remix will automatically adjust accordingly to your specifications.

You can specify the compiler version(s) in the following ways:

It's a good practice (though not mandatory) to start your smart contract with an SPDX License Identifier. It helps in making licensing and sharing code easier from a legal perspective.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19; // use only version 0.8.19

Value Types

Here, we will cover the most commonly used or basic types. For a complete reference on types with exceptions, examples, and much more, consult the official documentation at Solidity Types.

1. Booleans

Booleans are used to represent binary values: true or false.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BooleanExample {
    bool public flagTrue = true;
    bool public flagFalse = false;
}

2. Integers

Arithmetic is checked by default, reverting on overflow or underflow. Switch to unchecked mode for performance when overflow/underflow is acceptable, using unchecked { ... }.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract IntegerExample {
    int public signedInt = -10;
    uint public unsignedInt = 100;

    function checkedAddition(int a, int b) public pure returns (int) {
        return a + b;
    }

    function uncheckedAddition(int a, int b) public pure returns (int) {
        unchecked {
            return a + b;
        }
    }

    function uintOverflow() public pure returns (uint8) {
        uint8 a = 255;
        return a + 1;
    }

    function intUnderflow() public pure returns (int8) {
        int8 b = -128;
        return b - 1;
    }

    function integerRangeExample() public pure returns (int256, uint256) {
        return (type(int256).min, type(uint256).max);
    }
}

3. Address

The address type has two variants:

Use address payable for addresses meant to receive Ether, while address is for non-payable addresses, like contracts that cannot accept Ether.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract AddressExample {
    address public owner = 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF;
    address payable public payableAddress = payable(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF);
}

4. Fixed-size Byte Arrays

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ByteArrayExample {
    bytes3 public asciiBytes = 0x616263;
    bytes3 public hexBytes = 0x123456;
    bytes3 public charBytes = "XYZ";

    function getAsciiLength() public view returns (uint) {
        return asciiBytes.length;
    }
}

5. Rational and Integer Literals

Integer literals are decimal numbers (e.g., 69). No octal literals; leading zeros are invalid. Decimal fractions require digits after the decimal point (e.g., .1, 1.3), and scientific notation is supported (e.g., 2e10, 2.5e-1). Use underscores for readability in any format (e.g., 123_000, 0x2eff_abde), but they have no semantic impact.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract IntegerLiterals {
    uint public myNumber = 123_456;  // Use underscores for readability
    uint public myHex = 0x2eff_abde;  // Hexadecimal with underscores
}

6. String Literals

String literals in Solidity can be written with double or single quotes ("foo" or 'bar'). They can be concatenated (e.g., "foo" "bar" is equivalent to "foobar") and do not imply trailing zeros. Strings are implicitly convertible to bytes1 to bytes32, if they fit, and also to bytes and string. String literals can only contain printable ASCII characters (range 0x20 to 0x7E). Escape characters are supported, including \n, \t, \", \xNN, and \uNNNN for special characters or hex/Unicode sequences.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StringLiterals {
    string public myString = "Hello, Solidity!";
    string public concatenatedString = "Hello, " "Solidity!";  // Concatenation
}

7. Unicode Literals

While regular string literals can only contain ASCII, Unicode literals – prefixed with the keyword unicode – can contain any valid UTF-8 sequence. They also support the same escape sequences as regular string literals.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UnicodeLiterals {
    string public unicodeString = unicode"Hello 😃";
}

8. Hexadecimal Literals

Hexadecimal literals are prefixed with hex and enclosed in quotes (e.g., hex"001122FF"). They consist of hexadecimal digits and can use underscores as separators. Multiple hex literals can be concatenated (e.g., hex"0011" hex"2233" becomes hex"00112233"). They behave like string literals but are not implicitly convertible to strings.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HexLiterals {
    bytes memory hexValue = hex"001122FF";
}

9. Enums

Enums are one way to create a user-defined type in Solidity. Enums require at least one member, and its default value when declared is the first member. Enums cannot have more than 256 members.

The data representation is the same as for enums in C: The options are represented by subsequent unsigned integer values starting from 0.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EnumExample {
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
    ActionChoices public choice;
}

10. User-defined Value Types

User-defined value types create a zero-cost abstraction over an elementary type, similar to an alias but with stricter requirements. Defined as type C is V, where C is the custom type and V is a built-in type. Use C.wrap to convert to the custom type and C.unwrap to convert back. These types lack operators (e.g., ==) and disallow conversions with other types.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

type UFixed256x18 is uint256;

Functions

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract FunctionExample {
    function add(uint a, uint b) public pure returns (uint) {
        return a + b;
    }
}

Pure and View Keywords

The terms view and pure are used when a function reads values from the blockchain without altering its state. Such functions will not initiate transactions but rather make calls, represented as blue buttons in the Remix interface. A pure function will prohibit any reading from the state or storage.

Visibility

In Solidity, functions and variables can have one of these four visibility specifiers:

Appending the public keyword next to a variable will automatically change its visibility and generate a getter function (a function that gets the variable's value when called).

Arrays

Arrays in Solidity allow you to store lists of values. They can be dynamic or static, and are zero-indexed (the first element has index 0).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ArrayExample {
    uint256[] public list_of_favorite_numbers;

    function addNumber(uint256 _favoriteNumber) public {
        list_of_favorite_numbers.push(_favoriteNumber);
    }
}

Structs

Structs allow you to define custom types to group multiple variables into one complex variable. They can be used to represent an object with multiple properties.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StructExample {
    struct Car {
        uint year;
        string model;
        uint price;
    }
}

Mappings

Mappings are key-value stores, similar to hash tables, and can only be used for storage and memory.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MappingExample {
    mapping(address => uint256) public balances;
}

Data Locations

Solidity distinguishes between three types of data locations:

  1. Storage: Variables that are declared at the contract level are stored in storage, taking up space on the blockchain.
  2. Memory: Temporary variables, created during function execution, are stored in memory.
  3. Stack: Stores simple values and is used for managing small-sized variables.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DataLocationExample {
    uint256[] storage arr;
    uint256[] memory arr_mem;
}

Inheritance

The is keyword signifies inheritance and links the parent contract SimpleStorage to its child contract, AddFiveStorage.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import { SimpleStorage } from "./SimpleStorage.sol";

contract AddFiveStorage is SimpleStorage {}

We can now add new functions and modify existing functions by using the override keyword.

function store(uint256 _newFavNumber) public override {}

To address this, we need to mark the father function as virtual, enabling it to be overridden by child contracts:

function store(uint256 favNumber) public virtual {}