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.
- Statically Typed: Each variable requires a specified type.
- No Undefined or Null: Uninitialized variables have a default value based on their type.
- Error Handling: Use revert() or return a tuple with a bool indicating success.
- Value Types: Passed by value (copied during assignments or function arguments).
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
- Signed integers: int8 to int256.
- Unsigned integers: uint8 to uint256.
- int is an alias for int256, and uint is an alias for uint256.
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:
- address: A 20-byte value (Ethereum address) with no Ether-related functions.
- address payable: Same as address, but allows Ether transfers via transfer and send.
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
- The return types cannot be empty.
- If the function type should not return anything, the whole returns (<return types>) part has to be omitted.
- By default, function types are internal, so the internal visibility has to be specified explicitly for functions defined in contracts.
// 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:
- public: Accessible from both inside the contract and from external contracts.
- private: Accessible only within the current contract.
- external: Used only for functions. Visible only from outside the contract.
- internal: Accessible by the current contract and any contracts derived from it.
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:
- Storage: Variables that are declared at the contract level are stored in storage, taking up space on the blockchain.
- Memory: Temporary variables, created during function execution, are stored in memory.
- 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 {}