In this first article in the series, we’ll start with the basics of what Solidity is, what smart contracts are, how they work, how they are implemented, and then dig right into some cool Solidity examples and details.
Terminology
Of course, the first question poses itself: what is Solidity?
As you could have imagined, Solidity has something to do with smart contracts. Solidity is the very programming language we’ll be using for implementing smart contracts!
How exciting, isn’t it?
A simple, yet comprehensive and on-target definition of a smart contract states:
π‘ Definition: “Smart contracts are computer programs stored on a blockchain that run when predetermined conditions are met. They are typically used to automate an agreement‘s execution so that all participants can be immediately certain of the outcome, without an intermediaryβs involvement or time loss. They can also automate a workflow, triggering the next action when conditions are met.” — IBM
At this point, I’ll point out several key concepts emerging from this definition: programs, blockchain, conditions, agreement automation, participants, outcome, and no intermediary.
A somewhat more technical definition says:
π‘ Definition: “A ‘smart contract’ is simply a program that runs on the Ethereum blockchain. It’s a collection of code (its functions) and data (its state) that resides at a specific address on the Ethereum blockchain.” — Ethereum.org
From this definition we can take a few more key concepts, this time from a technical perspective: code, functions, data, state, and address.
During our exploration of Solidity, we’ll come upon these key concepts and explain their meaning and role in the smart contract context.
What is Solidity?
Solidity is a high-level, object-oriented programming language, meaning its level of abstraction is well above the physical layer of the machine(s) and platforms it runs on.
It will hide away all the intricacies of the underlying hardware and communication networks, enabling us to focus on the ultimate functionality.
Variables, objects, types, functions, and other language constructs will be our building blocks and stepping stones for creating even more exciting components which, when connected, will form our smart contracts.
The object-oriented property of the language is just the characteristic that facilitates the natural transfer of real-world entities and their behaviors into our models, finally represented and deployed as smart contracts.
With its fragrance reminding of C++, Python, and JavaScript, Solidity is statically typed, with support for inheritance (think of a smart contract as a class), libraries, and complex user-defined types (docs).
Solidity contracts run on Ethereum Virtual Machine or EVM. This article (and the ones to follow) will build on the official Solidity documentation, which I encourage you to regularly check out at docs.
As indicated by the URL, at the time of writing this article, the current Solidity version is v0.8.15 and unless explicitly stated otherwise, we will stick with it throughout the entire series.
Typical examples of smart contracts we can implement by using Solidity are voting, crowdfunding, blind auctions, multi-signature wallets, escrow arrangements, and many others.
Smart Contract in Solidity – An Example
A simple, yet illustrative example of a smart contract enables us to notice the basic elements of a smart contract implementation in Solidity:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.4.16 <0.9.0; contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
Our first example shows a short smart contract implemented in Solidity, which does only two things: it publicly enables storing a value in a state variable storedData
via function set and makes it publicly available for reading via function get
.
However, the simplicity of our example will introduce us to the basic contract structure and the most common language constructs used in a process of building smart contracts.
The line // SPDX-License-Identifier: GPL-3.0
states that the source code is licensed under the GPL version 3.0. You can learn more about software licensing models at
The line pragma solidity >=0.4.16 <0.9.0;
establishes the range of Solidity compiler versions supporting the source code execution, ranging from (including) Solidity v0.4.16 up to, but excluding, Solidity v0.9.0. In the general case, the pragma
keyword represents a compiler directive specifying how to treat the source code.
In our specific case, it designates the compiler versions which are compatible with our source code.
π‘ Note: I would like to point out a simple, yet practical detail: an open curly brace can be placed in the same line with the contract name, and that’s the style I usually prefer in other languages because it saves one line of code per block, making the code more compact without sacrificing readability. However, when it comes to Solidity, code readability benefits from placing the opening curly brace below the function head. Later on, when we introduce function modifiers, it will be much easier to notice at a first glance if we’re looking at the head or the body of a function. Of course, we could also mix the two styles depending on the situation, but in the spirit of consistency, I recommend keeping the opening curly brace in its line.
Content-wise, a contract is a collection of code and data. The code represents the behavior of a contract which we modeled upon a real-world entity, and it is implemented via functions.
The data represents the state of a contract and it is implemented via different types of objects, making the contract a stateful entity.
The data resides in storage, a memory space mapped to an address on the Ethereum blockchain.
The line contract SimpleStorage
defines the name of our contract, SimpleStorage
, which we’ll also refer to as the head.
Next is a contract body marked opening “{
” and closing “}
” curly braces. We will also use this head/body reference convention for other block constructs, such as functions.
The line uint storedData;
declares a state variable called storedData
of type uint
(unsigned integer of 256 bits in length).
storedData
can be read and written to, resembling a rudimentary database-like behavior. In our example, there are two functions, get
and set
, which read from and write to the variable storedData
.
The line function set(uint x) public
declares a public function set, which takes an unsigned integer x
. The function without a keyword returns does not return a result.
The line storedData = x;
assigns a value stored in the argument variable x
to the state variable storedData
. A function without a state mutability keyword is considered to have an implicit, default state mutability non-payable (explained below).
The line function get() public view returns (uint)
declares a public function get
, which can only read the state variables from its outside scope because of the visibility keyword view
(explained below). The function also must return an object, i.e. a result of type uint
.
The line return storedData;
returns the object storedData
.
Visibility and State Mutability
Functions can be and often are augmented with a visibility keyword.
Both get
and set
functions are declared with the visibility keyword public
, meaning that the functions are callable from (visible to) the parent contract and all the other contracts.
There are three more visibility keywords: private
, internal
, and external
.
- The visibility keyword
private
makes the function callable only from inside the parent contract. - The visibility keyword
internal
makes the function callable from the parent contract and all the inherited contracts. - The visibility keyword
external
makes the function callable only from the other contracts, and not from the parent contract.
Along with the visibility keywords, there are also three explicit state mutability keywords: pure
, view
, and payable
. These state mutability keywords determine if a function can access or modify the objects (state variables) in its outside scope.
- Keyword
pure
prevents the function from accessing its outside scope entirely, meaning the function can access only its internal objects and its arguments. - Keyword
view
prevents the function from changing its outside scope, meaning the function can access its outside scope objects (the contract state), its internal objects, and its arguments. - Keyword
payable
enables the function to receive Ether, change its outside scope objects (the contract state), its internal objects, and its arguments. The default state mutability non-payable is implicitly assumed if no mutability keyword is specified, and in that case, the function can change its outside scope objects (the contract state), its internal objects, and its arguments.
Note: Function defined as pure partially resembles a pure function in functional programming paradigm by having the second of the two important properties:
- the function returns identical values for identical arguments;
- the function has no side effects, i.e. it does not modify its outside scope. If a Solidity function defined as
pure
would have both properties, it would be considered truly pure in terms of the functional programming paradigm.
There will be a lot more details regarding visibility, state mutability, and their implications in the upcoming articles, but until we build more knowledge, we’ll stick with the general overview.
More details can also be found at the link describing the language grammar, and I strongly recommend an in-depth look at the language possibilities.
Conclusion
In this article, we introduced ourselves to the basics of smart contracts and Solidity.
First, we peeked into some of the key concepts and terms in the realm of smart contracts and Solidity.
Second, we got to know what Solidity is and what kinds of wizardry we can use it for.
Third, we ventured through a short example of a smart contract and saw some of its spells – many more await us on our journey.
Fourth, we faced the most common Solidity magic, cast by visibility and state mutability.
What’s Next?
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):