Day 3 of #30DaysOfWeb3 : Lets solidify Solidity [Part 2]

This is Part 2 of our solidity chapter. Here we will learn about functions, modifiers, inheritance and more

Day 3 of #30DaysOfWeb3 : Lets solidify Solidity [Part 2]

Lets start this part with the heart of any programming language i.e Functions. We will also cover Inheritance, Modifiers, Events and Visibility in this part.

So Lets get Started.

Functions

Function TypeDescription
That modifies the state of blockchain( Creates a transaction )These functions write or modify data on the blockchain. For e.g. change the value of state variables, Send ether to some account, etc.
That don't modifies the state of blockchain( No Transaction )They are free to call as they don't change the state of the blockchain. We can define these functions with a pure/view keyword in the function definition.
  • View: They read state but promise not to modify the state.
    function getData() public view {};

  • Pure: They promise not to modify the state and not to read the state.
    function getData() public pure {};

  • Payable : These functions require you to pay some ether while calling them.
    function buyTickets() public payable {};

  • fallback : Its an un-named function generally used to send ether to contract.
    function () public payable {};

Modifying the state includes Writing to state variables, Emitting events, Creating other contracts, Sending ETH via calls, Calling other functions not marked as view/pure.

Reading the state includes Reading from state variables, Accessing balance of address, Accessing any of the member of tx, msg, block (except msg.data and msg.sig), Calling other functions not marked as pure.

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

contract LearnFunctions {
    // State variable to store a number
    uint public num;

    // You need to send a transaction to write to a state variable.
    function set(uint _num) public {
        num = _num;
    }

    // You can read from a state variable without sending a transaction.
    function viewFunc() public view returns (uint) {
        return num;
    }
    // Pure Functions dont even read from state
    function pureFunc(string memory _name) public pure returns (string memory) {
        return "Hi, I am pure Function! " + _name;
    }
}

Input Parameter & Return Types of Function :

Below are the points to be noted while working with functions :

  • Functions can return multiple values that can be named and assigned to their name.
function getMenu() public view returns (uint, string, bool) {
    return (5, "Nisha", true); 
};

// Return statement can be omitted when returning named
function getMenu() public view returns (uint a, string name, bool b) {
    x= 5;
    name = "Nisha",
    b = false;
};
  • Use destructing assignment when calling another function that returns multiple values.
function destructingAssigments() public returns ( uint, string, bool, uint, uint) {
        (uint i, string name, bool status) = returnMany();

        // Values can be skipped using commas
        (uint x, , uint y) = (4, 5, 6);

        return (i, name, status, x, y);
    }
  • Mappings cannot be used as parameters or return parameters of contract functions that are publicly visible.
  • Be cautious with dynamic arrays as parameter because of their gas consumption. They can exceed the block gas limit and transaction can fail.

Inheritance

Inheritance basically means requiring a contract into some other contract. The contract who required becomes child contract and other becomes parent.

Types of InheritanceDescription
SingleA contract that inherit methods from another contract using "is" keyword after contract name. All the methods except private of parents contract will now be available to child contract.
MultipleA contract that inherit from multiple different contract.
When we get same signature function in parent contracts then its order or inheritance will determine whose function will win over.
Last specified contract overrides its previous ones.
Because solidity searches for a function from right to left and depth first search based.

Syntax :

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

contract Base {
   // State variables
    string private privateVar = "my private variable";

    // Private function can only be called inside this contract
    function privateFunc() private pure returns (string memory) {
        return "private function called";
    }

    function testPrivateFunc() public pure returns (string memory) {
        return privateFunc();
    }

    // Internal function can be called inside this contract and inside contracts that inherit this contract
    function internalFunc() internal pure returns (string memory) {
        return "internal function called";
    }
}

contract Child is Base {
    // Inherited contracts do not have access to private functions and state variables of parent

    // Internal function call be called inside child contracts.
    function testInternalFunc() public pure override returns (string memory) {
        return internalFunc();
    }
}

Visibility

Visibility lets you control how and from where your state variables and function will be accessed/called.

Visibility TypeDescription
PrivateFunction and variables of private type can only be called from inside the contract
uint private socialSecurityNumber;
function privateTest() private {};
InternalFunction and variables of internal type can only be called from inside the contract or from child contract
uint internal parentScore;
function updateScoreCard() internal {};
PublicPublic function and variables can be called from anywhere
string public name;
function getMenu() public {};
ExternalFunctions of external type can be called only from outside i.e other contract or transaction. Variables can not be external
function withdrawFunds() external {};

Coming back to our Shopping Store. Lets define functions that we will need and try to understand few of them.

FunctionDescription
placeOrderA payable function for customers to place an order
cancelOrderfor customers to cancel an order and get their refund back.
withdrawFundsfor owner of store to withdraw funds from contract to his own address.
getStatisticsto get statistics of the store
getAllYourOrdersto get details of all order of a particular user(address)
calculateTotalany user can find out total amount required to pay for the supplied items with quantity
updateItemQuantityUsed to update inventory of items. While order placing it will decrease quantity and while cancelling an order it will increase the quantity
addItemto add new item to the inventory
updatePriceto update price of a item
updateSaleSatusstart or end the sale
updateSalePercentageupdates the discount percentage to be given during the sale period
increaseInventoryto increase the item in inventory
decreaseInventoryto decrease the item in inventory
updateStatusto update the status of the order
updateShippingAddressto update the shipping address of a user(address)
getShippingAddressto get the shipping address of a user
getOrderShippingAddressto get the shipping address by orderId
getItemDetailto get details of an item
getOrderDetailto get details of an order

getStatistics

 /* 
        Function Name : getStatistics
        Function Description : to get statistics of the store
        ---------------------------------------------------------------------------------------
        Inputs : No Inputs
        ----------------------------------------------------------------------------------------
        Return :
            - uint : total items sold till now
            - uint : total sales of the store till now
            - uint : total discounts given till now
        ----------------------------------------------------------------------------------------
        Constraints :
            - Only owner of contract can get these statistics
    */
     function getStatistics() external view onlyMerchant returns (uint, uint, uint){
        return (totalItemsSold, totalSales, discountsGiven);
    }
}

calculateTotal

 /* 
        Function Name : calculateTotal
        Function Description : any user can find out total amount required to pay
                               for the supplied items with quantity
        ---------------------------------------------------------------------------------------
        Inputs : 
            - uint[] _items : Array of itemsId
            - uint[] _qty : Array of quantities. It'll correspond exactly with items parameter. 
        ----------------------------------------------------------------------------------------
        Return :
            - uint : total cost required to pay in wei
        ----------------------------------------------------------------------------------------
        Constraints :
            - Items array and quantity array should be of same length
    */
    function calculateTotal(uint[] memory _items, uint[] memory _qty) public view returns (uint) {
        require(_items.length == _qty.length);
        uint sum;
        for(uint i=0;i<_items.length;i++){
            sum += items[_items[i]].price * _qty[i];
        }
        return sum;
    }
}

Place Order :


 /* 
        Function Name : placeOrder
        Function Description : for customers to place an order with paying in ethers.
        ---------------------------------------------------------------------------------------
        Inputs : 
            - uint[] _items : Array of itemsId to buy
            - uint[] _qty : Array of quantities. It'll correspond exactly with items parameter. 
        --------------------------------------------------------------------------------------------
        Return :
            - uint : an orderId
        ------------------------------------------------------------------------------------------
        Constraints :
            - Mentioned quantity of each item should be available in inventory
            - Amount sent should be equal or more than total cost of all items
    */
    function placeOrder(uint[] memory _items, uint[] memory _qty) public payable returns (uint) {
        uint totalQty;
        uint sum;
        for(uint i=0;i<_items.length;i++){
            totalQty += _qty[i];
            sum += items[_items[i]].price * _qty[i];
            require(items[_items[i]].availableQty >= _qty[i], "Required Quantity Unavailable");
        }
        // uint sum = calculateTotal(uint[] _items, uint[] _qty);
        require(msg.value >= sum, "Insufficent Balance to place order");

        uint discount;
        uint netTotal = sum;
        uint orderId = orders.length + 1;
        if(isSaleOn){
            discount = discountPercentage * sum / 100;
            netTotal = sum - discount;
        }
        Order memory newOrder = Order(orderId, _items, _qty, sum, discount, netTotal, block.timestamp, Status.Accepted, msg.sender);
        orders.push(newOrder);
        orderShipping[orderId] = shippingAddress[msg.sender];

        updateItemQuantity(_items, _qty, 0);
        totalSales += netTotal;
        totalItemsSold += totalQty;
        discountsGiven += discount;
        customerOrders[msg.sender].push(newOrder);
        return orderId;
    }

updateItemQuantity


 /* 
        Function Name : updateItemQuantity
        Function Description : Used to update inventory of items. While order placing it will 
                               decrease quantity and while cancelling an order it will 
                               increase the quantity
        ---------------------------------------------------------------------------------------
        Inputs : 
            - uint[] _items : Array of itemsId
            - uint[] _qty : Array of quantities. It'll correspond exactly with items parameter. 
            - uint _type : an integer to denote whether to increase the quantity or decrease
        ----------------------------------------------------------------------------------------
        Return : 
            - bool : status of inventory update
        ----------------------------------------------------------------------------------------
        Constraints :
            - internal : this function can only be called by other functions of this contract 
                         or child contract. User cannot call this function directly
    */
    function updateItemQuantity(uint[] memory _items, uint[] memory _qty, uint _type) internal {
        for(uint i=0;i<_items.length;i++){
            if(_type == 0) items[i].availableQty = items[i].availableQty - _qty[i];
            else items[i].availableQty = items[i].availableQty + _qty[i];
        }
    }
}

withdrawFunds


  /* 
        Function Name : withdrawFunds
        Function Description : for owner of store to withdraw funds from contract to his own address.
        ---------------------------------------------------------------------------------------------
        Inputs : No Inputs
        ---------------------------------------------------------------------------------------------
        Return : Returns Nothing
        ---------------------------------------------------------------------------------------------
        Constraints :
            - Only owner of contract can call this
    */
    function withdrawFunds() external onlyMerchant{
        payable(merchant).call{value : address(this).balance}("");
    }
}

Modifiers

Function Modifiers are used to change the behaviour of a function before execution or after execution.Often used for checking condition prior or restrict access or validate inputs. The underscore used inside modifiers denotes that function code can now be executed.

So if a function is encountered with a modifier in its signature then modifier's code will be executed first and then when underscore is encountered in modifier then function's body gets executed.

Syntax :

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

contract Store {
  address public owner;
  uint a;
  // Example 1
  modifier onlyowner() {
          require(msg.sender == owner);
          _;
      }

 // Example 2
 modifier afterEffects(uint _b) {
          _;
          a = a+ _b;
   }

  function callModifier1() public onlyOwner {
      ... Do something 
  };

  function callModifier2(uint _b) public afterEffects(_b) {
      .... Do something
  }
}

Lets define our shopping store's modifiers that we will need.

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

contract Store {

// For functions that should only be called by contract owner
modifier onlyMerchant() {
        require(msg.sender == merchant);
        _;
    }
}

Constructor

A constructor is a function that is only executed upon the creation of a contract i.e when you deploy on the network and use it to run contract initialization code. It is declared using the "constructor" keyword and can accept parameters just like normal functions.

Lets define our shopping store's constructor that will set the storeName and owner address upon contract deployment.


contract Store {
    string public storeName;
    address public merchant; 

    constructor(string _storeName) {
        merchant = msg.sender;
        storeName = _storeName;
    }

}

Events

We can define events in solidity and emit them upon certain scenario. A transaction receipt containing log entries of events and actions is produced after execution of the transaction. Events are useful for DApp services, which can “watch” for specific events and perform specific action on the UI. An event generated is not accessible from within contracts.

Lets define our shopping store events and emit them in particular functions :

contract Store {
      event OrderPlaced(uint orderId, uint amount);
      event OrderCancelled(uint orderId, uint amount);
      event SaleStarted(uint percentage);
      event SaleEnded();

      function placeOrder(uint[] memory _items, uint[] memory _qty) public payable returns (uint) {
           ......
          emit OrderPlaced(orderId, netTotal);
          return orderId;
      }

      function cancelOrder(uint _orderId) public {
         ......
        payable(msg.sender).call{value : order.netTotal}("");
        emit OrderCancelled(_orderId, netTotal);
      }

     function updateSaleSatus() public onlyMerchant {
        isSaleOn = !(isSaleOn);
        if(isSaleOn) {
          emit SaleStarted(discountPercentage);
        }
        else {
          emit SaleEnded();
        }
    }
}

You can visit my github repo to get full source code of the smart contract to practise. You can use remix IDE to quickly play around with the contract and see if its working as expected. Go checkout and let me know if you found any suggestions or bugs. I will be happy to learn from you all.

github.com/nishajakhar/my-shopping-store

Thank You so much for being till here. Leave a Like/Comment if this blog was helpful to you in any way.

See you in the next chapter. Stay Tuned :)