Solidity & Smart Contract Development

This article covers smart contracts and solidity development, which is a continuation of my previous article.
Solidity
Solidity is the programming language for writing smart contracts on the Ethereum blockchain. The solidity documentation helps you write and understand smart contracts. The syntax of Solidity is almost like JavaScript.
Difference between Solidity and JavaScript
Solidity is:
Hyper language, i.e, has to declare the type of all variables.
compile language, i.e, after writing code, you have to go through phases of compilation which will transform its high-level code to low-level instructions that EVM will be able to understand. Unlike JavaScript that you write code and run it on a browser or your devices.
It's limited, unlike JavaScript allows string manipulation, which is not possible with Solidity.
It has a few lines of code
can only run on the blockchain, unlike JavaScript, which runs on the browser and other environments. Vyper is the other programming language used to write smart contracts on the Ethereum blockchain, but Solidity is common and supported by the Truffle framework.
Remix has an autocompile feature
Structure of solidity smart contract structure
SPDX license identifiers
Solidity from version 0.6.8 introduced SPDX license identifiers so developers can specify the license 1.4k the contract uses. (e.g., OpenZeppelin Contracts use the MIT license).
// SPDX-License-Identifier: MIT
Pragma Statement
made of pragma keyword, solidity, caret, plus version number, and semicolon. This ensures we use the correct version of Solidity. Using caret means we can compile using any version of solidity equal to that version number specified or higher than it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract MyContract {
}
Variable Types
Contrary to JavaScript, in Solidity, you have to declare variables before you use them.
Fixed-size types:
It occupies a fixed size in memory
Examples:
- Boolean type:
// isReady: true or false;
- uint 0: Important when sending a transaction of Ether or an ERC20 token. It’s like a number type in JavaScript, except that in solidit,y it can only hold positive numbers, which are integers and not floating-point numbers
uint 0;
- Address: the recipient address and the address of a smart contract
address recipient;
- Byte32 data: can hold any arbitrary binary data. It’s used to represent the string, as with Solidity, it's hard to manipulate strings. Byte32 is used to represent strings whose size doesn’t exceed 32 bytes
Variable size types:
bit complex and can hold variable size in terms of length
- string: a string of any length, and unlike JavaScript, we don’t have any convenience functions to manipulate strings. In many cases, people prefer to represent their string as byte32, and in other cases, represent using bytes, which is a generalization of bytes32 and used to represent binary data of no predefined length
string name;
bytes _data; //bytes
- arrays: used to represent a collection of data. Contrary to, JavaScript in solidity, arrays have to be arrays of the same type, e.g array of integers, an array of byte32
uint[] amounts;
- mappings: associative array, they have keys that map to values. Define the type of key and type of value mapped by each key, and define the type of mappings. It's similar to JavaScript objects and how they work.
mapping(uint => string) users;
user-defined data:
custom representation of data as commonly used in the C language.
- struct: You use the struct keyword, then name it in uppercase, and you’ll define different fields. It’s similar to a JavaScript object, but here you can’t instantiate a container. Struct types are used a lot in Solidity
struct User {
uint id;
string name;
uint[] friendIds;
}
- Enum: defined using the enum keyword after you name your struct. Define different options. Used as a sort of label
enum Color {
RED,
GREEN,
BLUE,
}
Built-in Variables
msg.sender;
This is the address of the sender of the transaction. It’s impossible to fake that, due to the cryptography of Ethereum, you’re sure that msg.sender is populated into the address of the sender.
msg.value
The value of Ether that was sent in a transaction.
// Unix
now block.timestamp
Timestamp in seconds, i.e, time elapsed since January 1st, 1970. Unix could reference the timestamp of a block from the time it was mined, and it can be manipulated by miners.
You can check out other built-in variables here on the solidity docs
Constructor
function, where you can run some initialization code inside a contract, the code is only run once when a smart contract is deployed. Some constructors can be defined with the function keyword; this was valid in earlier versions of Solidity.
constructor(uint _a) public {
a = _a;
}
Declaring Functions
Functions help manipulate data.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
constructor MyContract {
uint value;
// view - read only
function getValue() external view returns(uint) {
return value;
}
function setValue(uint _value) external {
value = _value; // assigning variable
}
}
Function Visibility
Function visibility keywords: they define who can have access to your function
Restrictive visibility keyword:
Private: A function can only be called within the smart contract. The function is prefixed with private (just good practice, not a must)
internal: function is limited to being called within a smart contract and from a smart contract that inherits from the smart contract. Solidity allows inheritance mechanisms like those in the C language.
External: The function is only called from the outside smart contract. Isn’t prefixed with _private.
Public: can be called within and outside the smart contract. It is the most permissive function visibility keyword.
The good rule of thumb for smart contracts is to give the least privilege to any entity first, try private, then internal, external, or lastly public. The greatest beginner mistake is using Public for all contracts.
Variable Visibility
declares an integer variable with private and name, i.e, can be read within the same smart contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
uint private a;
function foo() external {
uint b = a + 1;
}
}
A private variable is readable within the foo function. The level of restriction of private keywords is only valid with the EVM, which is within the Ethereum Blockchain, which is a public blockchain that doesn’t store anything private, as people will be able to access it on other platforms.
Internal: can be read within a smart contract and a smart contract that inherits it, but can’t be read in another smart contract.
public: can be read from within and outside the smart contract. It’s not a must to declare a public keyword, as Solidity does it by default.
Use the least possible privilege, try private, then internal if it doesn’t work, go for the public keyword.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
uint public a;
// you'll get an error since solidity has defined variable globally
/*function foo() public view returns {
return = a;
} */
function foo() external {
uint b = a + 1;
}
}
Deploying & interacting with a smart contract in Remix
write a contract with on Remix editor, with a pragma statement and the License from Open-Zeplin. Then compile using Remix’s auto-compile feature and then deploy on Remix.
Write a contract with Remix Editor, with a pragma statement and the License from Open-Zeplin. Then compile using Remix’s auto-compile feature and then deploy on Remix.
if/ if-else: The function is only executed if conditions are met
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
function foo() external {
// doesn't use superior equal sign(===) like javascript only uses (==)
if(msg.sender >= 100 && block.timestamp > 12345677... || ) {
}
// function is only executed if conditions are met
if(boolValue) {
} else {
}
}
}
for loop
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
bool boolValue;
function foo() external {
for(uint i = 0; i < 10; i++) {
}
}
}
while loop: same as for loop, it allows you to go through a set of data or compilation at a certain amount of time. Can’t know how long you’ll need to run the code, i.e, infinite loop. Can use the break and continue conditions.
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
function foo() external {
// testing condition which must be true or false
bool isOk = true;
while (isOk) {
//
if() {
isOk: false;
// break;
// continue
}
}
}
}
Arrays
Declare and manipulate the array. Arrays allow us to represent the compilation of data. It must contain an array of the same type, i.e, integer
Storage Arrays
Fixed-size array: specify the size of the array and lose control of the push method
Dynamic size Array(don’t specify the size of the array, just add using the push method)
Memory Arrays
- has to be declared. They are not safe in a contract since you can’t save data on them
- Accept array arguments and return arrays from data*
external takes call data, public & internal takes memory
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
uint[] myArray; //CRUD - CREATE/READ/UPDATE/DELETE
// Dynamic array
function foo() external {
// adding elements to array
myArray.push(2);
myArray.push(3);
// reading elements to array
myArray[0];
//updating elements to array
myArray[0] = 20;
//myArray[0];
// delete element to array
delete myArray[1];
//myArray[1] = 0;
// iterating through array
for(uint i = 0; i <= myArray.length; i++) {
myArray[i]
}
}
// Memory array
function foo() external {
uint[] memory newArray = new uint[](10);
newArray[0] = 10;
newArray[0];
newArray[0] = 20;
delete newArray[2];
}
// Accept & Reference Array in function
function foooBar(uint[] memory myArg) internal returns(uint[] memory ){
}
}
Mappings
Declare, manipulate, CRUD elements, default, nesting mappings. Has keys & values like objects in JavaScript. It’s possible to iterate through all entries of mappings, so in Solidity, you combine arrays and mappings to represent collections of data.
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
mapping(address => uint) balances;
// owner of address and address of another approved to use initial address(another person's address)
mapping(address => mapping(address => bool)) approved;
mapping(address => uint[]) scores;
function foo() external {
// Add
balances[msg.sender] = 100;
// Read
balances[msg.sender];
// Update
balances[msg.sender] = 200;
// delete
delete balances[msg.sender];
// default value
balances[someAddressThatDoNotExist] => 0
// exotic mapping 1
approved[msg.sender][spender] = true;
approved[msg.sender][spender];
approved[msg.sender][spender] = false;
delete approved[msg.sender][spender];
// exotic mapping 2
scores[msg.sender].push(1);
scores[msg.sender].push(2);
scores[msg.sender][0];
scores[msg.sender] [0] = 10;
delete scores[msg.sender][0];
}
}
Structs
structs are good to represent structured data in the smart contract. Define a template with different fields that represent your data, then create several clones of the template with the same fields but different values. It’s the same with classes in JavaScript, except that in Solidity struct can only define a field, not methods.
If you find yourself defining a lot of variables on your smart contract, it is a sign that you should be using a struct.
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
struct User { //template
address addr;
uint score;
string name;
}
// declare array of struct
User[] users;
// define mapping
mapping(address => User) userList2;
function foo() external { //parameter
User memory user1 = User(msg.sender, 0, _name);
User memory user2 = User(msg.sender, 0, _name);
User memory user3 = User(name: _name, score: 0, addr: msg,sender);
// no particular order but its verbalsome have to choose the notation
user3.addr;
user3.score = 20;
delete user1;
users.push(user2);
userList2[msg.sender] = User();
}
}
Events
With solidity events, you can push data from smart contracts to the web frontend or mobile application. For example, if it's a Defi project, you can create events on the smart contract so that whenever traders make transactions on the front-end, the contract emits events that can inform traders of ongoing trades. There could be a lot of events in a contract, and you need to use index keywords to filter specific events. You can use a maximum of 3 indices on a contract.
An event can’t be read within the contract once consumed it’s consumed by entities outside the contract. Instead of a storage variable, you can use an event
Two steps: declare the event on the contract level and on the front-end side of a dApp.
// SPDX license identifiers
pragma solidity ^0.8.0;
contract MyContract {
event NewTrade (
uint date,
address index from,
address to;
uint amount;
)
function trade(address to, uint amount) external {
emit NewTrade(now, msg.sender, to, amount);
}
}
Interacting with smart contracts
- Calling a function of another contract. Syntax, import keyword, contract interfaces(minimum information required for the contract to interact with another), and how propagation works. (Deploy on Remix to see how the two contracts interact) e.g ERC20 token with IERC20
// SPDX license identifiers
pragma solidity ^0.8.0;
import "ContractB"
contract A {
// interface of B/refernce B & address of B
address addressB;
function setAddressB(address _addressB) external {
address = _addressB;
}
function callHelloWorld() external {
Interface b = Interfaec(address);
// B b - B(addressB);
returns B helloWorld();
}
}
contract B {
function helloWorld() external pure returns(string memory) {
return "Hello world!";
}
}
contractb.sol
// SPDX license identifiers
pragma solidity ^0.8.0;
// contract interface - will be called in another contract
contract Interface {
function helloWorld() external pure returns(string memory);
// function foo() external;
}
contract B {
function helloWorld() external pure returns(string memory) {
return "Hello world!";
}
function foo() external {
}
Transferring Ether
manipulating Ether on Solidity smart contract is something powerful that can be done. Address controlled by private key
// SPDX-License-Identifier: MIT;
pragma solidity 0.8.0;
contract MyContract {
function sendEther(address payable to, uint amount) external {
to.transfer(amount);
// to.transfer(100);
// 1 wei = 10 ^ -18 ether = 0.000....1
} // address payable/ sendable address
function receiveEther() external payable {
msg.value; // amount of ether balance
address(this).balance;
}
fallback() external {
//
}
receive() external payable{
//
}
}
Dealing with Errors(require, throw)
errors in solidity smart contract: what happens when there’s an error in a solidity function,
Keywords used to create an error, like throw, require,and assert
What happens when there are errors in other smart contracts
// SPDX-License-Identifier: MIT;
pragma solidity 0.8.4;
contract ErrorContract {
uint a;
function throwError() external {
a = 10;
// BOOM error!
// gas will be consumed: avoid errors: trigger them if one of your arguemnets of function are out of range
}
function throwErr() external {
// was the first to be used in solidity and was depricated
// throw
}
uint b;
function revertErr() view external {
// specify why it was reverted
if(b == 10) {
revert("this is why it reverts");
}
}
function requireErr() view external {
require(a != 10, 'this is why it reverts');
// errors that can happen in lifecycle method of the contract
}
function assertErr() view external {
assert(a != 10);
// can never happen in the smart contract
}
function willThrow() pure external {
// revert("because reasons"); //view on deploy
require(true == false, 'because reasons');
assert(true == false);
}
function foo() external returns(bool) {
B c = new B();
address(c).call(abi.encodePacked("bar()"));
return false;
}
}
contract B {
function bar() pure external {
revert("because of other reasons");
}
}
Custom Function Modifiers
Function modifiers allow you to run some code before you execute some functions. It is good for access control and validating some agreements.
basic syntax, pass arguments, and change them
// SPDX-License-Identifier: MIT;
pragma solidity 0.8.4;
contract FunctionModifers {
// uint a = 10;
address admin;
function basicSyntax(uint a) external myModifier(a) myModifier2(a) {
// modifier is executed first then placeholder which is the function
}
function onlyPerson() external onlyAdmin() {
}
modifier onlyAdmin() {
require(msg.sender == admin, "only admin can call this function");
_;
}
modifier myModifier(uint a) { // internal by default
require(a == 10, "my error message");
_; // placeholder that stands for function
}
modifier myModifier2(uint a) { // internal by default
require(a == 10, "my error message");
_; // placeholder that stands for function
}
// first modifier then next & foo function
}
Inheritance
Inheritance: offers you an opportunity to reuse some code
courtesy of SolidityByExample
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
/* Graph of inheritance
A
/ \
B C
/ \ /
F D,E
*/
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
// Contracts inherit other contracts by using the keyword 'is'.
contract B is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
// Override A.foo()
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// Contracts can inherit from multiple parent contracts.
// When a function is called that is defined multiple times in
// different contracts, parent contracts are searched from
// right to left, and in depth-first manner.
contract D is B, C {
// D.foo() returns "C"
// since C is the right most parent contract with function foo()
function foo() public pure override(B, C) returns (string memory) {
return super.foo();
}
}
contract E is C, B {
// E.foo() returns "B"
// since B is the right most parent contract with function foo()
function foo() public pure override(C, B) returns (string memory) {
return super.foo();
}
}
// Inheritance must be ordered from “most base-like” to “most derived”.
// Swapping the order of A and B will throw a compilation error.
contract F is A, B {
function foo() public pure override(A, B) returns (string memory) {
return super.foo();
}
}
Thank you for reading through my article, despite its length wanted to put together the basics of solidity and smart contracts. Disclaimer: Most code snippets are more relevant to lower versions of Solidity. Check Solidity Docs, Solidity by Example, for more relevant ones for higher versions of Solidity.
You can leave a comment, suggestion, or point of correction; all are welcome.




