Since its introduction in 2014 the Solidity programming language has become the de facto standard to write smart contracts on the Ethereum blockchain network. Solidity is so popular that numerous blockchain networks support it, including Avalanche, Polygon and Binance Smart Chain.
In this article I'll cover the basics of writing a simple smart contract in Solidity that's a variation of an introductory Hello World program. The contract example might seem trivial, but it demonstrates some very important details that any developer must understand to program in Solidity.
How to compile and deploy a smart contract using an IDE
Solidity is a compiled language that uses a compilation model similar to Java. In this model, Solidity source code is compiled into bytecode. That bytecode is stored on the blockchain and is processed by an Ethereum Virtual Machine (EVM) hosted by one of the many computer nodes on the blockchain network. Figure 1 illustrates the process, similar to how a JVM processes Java bytecode.
The usual way to create a Solidity smart contract is to use an IDE such as Visual Studio Code or Remix. The VS Code IDE supports Solidity through an extension plugin; the Remix IDE, as shown in Fig. 2, is intended for use primarily as an online IDE and supports Solidity out of the box.
Remix also tightly integrates with blockchain technology. Developers can create, compile and deploy a smart contract to an internal blockchain emulator, a test blockchain on the network called a testnet or a production blockchain typically called a mainnet. For developers just starting out with Solidity programming, using Remix is the easiest way to go as it's an all-in-one way to create smart contracts.
The structure of a Solidity smart contract
Developers typically create Solidity smart contract source code as a .sol file.
The structure of a Solidity smart contract is similar to class in object-oriented programming. In OOP, a class is the basic organizational unit to group functions and data members; the analogical equivalent in Solidity is a contract.
Fig. 3 shows a simple smart contract that exposes a function named getMessage() that returns a message when called. The sections below describe the details of the smart contract.
As mentioned previously, Solidity is a compiled programming language. The Solidity compiler has evolved over the years to accommodate numerous compiler versions today. As such, a Solidity smart contract must declare the compiler version the source code supports. This declaration is the first statement in a smart contract and is defined using the pragma keyword as shown in callout 1, Fig. 3. In this example, the pragma statement indicates the developer is using a Solidity compiler version of 0.8.9.0 or later. The code for the actual smart contract follows after this statement.
A developer next declares a smart contract using the contract keyword in callout 2, Fig. 3. In the example above, the smart contract is named BasicContract. All smart contract code is placed between the curly brackets of BasicContract.
As mentioned at the start of the article, Solidity is very similar to OOP languages such as Java or C#. One of these similarities is that Solidity supports a constructor to implement initialization behavior in a smart contract. The constructor in callout 3, Fig. 3 assigns the value Hello, World! to the variable named phrase. This behavior runs when the smart contract first deploys to the targeted blockchain network.
Another similarity to OOP is that Solidity supports private and public scope. Private scope means that variables and functions declared as private are only visible from within the smart contract. Variables and functions declared as public are accessible from outside the smart contract.
In the BasicContract code shown in Fig. 3, the variable named phrase is declared at Line 6. The variable is of type string and has a private scope, which means it is only viable from within the smart contract. However, the function named getMessage() in callout 4, Fig. 3 is public. This means the function can be called from outside the smart contract.
For those of you that have programmed in Java or C#, this is pretty standard stuff. But the internal code of the getMessage() function contains keywords such as memory, view and bytes that reveal special aspects of Solidity programming.
Let's take another look at the getMessage() function in an isolated manner.
Anatomy of a Solidity function
Fig. 4 shows the Solidity getMessage() function in isolation. The function returns a string as declared by (string memory) at Line 12. The keyword memory means the return value is stored in memory only. Nothing is stored on the underlying computer that supports the smart contract.
Also, notice that the getMessage() function gains public scope and that the function signature uses the keyword view.
The keyword view, which is particular to Solidity, means the state of the contract is unchanging -- in other words, the function is read-only. No new data is added to the smart contract, and values for variables outside the function are unchanged.
For example, Fig. 5 shows an erroneous use of view in Solidity code. Notice that the keyword view is used in the signature of the add() function at Line 22, yet the add() function changes the state of the variable named c declared at Line 20 which is outside the function. Using the keyword view enables the Remix IDE to throw a design-time error because the state of the contract is changed.
For example, at Line 13 in Figure 4 above, to concatenate two strings together in Solidity one must convert the strings into two byte arrays combined using the abi.encodePacked() function to create a third array named concatenated. The resulting string is the value returned by the getMessage() function.
The abi library, which is built into Solidity, provides functions to encode and decode data according to the ABI specification. The ABI specification describes a set of functions commonly used in smart contract programming in a language-agnostic manner; string concatenation is one such function.
There are other libraries one can import into the smart contract that simplify string manipulation. However, using Solidity out of the box to combine strings takes a low-level approach to working with strings. We'll discuss importing libraries into Solidity in depth in future articles.
Understanding gas fees in a Solidity function
In addition to providing low-level programming capabilities, there is something else that is special about Solidity programming in particular and smart contract programming in general. To run a smart contract on a blockchain incurs fees that must be paid in the cryptocurrency native to the given blockchain.
For example, if you run a Solidity smart contract on the Ethereum network, you will incur fees that must be paid in the Ether (ETH) cryptocurrency. Run a Solidity program in the Hedera network and you'll incur fees that must be paid in HBAR. There are other tandems of blockchains and cryptocurrencies to consider as well.
These fees are charged because of the nature of the relationship between smart contracts and associated blockchain network.
These days, the cost of computing in terms of the actual work done is not that much of a concern to many developers. Storage is cheap, memory is cheap and the cost of CPU cycles is infinitesimal.
However, every bit of work a smart contract does and every resource it uses matters. Blockchain networks are, by definition, a consortium of tens and up to thousands of independently owned, autonomous machines that act in concert according to well-known algorithms. The owners of those machines want to be paid for the resources a smart contract uses.
To deploy a Solidity smart contract onto a production blockchain, such as Ethereum's mainnet, there's a fee whenever resources such as memory and CPU cycles are used to validate a transaction. For example, there's a fee when the smart contract is deployed, and another one when a smart contract's function is called. Any fee incurred to validate a transaction is called a gas fee.
Gas fee calculation depends on the blockchain upon which the smart contract is deployed. The details of gas fee optimization are a bit beyond the scope of this article; we'll cover them in a later one. For now, the important thing to understand is that the smart contract you deploy onto a production blockchain will cost you money as it runs. Also, you must hold enough of the blockchain's cryptocurrency to pay for the transaction fees your smart contract incurs.
When you run a smart contract, there is no free lunch. Such is the nature of decentralized computing.
Putting it all together
Smart contracts built on blockchains are transforming enterprises and supply chains. Various blockchain platforms support Solidity, which suggests the world will need more Solidity programmers, not fewer. Use cases will grow. Opportunities are apparent.
Solidity is a very powerful programming language that lets developers write smart contracts that manage tasks far more important to a business than a simple Hello World application. Real estate leases, ticket purchases and supply chain activities all can emanate from a blockchain. As with learning any programming language, it's best to start at the beginning with Solidity before moving to its more advanced features.