The Ultimate Solidity cheatsheat. Jumpstart your Web 3.0 development with the ultimate solidity tutorial which will take you from Zero to hero in Solidity in the least possible time. Now, if you need to run this code quickly without much hassle and installation, you can make use of Remix IDE to test your code online. You can check the tutorial for using Ethereum Testnets for learning to compile solidity code and run it on virtual machines or testnets.
The Post is being constantly updated with each solidity syntax or functionality change. So, that you may find everything related to solidity on a single page. You can use the menu to jump to a specific section or topic or you can use “CTRL+F” and find what are you looking for.
Table of Contents
How to use this Solidity Cheatsheat
This tutorial contains the basic syntax of solidity and basic code blocks of solidity which can be used as building blocks to build your next big application for Web 3.0.
Solidity Basics
In this section, we are going to cover the basics of Solidity programming. There are some important things that must be kept in mind while using solidity.
- In solidity, each expression has to end with ;
- Comments are written as // followed by a comment
- Every single contract has an address which is used to communicate with the contract.
- Wallet address and contract address act as the same and they both can receive and hold funds.
- Blockchain nodes can not make direct API calls.
Solidity data types
Solidity has different data types which include boolean, Uint, Int, string, address and byte. Solidity is a statically typed language which means the type of every variably must be specified.
Type | Value | Default Value |
Bool | True | |
int | Signed or unsigned. Bits can be specified (unit8-uint256). By default its uint256 | 0 |
unint | Unsigned number | 0 |
String | words, letters, text | |
Address | Ethereum wallet address | |
Bytes | Bytes value. bytes can be specified as well. Eg:- bytes32 is the maximum size. |
The visibility of a variable is internal by default. We can change the visibility by adding keyword public. For example:-
uint256 public favNumber;
Reducing Gas cost for variables (constant vs mutable variables)
In Solidity, there are two keywords for variables whose values do not change namely constant and immutable. The contact variables are those that are declared outside of a function only once and they are typically written in caps.
uint256 public constant MINIMUM_USD = 50;
The variables which we declare outside a function but then assign a constant value in a function or a constructor are called immutable variables in Solidity. For, example:-
address public * immutable * i_owner;
constructor() {
i_owner = msg.sender;
}
Solidity Functions
In solidity, functions are a block of code that can be used to write code once and execute it multiple times in our program. A function can be created with the keyword function. for example
function store(uint256 _favnumber) public{
favNo = _favnumber;
}
Functions Visibility in Solidity
Functions visibility is Solidity is the authorization to call a function and can be specified as follows:-
- Public – Any contract outside or inside can call our function. adding public to a variable makes it a getter function.
Private
– only inside the contract that defines the function.Internal
– only inside the contract that inherits ainternal
function. Only a contract and its children can call itExternal
– only other contracts and accounts can call
If no visibility is specified, it automatically assumes the internal visibility. Functions can also have two additional parameters called View and Pure. The View and pure functions do not cost any gas fee to execute and do not allow to modify blockchain state. However, if view or pure functions are called from gas costing functions, they cause gas. Pure functions also disallow reading from blockchain state.
function retrieve() public view returns (uint256){
return number;
}
Payable Functions
A function that can receive funds into the smart contract. A function can be declared payable just by adding the payable keyword.
function public payable fund () {
}
Constructor Function
Constructor is a function that is automatically called whenever you deploy a contact in the same transaction. for example to setup an owner of a contract we can use constructor as following.
address public /* immutable */ i_owner;
constructor() {
i_owner = msg.sender;
}
Function Modifiers
Modifiers amend the functionality of a function in declaration. For example a modifier can be created as :-
modifier onlyOwner {
// require(msg.sender == owner, "Not Owner");
if (msg.sender != i_owner) revert NotOwner();
_;
}
Now, we can limit the function to only owner by following code:
function withdraw() payable onlyOwner public {
}
Receive and Fallback functions
These are special functions in Solidity. Receive function is automatically called when someone sends funds to a contract. Whereas fallback is called when there is some data in the transaction and there is no corresponding function, so fallback function is automatically called.
contract FallbackExample {
uint256 public result;
// Fallback function must be declared as external.
fallback() external payable {
result = 1;
}
receive() external payable {
result = 2;
}
}
// Explainer from: https://solidity-by-example.org/fallback/
// Ether is sent to contract
// is msg.data empty?
// / \
// yes no
// / \
// receive()? fallback()
// / \
// yes no
// / \
//receive() fallback()
Solidity Structs
We can create a struct to store multiple variables. For example
struct people{
uint256 number;
string name;
}
people public ammar = people({number:2,name:"ammar"});
First, we defined a struct and then we created an object with name of ammar and curly brackets were used to assign the values to struct variables.
Solidity Arrays
Arrays are used to store a list of objects or variables of other types. To define an array in Solidity we just use [] after the data type. For example, to create an array of people use the following code.
people[] public peorsons;
If we give it a size [3], it will only be able to hold 3 objects of that type. Now to push objects to an array use the following code.
function addtoarray(string memory _name, uint _favno) public{
persons.push(people(_name,_favno));
}
The second method is to first create a struct and then push it into the array.
function addtoarray2(string memory _name, uint _favno) public{
people memory person= people({name:_name, number:_favno});
persons.push(person);
}
Resetting an array
An array in solid can be reset by simply reinitialzing it. For example:-
funders = new address[](0);
The code resets array with zero elements.
Solidity data storage locations
Solidity can store and access data for EVM in 6 locations which are used to store the variable values temporarily or permanently in the state as per the requirement. The basic solidity data storage locations are as follows.
- Stack
- Memory
- Storage
- Call Data
- Code
- Logs
Calldata and memory mean that the variable is going to exist only temporarily. Therefore in the above function call, we used the keyword memory. Storage functions are somewhat permanent variables. If we don’t specify anything with a variable, it becomes a storage variable. If we use calldata we can’t reassign the variable value.
We can not say a variable is code, log or stack. Moreover, data storage location can only be specified for array, string, struct or memory types. For function parameters, it allows only calldata and the memory location only.
Mappings in Solidity
Mappings in Solidity are used to store the data in the form of key-value pairs in the form of a dictionary. Here each key is mapped to a single value and can be used to retrieve its value.
For example, to create a mapping from a string to a number, we can use the following code.
mapping (string=>uint256) public nameToFavno;
Now, to add values to mapping, you can modify the above function as following. It will also create a mapping while adding it to the array.
function addtoarray2(string memory _name, uint _favno) public{
people memory person= people({name:_name, number:_favno});
persons.push(person);
nameToFavno[_name] = _favno;
}
Importing Contracts in Solidity
A contract in solidity can be imported in solidity with simple import command. The imported contract can then be deployed within from host contract or can be used by it. To import a contract you can use the following command.
import "./SimpleStorage.sol";
Now to create a new imported contract within the host contract use the following syntax. You have to create a global variable and then create a function to create new contract with the new keyword.
contract StorageFactory {
SimpleStorage public simpleStorage;
function CreateSimpleStorageContract() public{
simpleStorage = new SimpleStorage();
}
}
Once deployed, it will appear as follows on Remix IDE.
Now to keep a record of all created contracts within a contract we can make use of an array.
pragma solidity 0.8.8;
import "./SimpleStorage.sol";
contract StorageFactory {
SimpleStorage[] public simpleStorageArray;
function CreateSimpleStorageContract() public{
SimpleStorage simpleStorage = new SimpleStorage();
simpleStorageArray.push(simpleStorage);
}
}
To interact with the imported contract we can create functions to use the imported contract. We use array index to retrieve the contract and then can make use of the imported contract.
function SfStore(uint _simpleStorageIndex, uint _numbertoStore) public {
SimpleStorage simpleStorage = SimpleStorage(simpleStorageArray[_simpleStorageIndex]);
simpleStorage.store(_numbertoStore);
}
function SfGet(uint _simpleStorageIndex) public view returns(uint) {
SimpleStorage simpleStorage = SimpleStorage(simpleStorageArray[_simpleStorageIndex]);
return simpleStorage.retrieve();
}
To simplify the function we can directly make use of the array index and leave creating new variable of SimpleStorage type.
function SfStore(uint _simpleStorageIndex, uint _numbertoStore) public {
simpleStorageArray[_simpleStorageIndex].store(_numbertoStore);
}
function SfGet(uint _simpleStorageIndex) public view returns(uint) {
return simpleStorageArray[_simpleStorageIndex].retrieve();
}
Loops in Solidity
Loops in Solidity works in the same manner as javascript or python.
For loop in Solidity
For loop in solidity loops through all values in sequence. we give it an initial value, ending condition and step. For example:-
for (uint256 funderIndex=0; funderIndex < funders.length; funderIndex++){
address funder = funders[funderIndex];
addressToAmountFunded[funder] = 0;
}
This loop resets all mappings.
Inheritence and overloading in Solidity
Inheritance in solidity can be used to extend the functionality of a contract or modify the functioning of a function. For overloading to work, we need to add a virtual keyword with the original function and override keyword with the new function. The following contract will inherit from the main contract and override a function to store a value.
contract ExtraStorage is SimpleStorage{
function store(uint256 _favoriteNumber) public override {
favoriteNumber = _favoriteNumber+5;
}
}
How to send and receive payments in Solidity?
In Solidity, msg is a global object and msg.value contains the value of each transaction and msg.sender contains the address of tx initiator. For example to control the minimum amount of transactions we can use the keyword require along with msg.value.
function fund() public payable {
require(msg.value > 1e18, "Didn't send enough");
}
Here require keyword limits that the minimum value of the transaction be 1e18 Wei (or 1 ether) otherwise all preceding work will be reverted with an error message. All unspent gas is reverted to original address.
How to get crypto exchange value in a smart contract?
Oracles are entities that can provide real-world data to smart contacts. Chainlink is one of the largest decentralized oracle networks available and it provides functionality to get exchange data from the real world. There are some core functions of chainlink which can be used for outside world data which are as follows:-
- Chainlink Data Feeds. Where a network of chainlink nodes collects exchange data and stores it in a reference contract which we can invoke to get the latest data.
- Chainlink VRF can be used to generate random numbers.
- Chainlink Keepers- Used for event-driven code execution.
- End to End Reliability – For making API requests.
So, we can use chainlink data feeds to get updated exchange rates.
Communicating with chainlink contract to get conversion rate
To communicate with other contracts we need two things in solidity. Number 1 is the contract address and secondly the ABI which tells all functions available for interaction. For example to get data feed from Chainlink Ethererum Goerli testnet contract, we can use the following code. Contract address can be taken from Chainlink data feeds website.
The simplest way to get ABI is through interfaces which only contains function declarations and do not contain the actual logic. The chainlink aggregator interface is available at github page. We can directly import an interface from Github repository.
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
function getPrice() public{
AggregatorV3Interface priceFeed= AggregatorV3Interface(0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e);
(uint roundID, int price, uint startedAt,uint timeStamp, uint80 answeredInRound)= priceFeed.latestRoundData();
}
Now if we are interested in only price we can remove other unused variables.
(, int price, ,, )= priceFeed.latestRoundData()
This return value in an integer. Remeber last 8 are after decimal. we need to have it 18 extra zeroes for wei. So, our final code will be as follows.
function getPrice() internal view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(
0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
);
(, int256 answer, , , ) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
}
// 1000000000
function getConversionRate(uint256 ethAmount)internal view returns (uint256)
{
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000;
// the actual ETH/USD conversion rate, after adjusting the extra 0s.
return ethAmountInUsd;
}
Libraries in Solidity
Libraries can hold all your internal functions without any state variables and these can be directly called in the contract. A library can be defined as following:-
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
// Why is this a library and not abstract?
// Why not an interface?
library PriceConverter {
// We could make this public, but then we'd have to deploy it
function getPrice() internal view returns (uint256) {
// Rinkeby ETH / USD Address
// https://docs.chain.link/docs/ethereum-addresses/
AggregatorV3Interface priceFeed = AggregatorV3Interface(
0x8A753747A1Fa494EC906cE90E9f37563A8AF630e
);
(, int256 answer, , , ) = priceFeed.latestRoundData();
// ETH/USD rate in 18 digit
return uint256(answer * 10000000000);
}
// 1000000000
function getConversionRate(uint256 ethAmount)
internal
view
returns (uint256)
{
uint256 ethPrice = getPrice();
uint256 ethAmountInUsd = (ethPrice * ethAmount) / 1000000000000000000;
// the actual ETH/USD conversion rate, after adjusting the extra 0s.
return ethAmountInUsd;
}
}
Now to use the value in our main contract we need to specify the library.
import "./PriceConverter.sol";
contract FundMe {
using PriceConverter for uint256;
function fund() public payable {
require(msg.value.getConversionRate() >= MINIMUM_USD, "You need to spend more ETH!");
addressToAmountFunded[msg.sender] += msg.value;
funders.push(msg.sender);
}
}
Withdrawing Funds in Solidity
There are three ways to withdraw funds in Solidity namely:
- Transfer
- Send
- Call
There are limitations in 1st two methods as explained by Solidity by example. Call method is the recommended method after Dec 2019. Transfer and Send can only use 2300 gas.
Transfer
We can use transfer for sending ethereum in the following manner. We need to typecast msg.sender topayable address
payable(msg.sender).transfer(address(this).balance);
Send
Send can be used as following. If we don’t include require statement, it does not automatically give error and amount will not be reverted.
bool sendSuccess = payable(msg.sender).send(address(this).balance);
require(sendSuccess, "Send failed");
Call (The recommended method)
Call is a lower level function and can be used to call any function without ABI.
(bool callSuccess, ) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
The call function returns two parameters.one is bool that we have used and the other is bytes (bytes memory dataReturned) which we don’t need so left it blank. Morover on right handside (” “) indicates that we are not calling any function.
Custom Errors in Solidity
Custom errors in solidity can be defined once in code and can be called during the program execution.
error NotOwner();
They can be used to replace require statement with if statement.
if (msg.sender != i_owner) revert NotOwner();
Credits
Much of the code has been taken from Learn Solidity Youtube course by freecodecamp.
FAQ
Is semicolon necessary in solidity?
Yes, Solidity requires that every statement finishes with semicolon.
What is the maximum size of Uint and bytes in solidity?
The maximum size of uint is uint256 and for bytes, its bytes 32.
What is the default visibility of a function in Solidity?
The default visibility of a function in solidity is set to internal. This means that only a contract and its children can call it.
What is the difference between calldata and memory variables in solidity?
Both calldata and memory are temporary variables in Solidity. If we use calldata, we can not reassign the values of the variable. Whereas for memory variables we can reassign the value.
What is the difference between constant and immutable variables in Solidity?
Constant variables are those that are declared and defined outside function (given constant value) whereas the immutable variables are those that are declared outside function but are given a constant value in the function.