Basics of Solidity

Basics of Solidity

Β·

8 min read

Hello Developers πŸ‘‹

In this article, we are going to learn the basics of solidity. Solidity is a new, high-level programming language created specifically to implement smart contracts on the Ethereum blockchain.

Table of contents

  1. Features of Solidity
  2. Access Modifiers
  3. memory vs storage
  4. Basic Types
  5. Variables
  6. Data Structures
  7. Conditionals and Loops
  8. Functions
  9. Error handling
  10. Inheritance

Features of Solidity

  1. Object-oriented programming language just like C++ or Java.
  2. Statically typed language like Typescript - helps to catch bugs in the early stage of the development process.
  3. Highly influenced by other programming languages such as Javascript, Python, and C++.
  4. Supports Inheritance of contracts and runs on Ethereum Virtual Machine(EVM).

Let's begin

Access Modifiers

There are four basic access modifiers used by variables and functions

  • Public: The value of the variables and functions can be accessed within the contract, child contract, and from external functions.

    contract MyContract {
      uint256 public myPublicVar;
      function getVar() public view returns(uint) { 
          return myPublicVar;
      }
    }
    
  • External: The value of the variables and functions can be accessed only from external functions.

    contract MyContract {
      uint256 external externalVar; 
      function getVar() external view returns(uint) { 
          return externalVar;
      }
    }
    
  • Internal: The value of the variables and functions can be accessed within the contract and child contract.

    contract MyContract  {
      uint256 internal internalVar; 
      function getVar() internal view returns(uint) { 
          return internalVar;
      }
    }
    
  • Private: The value of the variables and functions can be accessed within the same contract only.

    contract MyContract { 
      uint256 private privateVar; 
      function getVar() private view returns(uint) { 
          return privateVar;
      }
    }
    

memory vs storage

We can consider memory as a RAM and storage as a Hard disk. Any value that is stored in a memory variable will be temporary and will be wiped after execution stops while the value in the storage variable will be stored permanently on the blockchain and after the new execution, the variable will still have the previously stored value. The structure of storage can never be changed after the deployment of the contract but values inside it can be.

  • memory consumes comparatively less gas.
  • memory can only be used in methods, not at the contract level (state variable).
  • Solidity tries to declare variables in storage if the memory keyword is not used.
  • memory creates a new copy of the variable while storage points to the same location and can alter the values of the original variable.
Example
pragma solidity >=0.8.7;

// Creating a contract
contract MemoryContract
{ 
    // state variable (always in storage)
    uint[] public numbers;

    function addNumbers() public  {
        numbers.push(1);
        numbers.push(2);  
        // numbers = [1, 2]

        //1. Creating a new memory instance 
        uint[] memory myMemoryNumbers = numbers;
        myMemoryNumbers[0] = 10;
        // myMemoryNumber = [10, 2]
        // numbers = [1, 2]  


        //2. Creating a new storage instance
        uint[] storage myStorageNumbers = numbers;
        myStorageNumbers[0] = 10;
        // myStorageNumbers = [10, 2]
        // numbers = [10, 2] 
    } 
}

Basic Types

Example
// specify the version of solidity
pragma solidity >=0.8.7;   

// Creating a contract
contract BasicTypes {   

    // Boolean value
    bool public myBoolean = false;

    // Integer variable
    int32 public myInt = -60313;
    // unsigned Integer
    uint public myUInt = 60313; // alias for uint256

    // String variable
    string public myStr = "Saurabh";
    // Byte string (uses less gas)
    bytes32 public myByteString = "Bomble";

    // Byte variable
    bytes1 public b = "a";

    // Defining an enumerator
    enum myEnum { ACCEPTED, PENDING, REJECTED }  
}

Variables

Syntax

<type> <access modifier> <variable name>;

Types of variables

  • State variables - These variables are the variables that are stored permanently in the contract storage on the blockchain.
  • Local variables - These variables are not stored on blockchain but are created within a scope (e.g function) to help write business logic.
  • Global variables - These variables exist in the global namespace and can be invoked from anywhere in the contract. Global variables hold information about different transactions and blockchain properties. eg. block, msg, and tx.
Example
pragma solidity >=0.8.7;

contract VariableType {
    // State variables => stored on blockchain  
    uint myUint = 1;  
    //160-bit ethereum address 
    address public myAddress = 0xE9Bc4d002161aA50E15728ba19aDC3aA34a91D98; 

    constructor() {
        // msg is global variable
        myAddress = msg.sender  // address of user who deployed the contract
    }

    function getValue() public pure returns(uint) {
        myAddress = msg.sender  // address of current caller
        uint value = 1; // local variable
        return value;
    }
}

Data Structures

  • Array - We can store a collection of elements of the same type in an array. We can access, push, and pop an element from an array and can calculate the length of the array. Arrays in solidity are 0-indexed.

  • Struct - Struct allows us to create user-defined data types.

  • Mapping - Mapping in solidity is like unordered_map in C++ or dictionary in python. It is used to store key-value pairs. Here, keys can be of any built-in data type and values can be of any type including user-defined types.

Example
pragma solidity >=0.8.7;

contract DataStructure {
    // Array
    uint[] public myIntArray = [1, 2, 3];
    string[] public myStringArray = ["apple", "banana"];
    uint[][] public array2D = [ [1,2,3], [4,5,6] ];

    function addValue(string memory _value) public {
        myStringArray.push(_value);
    }

    function valueCount() public view returns(uint) {
        return myStringArray.length;
    } 

    // Mapping (key-value store)
    mapping(uint => string) public names;

    constructor() {
        names[1] = "Saurabh";
        names[2] = "John";
        names[3] = "Doe"; 
    }

    struct Book {
        string title;
        string author;
    }


    mapping(uint => Book) books; // key = id of book(unit), value = Book
    // Nested mapping
    mapping(address => mapping(uint => Book)) public myBooks; 

    function addBook(uint _id, string memory _title, string memory _author) public {
        books[_id] = Book(_title, _author);
    }

    function getBook(uint _id) public view returns(string memory, string memory){
        string memory _title = books[_id].title;
        string memory _author = books[_id].author; 
        return (_title, _author);
    }

    function addMyBook(uint _id, string memory _title, string memory _author) public {
        myBooks[msg.sender][_id] = Book(_title, _author);
    } 
}

Conditionals and Loops

  • Conditionals - Conditionals include if-else, ternary operator
  • For Loop - similar to c++ loop. Takes three arguments separated by a semi-colon. viz. initialization, condition, and iteration.

  • While loop - Execute a statement or block of statements repeatedly as far as the condition is true and once the condition becomes false the loop terminates.

  • Do while - Similar to while loop except the condition check happens at the end of the loop

Example
pragma solidity >=0.8.7;

contract ConditionalsLoops {
    // Loop
    uint[] public numbers = [1,2,3,4,5];
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function countEven() public view returns(uint count) {
        count = 0;
        for(uint i = 0; i < numbers.length; i++) {
            count += isEven(numbers[i]) ? 1 : 0;
        } 
    }

    // Conditionals (just like js)
    function isEven(uint _value) public pure returns(bool) {
        return _value % 2 == 0; 
    }

    function isOwner() public view returns(bool) {
        if(msg.sender == owner) return true;
        return false;
    }
}

Functions

  • View functions - This function can only read the state variables.

  • Pure functions - This function can neither read nor modify the state variables.

  • returns(type) - denotes return type of the function.

Example
pragma solidity >=0.8.7;

contract FunctionContract {  
    uint myUint = 1;

    function getValue() public pure returns(uint) {
        uint value = 1; 
        return value;
    }

    function getMyUint() public view returns(uint) { 
        return myUint;
    }
}

Error handling

  • revert(string memory msg) - aborts the execution and revert any changes done to the state. returns the msg.
Example
if(currentStatus == Status.VACANT) {
     revert("Currently occupied");
}
  • require(bool condition, string memory message) βˆ’ If the condition is not met, this method call reverts to its original state and throws a custom message.
Example
require(currentStatus == Status.VACANT, "Currently occupied");

Inheritance

Inheritance is one of the most important features of the object-oriented programming language. It makes code scalable, reusable, and more organized. Parent contract is known as base contract and the contract that inherits methods and properties from the base contract is called a derived contract.

  • A derived contract can access all public and internal features of the base contract.
  • Function overriding is allowed provided the function signature remains the same.
  • super keyword in the child contract helps to call the constructor of the parent.
  • Contracts can inherit other contracts by using the is keyword.
  • Function to be overridden by a child contract must be declared as virtual.
pragma solidity >=0.8.7; 

contract BaseContract {  
    uint public a;   
    uint public b;    

    // Wrong defination to be corrected in derived 
    function getDiff() public view virtual returns(uint) {
        return(a*b);
    }
} 

// Defining child contract 
contract DerivedContract is BaseContract{   
    constructor(uint _a, uint _b) {
        a = _a;
        b = _b;
        super;
    }

    // Correct defination with same signature
    function getDiff() public view virtual override returns(uint) {
        return(a-b);
    }
} 

// Defining calling contract
contract CallerContract {
    DerivedContract derivedContract;
    constructor(uint _val1, uint _val2) {
        derivedContract = new DerivedContract(_val1, _val2); 
    }

    function getDiffAandB() public view returns(uint) { 
        return derivedContract.getDiff();
    }
}

Conclusion

Thanks for readingπŸ™. That's it for this chapter. We have learned most of the basic concepts in solidity to get started to write our smart contracts.

Follow me on Hashnode for

  • Upcoming articles on Blockchain technology.
  • Web development (React.js+Nest.js)
  • App development (Flutter, React Native)

Finally

Β