DEV Community

Cover image for A Friendly Beginner's Guide to Solidity (Part Two)
ADESANYA JOSHUA AYODEJI
ADESANYA JOSHUA AYODEJI

Posted on • Updated on

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 pure returns (Car memory _car) {    
        Car memory newCar = Car(_amount,_name);
        return newCar;     
    }
}

Enter fullscreen mode Exit fullscreen mode

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 pure returns (Car memory _car) {   
        Car memory newCar = Car(_amount,_name);
        return newCar;     
    }
}

Enter fullscreen mode Exit fullscreen mode

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 pure returns (Car memory _car) {  
        Car memory newCar = Car(_amount,_name);
        return newCar;     
    }
}

Enter fullscreen mode Exit fullscreen mode

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 pure returns (Car memory _car) {  
        Car memory newCar = Car(_amount,_name);
        return newCar;     
    }
}

Enter fullscreen mode Exit fullscreen mode

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 pure returns (uint amount, string memory name, Car memory _car) {   
        Car memory newCar = Car(_amount,_name);
        return (_amount, _name, newCar );   
    }
}


Enter fullscreen mode Exit fullscreen mode

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 memory _car) {  
         if (_amount > 1000){
             newAmount = _amount;
         }
        Car memory newCar = Car(newAmount,_name);
        return newCar;   
    }
}

Enter fullscreen mode Exit fullscreen mode

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;

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



}

Enter fullscreen mode Exit fullscreen mode

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 view returns (Car memory _car) {  
        require(_amount > newAmount);
        Car memory newCar = Car(newAmount,_name);
        return newCar;   
    }
}


Enter fullscreen mode Exit fullscreen mode

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 {

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

}

Enter fullscreen mode Exit fullscreen mode

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 {

}

Enter fullscreen mode Exit fullscreen mode

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 pure returns (string memory) {
    return "Hello World";
  }
}

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


Enter fullscreen mode Exit fullscreen mode

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 {
    struct Car { 
        uint amout; 
        string name; 
    }

    event CarCreated(uint _amount, string  _name, Car _car); 

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


}


}

Enter fullscreen mode Exit fullscreen mode

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
}) 
Enter fullscreen mode Exit fullscreen mode

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;

}

Enter fullscreen mode Exit fullscreen mode

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

}

Enter fullscreen mode Exit fullscreen mode

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

}

Enter fullscreen mode Exit fullscreen mode

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
  }

}

Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)