With this article, we continue our journey through the realm of Solidity data types following today’s topics:
- contract types,
- fixed-size byte arrays,
- dynamically-sized byte arrays,
- address literals,
- rational, and
- integer literals.
It’s part of our long-standing tradition to make this (and other) articles a faithful companion or a supplement to the official Solidity documentation.
👇 Download PDF Slide Deck at the end of this tutorial!
To quote the official Solidity documentation, “every contract defines its own type”.
This statement might seem a bit cryptic, and since we’re an efficient crowd, we’d surely like to know what it means.
We can all remember that some number of articles ago, we mentioned how Solidity has key elements of an object-oriented programming language (OOPL). We also emphasized how smart contracts in Solidity are very similar to classes in an OOPL.
Classes themselves are a mesh of custom data types, i.e. structs, and functions, which qualifies classes to be treated as types.
👉 By extension, our contracts are also treated as types, and as every contract is unique in its own right, it defines its own type. Being a type, we can implicitly convert a specific contract to a contract it inherits from, i.e. if contract “Aa” inherits from contract A, it can also be converted to contract “A”.
Besides that, we can explicitly convert each contract to and from the address type. Even more, we can conditionally convert a contract to and from the
address payable type (remember, that’s the same type as the
address type, but predetermined to receive Ether).
The condition is that the contract type must have a
payable fallback function. If it does, we can make the conversion to address payable by using
However, if the contract type does not implement (a more professional way to say “have”) a
payable fallback function, then the conversion to address payable has to be even more explicit (no swearing!) by stating
A local variable
obc of a contract type
OurBeautifulContract is declared by
Once we point our variable
obc to an instantiated (newly created) contract, we’d be able to call functions on that contract.
In terms of its data representation, a contract is identical to the
address type. This is important because the contract type is not directly supported by the ABI, but the address type, as its representative, is supported by the ABI.
In contrast to the types mentioned so far, contract types don’t support any operators.
The members of contract types are the external functions (the functions only available to other contracts) and state variables whose visibility is set to
When we need to access type information about the contract, like the
OurBeautifulContract above, we’d call the
type(OurBeautifulContract) function (docs).
Fixed-Size Byte Arrays
The value type
bytesN holds a sequence of bytes, whose length, and accordingly
N goes from 1 to up to 32, i.e.,
The available operators for fixed-size operators are:
<=, <, ==, !=, >=, >(evaluate to bool)
- Bit operators:
&, |, ^(bitwise exclusive or),
- Shift operators:
- Index access: If
xis of type
0 <= k < Nreturns the
k-th byte (read-only). In other words,
xup to (inclusive)
x[N-1]is available for index access; if
N = 1, then only
xis of type
xis the only element, i.e. byte accessible by the index.
The shifting operator always uses an unsigned integer type as a right operand, which represents the number of bits to shift by, and returns the type of the left operand.
Let’s take a look at a simple example to illustrate:
bytes2 lo = 0x1234; // (lo is the left operand) uint8 ro = 5; // (ro is the right operand variable, must be u... type) lo << ro // will evaluate to an lo type, bytes2
A fixed-size byte array has only one member, .length, that holds the fixed length of the byte array. This member is accessible as the read-only value.
⚡ Warning: Since the type
bytes1 is a sequence of 1 byte in length, the type
bytes1 is a fixed-size byte array of 1-byte sequences. However, each element of the array is padded with 31 bytes, due to padding rules for elements stored in memory, stack, and call data, i.e., except in storage. Therefore, according to the official Solidity documentation, it’s better to use
bytes type instead of
💡 Note: Value types in storage are packed/compacted together and share a storage slot, taking only as much space per value type as really needed. In contrast, the stack, memory, and
calldata pad value types and store in separate slots, meaning that each variable uses a whole slot of 32 bytes, even if the value type is shorter than 32 bytes, effectively wasting the memory space.
Before Solidity v0.8.0, the keyword
byte was an alias for
Dynamically-Sized Byte Arrays
There are two dynamically-sized non-value types, namely
bytesis a dynamically-sized byte array, while
stringis a dynamically-sized UTF-8-encoded string.
Address literals are hexadecimal literals that pass the address checksum test, e.g.
Hexadecimal literals will produce an error if they are between 39 and 41 digits long and do not pass the checksum test.
However, we can remove the error by prepending zeros to integer types or appending zeros to
The Ethereum Improvement Proposal EIP-55 defines the mixed-case address checksum.
Integer and Rational Literals
Integer literals are created using a sequence of digits from a range 0-9, and each digit is interpreted (weighted) based on its position in the sequence.
Multiplied by an exponent of 10, e.g. 217 is interpreted as two hundred and seventeen, because, reading from right to left, we have 7 * 100 + 1 * 101 + 2 * 102.
A reminder, 100 = 1.
Octal literals don’t exist in Solidity and leading zeros are invalid.
Decimal Fractional Literals
Decimal fractional literals consist of a dot
. (or, depending on the locale) and at least one number on either of the sides, e.g.
💡 Info: “A locale consists of a number of categories for which country-dependent formatting or other specifications exist” (source).
Solidity also supports scientific notation in the form of 2e10, where 2 (left of “e”) is called mantissa (M) and the exponent (E) must be an integer. In a general form, we would write it as MeE and it is interpreted as M * 10**E, e.g. 2e10, -2e10, 2e-10, 2.5e1.
Readable Underscore Notation
We can also do a neat thing: separate the digits of a numeric literal for easier readability, such as in decimal
0x2eff_abde, scientific decimal notation
However, there are no leading, trailing, or multiple underscores; they can only be added between two digits.
Number Literal Expressions
Expressions containing number literals preserve their precision until they are converted to a non-literal type.
Such a conversion means an explicit conversion, or that the number literals are used with something else than a number literal expression, like boolean literals.
This behavior implies that computations don’t overflow and divisions don’t truncate in number literal expressions.
A very good example would be a number literal expression (2**800 + 1) – 2**800, which results in the constant 1 (of type
uint8), although the intermediate results would not fit the capacity of the EVM word length of 32 bytes.
One more example shows that an integer 4 is produced by computing the expression
.5 * 8, although the intermediary results are not integers.
⚡ Warning: most operators produce a literal expression when applied to number literals, but there are also two exceptions:
- Ternary operator
(... ? ... : ...),
- Array subscript (
In other words, expressions like
255 + (true ? 1 : 0) or
255 + [1, 2, 3] are not equivalent to using the literal 256 (the result of these two expressions), as they are computed within the type
uint8 and can lead to an overflow.
Number literal expressions can use the same operators as the integers, but both operands must compute yield an integer.
- If either of the operands is fractional, bit operations are inapplicable for use;
- If the exponent is a decimal fractional literal, the exponentiation operation is also inapplicable for use.
Shifts and exponentiation
* operations with literal numbers in place of a left
(base*) operand and integer types in place of the right (
exponent*) operand are performed in the
uint256 for non-negative literals or
int256 for negative literals (a
* symbol pertains to the exponentiation operations context).
⚡ Warning: Since Solidity v0.4.0 division on integer literals produces a rational number, e.g. 7 / 2 = 3.5.
Solidity has a number literal types for each rational number, e.g. integer literals and rational number literals belong to the same number literal type.
All number literal expressions (expressions with only number literals and operators) also belong to number literal types, e.g. 1 + 2 and 2 + 1 belong to the same number literal type.
💡 Note: When number literal types are used with non-literal expressions, they are converted into a non-literal type, e.g.
uint128 a = 1; uint128 b = 2.5 + a + 0.5;
Here, 1 is converted into a non-literal type
uint128, i.e. variable
a, but a common type for both
uint128 doesn’t exist and the compiler will reject the code.
In this article, we added even more data types in Solidity under our proverbial belt!
- First, we introduced and learned about the contract type.
- Second, we fixed our understanding of the fixed-size byte array type.
- Third, the situation got dynamic by studying the dynamically-sized byte array type.
- Fourth, we addressed the… what was it called… Aha – address literals!
- Fifth, we came to the most rational decision and discovered what rational and integer literals are and, of course, how can they be put to good use.
Slide Deck Data Types
You can scroll through the data types discussed in this tutorial here:
This tutorial is part of our extended Solidity documentation with videos and more accessible examples and explanations. You can navigate the series here (all links open in a new tab):
I’m an experienced computer science engineer and technology enthusiast dedicated to understanding how the world works and using my knowledge and ability to advance it. I’m focused on becoming an expert in Solidity and crypto technology, with a passion for coding, learning, and contributing to the Finxter mission of increasing the collective intelligence of humanity.