Popularized by Ethereum, smart contracts are programs that are stored and executed on a blockchain. The term was coined in the late ’90s in an attempt to “provide the blueprint for ideal security.”
This blueprint governs the relationship between parties when an event occurs using code, making it vastly secure and as predictable as the code itself.
Much like an external (Ethereum) account, smart contracts have addresses and can hold and transfer funds. External accounts, however, are not bound to a single network. You can use the same account to connect to any number of blockchain networks.
Smart contracts, on the other hand, can only be connected to one specific network. They can augment or replace real-life contracts because of their transparent nature and the immutability of the system (blockchain) they run on. The most popular way to write such a contract is through the Solidity programming language.
Solidity is a compiled object-oriented programming language created by the Ethereum team that has JavaScript-like syntax. Unlike JavaScript, Solidity is strongly-typed and makes great use of inheritance.
Solidity compiles our source code into deployable byte code and an Application Binary Interface (ABI) to interact with the byte code using other smart contracts or programming languages.
// SPDX-License-Identifier: UNLICENSED pragma solidity >=0.4.17 <0.9.0; contract Storage { uint data; function set(uint newData) public { data = newData; } function get() public view returns (uint) { return data; } }
Because smart contracts’ source code is often readily available to read, it’s a good idea to specify the license of your code in the first line after SPDX-License-Identifier:
.
Following that, the pragma
directive tells the compiler which version of Solidity to use. The versions start with 0.
to indicate that breaking changes are to be expected in minor, regular updates. Our smart contract can be compiled against version 0.4
or higher, but not version 0.9
.
Contracts in Solidity are pretty similar to JavaScript classes in that they hold variables and methods that interact with one another. But unlike classes, you do not need a this
keyword to access a variable in Solidity. It’s also mandatory to have semicolons after declarations (function definitions do not count.)
Our Storage
contract holds integer data
(annotated by the uint
type keyword) and exposes two functions that can change and display it. The data
variable is a storage variable that will exist for the lifetime of our contract. If we deploy this contract, anyone can call set
and get
to modify and retrieve the value of data
.
To initialize our data
variable with a value, contracts can provide a constructor
function that takes zero or more arguments:
contract Storage { uint data; constructor (uint defaultData) { data = defaultData; } function set(uint newData) public { data = newData; } function get() public view returns (uint) { return data; } }
A contract can inherit from another contract through the is
keyword:
// SPDX-License-Identifier: UNLICENSED pragma solidity >=0.4.17 <0.9.0; contract C { string public greeting = 'hello'; } contract D { string public farewell = 'goodbye'; } contract E is C, D {}
Contract D
will have access to both greeting
and farewell
variables.
Solidity functions have the following pattern:
function <function name>(<parameters type>) \[function type\] [returns (<return type>)] {}
The commonly used function types are public
, external
, private
, internal
, view
, pure
, and payable
.
Here’s how the types that govern the visibility of the function work:
public
: anyone can call the functionexternal
: anyone but the contract can call the function. Using the external
type instead of public
can have a performance boost and potentially save a lot of gasprivate
: only the contract holding the function can call itinternal
the contract and its derivatives can call the functionThese types govern access to the state:
view
: the function only reads the statepure
: the function neither reads nor writes to the stateNote that payable
is used when the function can accept payment when it’s called.
A more detailed pattern for the function type would look like this:
{public|external|private|internal} [pure|view|payable]
Thus, in our contract, the get
function that has the types public view returns (uint)
is universally accessible, reads data, and returns an integer.
Solidity, however, provides us with a shortcut to automatically create get
functions that display our state. By simply adding the keyword public
before our variable data
, a data
function will be created to replace our get
function:
contract Storage { uint public data; // a data function will be created to access the `data` variable constructor (uint defaultData) { data = defaultData; } function set(uint newData) public { data = newData; } }
To test and deploy our contract to a blockchain network, we can either rely on an IDE like Remix to simplify our job or use a real coding environment. For this article, we’ll use Remix.
Remix is a Solidity IDE used to compile, deploy, and manually test Solidity code. It can interface with an array of Ethereum test networks, as well as the main network.
First, create a new contract under the contracts
directory and copy over our contract code.
When our Solidity code is saved, Remix will automatically compile our code, creating a bytecode that gets sent to the network, as well as an ABI to interact with the deployed contract.
We can inspect both the bytecode and the ABI in the Solidity compiler tab:
If our code compiles successfully, we can start deploying and interfacing with it under the deploy and run transactions tab.
In this tab, we can pick our environment, the contract to deploy, and which account to deploy it with. JavaScript VM
means that Remix will maintain a blockchain network inside our browser to make testing run as fast as possible.
The value input is associated with payable
function calls and is unnecessary for our contract.
At the bottom of the tab, we see Deployed Contracts
, which indicates that we can deploy multiple instances of one or many contracts.
Under Contracts
, Remix picks up on our defaultData
inside the constructor
function. Enter an integer and hit Deploy to deploy a new contract:
The data
button represents the automatically generated getter for our public data
variable, while the set
button represents our set
method.
The color difference can be attributed to the fact that our getter data
function does not modify the state in our app, and therefore, does not cost anything to run on the blockchain. However, our set
function is a transaction type of function that consumes gas and resources to run, much like the initial Deploy
button.
Running the data
function would return our initial input 2021
and set
modifies it accordingly.
In this article, we looked at the basic building blocks of a Solidity contract, as well as how to write, compile, deploy, and test our Solidity code using the Remix IDE. From this point on, we should take it a step further and use a real environment using Ganache and Truffle. Happy hacking.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowJavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.