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
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 Type | Description |
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 Inheritance | Description |
Single | A 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. |
Multiple | A 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 Type | Description |
Private | Function and variables of private type can only be called from inside the contract uint private socialSecurityNumber; function privateTest() private {}; |
Internal | Function and variables of internal type can only be called from inside the contract or from child contract uint internal parentScore; function updateScoreCard() internal {}; |
Public | Public function and variables can be called from anywhere string public name; function getMenu() public {}; |
External | Functions 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.
Function | Description |
placeOrder | A payable function for customers to place an order |
cancelOrder | for customers to cancel an order and get their refund back. |
withdrawFunds | for owner of store to withdraw funds from contract to his own address. |
getStatistics | to get statistics of the store |
getAllYourOrders | to get details of all order of a particular user(address) |
calculateTotal | any user can find out total amount required to pay for the supplied items with quantity |
updateItemQuantity | Used to update inventory of items. While order placing it will decrease quantity and while cancelling an order it will increase the quantity |
addItem | to add new item to the inventory |
updatePrice | to update price of a item |
updateSaleSatus | start or end the sale |
updateSalePercentage | updates the discount percentage to be given during the sale period |
increaseInventory | to increase the item in inventory |
decreaseInventory | to decrease the item in inventory |
updateStatus | to update the status of the order |
updateShippingAddress | to update the shipping address of a user(address) |
getShippingAddress | to get the shipping address of a user |
getOrderShippingAddress | to get the shipping address by orderId |
getItemDetail | to get details of an item |
getOrderDetail | to 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 :)