A Friendly Beginner's Guide to Solidity (Part Two)

A Friendly Beginner's Guide to Solidity (Part Two)

This is the continuation of A Friendly Beginner's guide to Solidity (Part One). We looked at the key topics such as Ethereum, Solidity, Ethereum Virtual Machine and Smart Contract, then we dived deep fully into Solidity.

We will be looking into more advanced concept in solidity in this article.

Functions

We looked at functions in the part one but we will take a deeper look into it.

There are various kinds of functions in solidity, they are

  • Public functions
  • Private functions
  • Internal functions
  • external functions

Public functions - These are functions that can be called by any contract, this is not advisable, it exposes your function, especially if values can be changed as a result of calling the function. it exposes your contract to attacks. The public keyword is added after the function parameters.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   function createCar(uint _amount, string memory _name) public returns (Car _car) {     
        Car newCar = Car(_amount,_name);
        return newCar;     
    }
}

In solidity, we must specify the return datatype of the data the function will return, as seen in the function above.

Private functions - These are functions that can only be called within your contract. It is not accessible to outside functions. A good rule of thumb is to make your contract private by default. The private keyword is added after the function parameters.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   function createCar(uint _amount, string memory _name) private returns (Car _car) {     
        Car newCar = Car(_amount,_name);
        return newCar;     
    }
}

Internal functions - These are private functions but they are accessible to contracts that inherit from your contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   function createCar(uint _amount, string memory _name) internal returns (Car _car) {     
        Car newCar = Car(_amount,_name);
        return newCar;     
    }
}

External functions - These are functions that are only accessible outside your contract, if you need to expose a function to the public, an external function is a good idea.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   function createCar(uint _amount, string memory _name) external returns (Car _car) {     
        Car newCar = Car(_amount,_name);
        return newCar;     
    }
}

Returning Multiple Values

In most cases, functions return values and the values returned can be more than one, this depends on what the function is trying to achieve. We have seen in previous examples, how to return a single value, let's also see an example of how to return multiple values. The data type for all the return values must also be specified when we are returning one or more values

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

function createCar(uint _amount, string memory _name) external returns (uint amount, string name, Car _car) {     
        Car newCar = Car(_amount,_name);
        return (_amount, _name, newCar );   
    }
}

Conditional statements and Loops

If Statement

If statements are conditional statement used to check if a statement is true or false, its usage in solidity is similar to other languages i.e JavaScript

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   uint newAmount = 1000;

   function createCar(uint _amount, string memory _name) external returns (Car _car) {     
         if (_amount > 1000){
             newAmount = _amount;
         }
        Car newCar = Car(newAmount,_name);
        return newCar;   
    }
}

The logic above checks if the _amount is greater than 1000, if it is true it changes the newAmount variable to the amount passed into the function, if it is false it uses the default value assigned to the newAmount Variable.

For Loops

For loops allows us to execute a statement multiple times based on a condition. For Loops in solidity are similar to other languages i.e javascript

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {

    struct Car { 
        uint amout; 
        string name; 
    }    

    string[] carNamesList = ["BMW","Toyota","Honda"];
    uint carAmountList = [1000,2000,2000];
    Car[] allCars;

   for (uint i = 0; i < carNamesList.length; i += 1) { 
        Car newCar = Car(carAmountList[i], carNamesList[i]);
        allCars.push(newCar);
   }

}

We created a carNamesList and carAmountList array, which contains list of the car names and car amounts, then we used the for loop to go through the arrays and created new cars and added it to the allCars array.

Require

Require is similar to if statement, it evaluates if a statement is true or false, if the statement is true, the lines below the require statement is evaluated, if it is false, it throws an error.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    struct Car { 
        uint amout; 
        string name; 
    }

   uint newAmount = 1000;

   function createCar(uint _amount, string memory _name) external returns (Car _car) {     
        require(_amount > newAmount);
        Car newCar = Car(newAmount,_name);
        return newCar;   
    }
}

The logic above checks if the _amount is greater than the amount set in the newAmount variable, if it is true, the lines below executes, if it is false, it throws an error.

Generating random numbers

To generate a random number, we will need the ethereum hash function known as keccak256, it is a version of SHA3. This hash function returns a 256-bit hexadecimal number and the hash function is expecting a single parameter of type bytes.

In order to use it we need to encode the string we will pass to the function using abi.encodePacked, this function will convert the string to the type bytes.

Lets see it in action.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {

 keccak256(abi.encodePacked("aaaab"));
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5

}

Imports

When we have our contracts in different files and we want to use them in another file, we use imports.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// NFT contract to inherit from.
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyNFT is ERC721 {

}

We imported a contract above that can be used to create an NFT token with less code.

OpenZeppelin offers open source OpenZeppelin Contracts, written in Solidity, for building secure smart contracts. OpenZeppelin Contracts uses ERC standards for Ethereum-based tokens that can be used in many types of projects. In an effort to minimize cyber risk associated with building secure smart contracts on Ethereum or other blockchains, OpenZeppelin Contracts are continually audited and tested.

Inheritance

This is a popular concept in object oriented programing, where a class inherits the properties of another class. In solidity Contract can inherit from other contracts.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Hello {
  function HelloWorld() public returns (string memory) {
    return "Hello World";
  }
}

contract HelloWord is Hello {
  function AnotherHelloWorld() public returns (string memory) {
    return "Another Hello World";
  }
}

The first contract has access to only the HelloWorld function, the second contract has access to both the HelloWorld function and the AnotherHelloWord function.

The is is the keyword for the inheritance.

Events

Events are a medium for us to communicate what is happening on the blockchain to the client-side or front end of our application. What makes it possible for the frontend to know when a change has occurred on the blockchain is called events.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    event  CarCreated(uint _amount, string memory _name, Car _car); 

    function createCar(uint _amount, string memory _name) internal returns (Car) {     
         Car newCar = Car(_amount,_name);
         emit CarCreated(_amount,_name, _car);
         return newCar;     
  }


}

we used the event keyword to create the CarCreated event. In the createCar function, we emit the CarCreated event. The client-side gets the event and does something with it.

// The frontend implementation
YourContract.CarCreated(function(error, result) {
   // do something when car is created
})

Addresses

The Ethereum blockchain is made up of accounts, these accounts works similarly to bank accounts. An account has a balance of Ether (the currency used on the Ethereum blockchain), and you can send and receive Ether payments to other accounts.

Each account has an address, which you can think of like a bank account number. It's a unique identifier that points to that account, and it looks like this: 0x1443498Ef86df975D8A2b0B6a315fB9f49978998 (This is my address, you can send some ether). Thank you in advance.

The address is the user identity on the blockchain(Ethereum Blockchain).

Mappings

Addresses are one of the most important things we map our data to on the Ethereum blockchain, we can map things like account balance(Ether), NFTs, userIds, and other things which belong to us on the blockchain to our address.

For example, to match our address to our account balance, we can do this

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {

    mapping(address => uint) public accountBalance;

}

The name of the mapping we created above is accountBalance. we are mapping address to uint using the mapping keyword, address is a datatype, just like uint.

Msg.sender

To get access to our address, we need access msg.sender, it is a global variable available to all functions, msg.sender outputs the user's address.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
    uint amount = 5000;
    mapping(address => uint256) public accountBalance;


    accountBalance(msg.sender) = amount;
    // mapping is done, now we can access the account balance 
    // from the accountBalance mapping when we need it

}

We assigned the amount to our address, and we got our address from msg.sender.

Constructors

It is an optional special function that has the same name as the contract. it will get executed only one time when the contract is first created.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
   constructor(
        string[] memory carList,
    )

  //Do other things

}

The carList array will only be created the first time the contract is created.

Function modifiers

They are kind of half functions that are used to modify other functions, it is used mostly to check some requirements before executing the function.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloWorld {
   address private  _owner;
   constructor(
         _owner = msg.sender;
    )

   // modifier function
   modifier onlyOwner() {
    require(msg.sender == _owner);
    _;
  }

  function doSomething() public onlyOwner {
      // This function only runs when the onlyOwner 
      //  modifier question evaluates as true
  }

}

We created a private variable called _owner which has an address datatype, we assigned our address to the variable in the constructor which only runs one, which means it is storing the address of the owner of the contract.

We created a modifier function called onlyOwner, using the modifier keyword. In the modifier function, we are checking if the address matches the address we initially stored which is the address of the owner of the contract.

We assigned the modifier function to the doSomething function, this means that the function will only run if the person trying to access it is the owner of the contract.

In the Part 3 of this article we will be using all these concept to build an NFT contract and front end application.

I hope you found this tutorial useful.

Thank you for reading.

References