Solidity, a statically typed, high level, event driven, object oriented programming language that is used for coding smart contracts. Solidity was initially proposed by Gavin Wood in August of 2014 and was later developed by the Ethereum project's Solidity team. Solidity is designed to create smart contracts that run on the Ethereum Virtual Machine (EVM). It is by choice the best supported programming on Ethereum, Binance, Polygon network amongst others.
Solidity bears a similarity to ECMAScript, otherwise known as JavaScript, Solidity was actually inspired by ECMAScript and other curly braces language like Java, C++. Personally writing Solidity feels like writing JavaScript with a bit of Java syntax, this is because of the strictness in explicitly declaring the type of a variable, argument or return value from a function. Solidity as a programming language on the blockchain is a fully fledged Turing complete machine and this implies that it can simulate a universal Turing machine, in simple terms this means that we could use it for real world implementation of branching and conditional logic, we could also do iterations based on those conditions.
To read more articles like this please visit Netcreed
In this article we are going to look at what solidity is as a language, applications of solidity and we will write the most simple smart contract ever.
What is Solidity
Statically Typed
One of the most notable thing with solidity is the type declaration. Solidity is statically typed and as such it is required that we declare a type for every single variable or return value. This is to ensure that there is strictness when writing smart contracts, when programming, things can easily get out of hand and code executed on the blockchain is immutable so the compiler will check our code at run time to ensure that we trying to perform the right operation on the right type of value.
pragma solidity >= 0.7.0 <0.8.0;
string name = "my name";
bool isTrue = false;
uint age = 25;
Object Oriented
Solidity is an object oriented programming language and supports all the principles of OOP, a class in solidity is called a contract. A typical class in solidity should look like this.
pragma solidity >= 0.7.0 <0.8.0;
contract OneKoin {
bytes256 name;
constructor(string _name) public {
name = name;
}
}
We have just created a simple contract
however as the need be we could use more advanced features of OOP, solidity also supports interfaces, we can create an interface using the interface
keyword, and it functions just like in other languages. In solidity we can override methods defined on parent classes when inheriting from them allowing us to further customize or control the behaviour of a child class.
interface IERC20 {
function totalSupply() public view returns(uint256);
}
// this contract is expected to conform to the above interface
contract ERC20 is IERC20 {
// we can also override contract members
uint256 totalSupply public override = 2000000;
}
Event Driven
In solidity the concept of events exists to notify a consumer about what is currently going on with the blockchain, just like how events are fired in the DOM or just like how they are emitted in Nodejs, we can create an event and then emit it later when something tries to change the state of the blockchain.
// creating an event
pragma solidity ^0.4.21;
// Creating a contract
contract EventContract {
// Declaring state variables
uint256 public value = 0;
// Declaring an event
event Increment(address owner);
// Defining a function for logging event
function getValue(uint _a, uint _b) public returns(uint) {
emit Increment(msg.sender);
value = _a + _b;
}
}
Solidity was designed to run on the EVM which powers the Ethereum blockchain, however due to the similiarity between the Ethereum blockchain and other blockchains like Binance, Polygon; solidity can be used to write smart contracts that can be deployed to any network that supports solidity and uses an EVM, this is to ensure that there is a smooth developer experience, you can expect your projects to behave in a predictable manner irrespective of the network you deploy to, as far as it supports an EVM.
Structure Of a Solidity file
The most basic solidity program is composed of a lisence declarations, which is followed by a pragma definition. Most smart contracts will only specify the pragma version, that is the version of compiler that should be used to execute our code. It is quite important that we do this to prevent our code from being compiled with a compiler that introduces breaking changes in the code base. We can specify things like the abi encoder and decoder, the abi encoder has a v2 that is used for encoding and decoding deeply nested arrays and objects. If it isn't already visible but it is required of us to put a semi-colon at the end of each line just like we do in most curly bracket language.
pragma solidity >= 0.7.0 <0.8.0; // pragma version
contract Coin { // contract alias for class
// The keyword "public" makes variables
// accessible from other contracts
address public minter; // simple "value" types
// "Reference" type
mapping (address => uint) public balances;
// Events allow clients to react to specific
// contract changes you declare
event Sent (address from, address to, uint amount);
// Constructor code is only run when the contract
// is created
constructor() public {
minter = msg.sender;
}
// Sends an amount of newly created coins to an address
// Can only be called by the contract creator
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
require(amount < 1e60);
balances[receiver] += amount;
}
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent (msg.sender, receiver, amount);
}
}
Solidity allows us to import code from other files, allowing us to modularize our code. The import syntax of solidity is similar to that of javascript, we will examine how we can import a module from another file.
import "path";
This is a standard way to import a module from solidity. howerver this imports every contract defined in the module and it pollutes the workspace. So there exists a way to deal with this pollusion, we can use the import
as syntax to avoid this.
import * as something from 'path'
we can also use an alternative syntax, that still reads like the above.
import "filename" as symbolName;
This is just for conveinece, we can use destructuring syntax to get access to contracts declared in other modules.
import {symbol1 as alias, symbol2} from "filename";
The as
allows us to rename an exported member from a file to avoid namespace collision.
comments in solidity are similar to comments in other programming languages. we use a double forward slashes //
for a single line comment.
// single line comments
/**
use the normal synatx
for comments spanning accross
multiple lines
**/
Variables
Just like other programming languages we can create variables we can use to track the state of the smart contract.
pragma solidity >=0.4.0 <0.9.0;
contract Storage {
uint stored; // State variable
}
We can annotate our variables with types, there are some built in types that comes baked with the solidity language however there is a distinction between the types solidity provides, types in solidity is classified into;
- Value Type
- Reference Type
Value Type
Any particular type which must be assigned a native value like a string or a boolean, they refer to the the simple types which serves as building blocks for more complicated structure of data, a value type is always assigned a value, they include.
-
uint
which is used to represent unsigned integers, that is numbers that cannot have a minus sign, thus it will never be negative. -
int
used to represent numbers that may contain positive or negative values. -
bool
to represent Boolean values, can only be either true or focus. -
address
to represent ethereum addresses on the blockchain. -
bytes/strings
used to represent character sequences however bytes is advantageous because it cost less gas.
pragma solidity >=0.4.0 <0.9.0;
contract Example {
// integers
int age = 23; // alias for int256
uint amount = 100000 // alias for uint256 or uint8
bool isTrue = false;
bytes32 name = 'bob';
string fam = 'jack';
}
There are also three other types of value types. We have;
-
address
this is used to represent an ethereum address. -
address payable
this is also used to represent an address, the difference between this andaddress
is thataddress payable
is an Ethereum address we can send ether to. This type of address has some members liketransfer
,balance
e.tc. You can easily convertadrress
topayable
by typecasting theaddress
using thepayable
function.
pragma solidity >=0.4.0 <0.9.0;
contract Example {
address owner = "0xF...";
address payable sender = msg.sender;
address payable newAddr = payable(owner);
}
-
Enums
this is a fixed length of constant values, it is often used to avoid code duplication or to avoid hardcoding some values. Each item in an enum will return it's index when used.
pragma solidity >=0.4.0 <0.9.0;
contract Example {
enum MyChoices { stayAtHome, code, sleep, read };
function goToSleep() public returns(uint) {
return MyChoices.sleep
}
}
Reference Types
These are types that do not store the value of a variable, rather they store a reference to the location that contains the value, thus they are not really concerned with the value of the data.
-
mapping
A mapping is a data structure used to store key-value pairs much like a dictionary or hash table, the key can be of any particular type, while the value can also be of any particular type. -
struct
This is a way of creating our own custom types in solidity. A struct is similar to a map in that it is also a key-value pair data structure, but astruct
can contain both value types and reference types as members. -
array
An array is a bunch of variables of the same type. Each variable in the array has an index and can be accessed using that index, solidity allows for both dynamic and fixed sized arrays.
pragma solidity >=0.4.0 <0.9.0;
contract Example {
// array
uint[5] arr = [1, 2, 3, 4, 5];
struct SuperHero { // struct
string name;
string alias;
}
// mapping
mapping (address => SuperHero) superheroes;
function makeSuperHero(string name, string val) public pure returns(SuperHero) {
SuperHero Batman;
Batman.name = name;
Batman.alias = val;
return Batman;
}
function getHero(address hero) public view returns(SuperHero) {
return superheroes[address];
}
}
Scope
Variables in solidity are classified based on their scope and by this I mean, where can we get access to this variable from inside the smart contract? Think of it like scope in JavaScript. There are three types of variables in solidity;
- State variable which are declared inside the smart contract body and can be used from anywhere inside the smart contract.
- Local variables which are declared inside a function body and can only be used inside the function it was created in.
- Global variables that are always available to every smart contract, basically the compiler supplies the value of this variables at compile time. They include information about the blockchain or the address which is trying to deploy the smart contract e.g msg.sender.
pragma solidity >=0.4.0 <0.9.0;
contract Example {
uint val = 100; // state variable
address = msg.sender; // Global variable
struct SuperHero {
string name;
string alias;
}
mapping (address => SuperHero) superheroes;
function makeSuperHero(string name, string val) public pure returns(SuperHero) {
SuperHero Batman; // local variable
Batman.name = name;
Batman.alias = val;
superheroes[msg.sender] = Batman;
return Batman;
}
function getHero(address hero) public view returns(SuperHero) {
return superheroes[address];
}
}
Functions
No programming language would be complete without functions, solidity has functions for creating reusable blocks of code or to isolate logic in a smart contract. If a function returns a value it is required that we use the returns(type)
keyword to specify the return type, however if the function does not return any value we can omit that statement. It is also necessary to specify the type of the arguments we pass to a function, to ensure that we are performing the right operation on the right data.
pragma solidity >=0.4.0 < 0.9.0;
contract Example {
uint val = 2000;
function getVal() public view returns(uint) {
return val;;
}
}
The above function is a very simple function that returns a state variable , to declare a function, we use the function
keyword then the name of the function followed by parenthesis and we specify the argument we want to pass to the function if it accepts any, we can then specify a modifier for the function, we then we specify the type of function that we are creating then we use the returns
keyword if there's any return value.
function name(type argument) modifier functionType returns(type){
body;
}
We can classify our functions based on the modifier we attach to the function;
public
Functions that are tagged as public can be called from any where in the smart contract, by other smart contracts.private
Functions tagged as private are local to a contract or library, they can only be called in the contract that they are declared in.external
Functions tagged as external are similar to public functions, they can be called from transactions.internal
internal function can only be called inside the current contract they are defined in and cannot be used outside of this context.
// can be called from anywhere
function sayHey() public {}
// can only be called inside the smart contract
function imPrivate() private {}
// can be called from outside the smart contract
function anyWhere() external {}
// only inside the smart contract
function notPublic() internal {}
We can also classify functions based on the effect they will have on the smart contract;
-
view
View functions do not modify the state of the smart contract, they only return a value; -
pure
Pure functions will not have any side effects on the smart contract, state variables cannot be modified, we cannot read from state variables inside pure functions. A pure function is only concerned with it's argument. -
payable
These are functions that allow smart contracts to receive some ethers. -
non payable
these function marks a smart contract as unable to receive some ether.
// return a value from the smart contract
function anyWhere() external view {
return totalSupply;
}
// only work with arguments
function work(uint _val) external pure {
return _val**
}
// allow a smart contract recieve ether
function payMe(uint amount) external payable {}
So far we have looked at a lot! I think at this stage we have learned enough to code up our own basic smart contract, remember I said It would be the simplest smart contract.
pragma solidity >=0.4.0 < 0.9.0;
contract Example {
// a value we are going to read and write later
uint val = 2000;
// getting a reference to the address that will deploy this contract
address owner = msg.sender;
// get the value
// anyone can do this.
function get() external view returns(uint){
return val;
}
// set the value
// only the owner can do this.
function set(uint _val) external {
// if the caller is not the owner this will fail and revert.
require(msg.sender == owner);
val = _val;
// emit an event that logs the sender and the value
// each time we change it.
emit ValueChanged(msg.sender, _val);
}
event ValueChanged(indexed caller, uint indexed val);
}
That's it for this introduction, in subsequent articles we are going to look more into detail about solidity, hope you enjoyed this and you learned something new!
To read more articles like this please visit Netcreed