Learn about the essential concepts of a Smart Contract

Anass ELABBADI
Coinmonks
9 min readJun 1, 2022

--

In this article, I’ll take you through a walkaround of all the essential concepts of a Smart Contract, that will help you build your own and have an overview of how they work.

I assume you have some experience programming in another language (e.g. Javascript).

Table of Contents

  • Basics
  • Optimization
  • Time units
  • Function modifiers
  • Saving Gas With ‘View’ Functions
  • REVIEW FIRST PART
  • The payable Modifier
  • Random Numbers
  • Tokens on Ethereum
  • Transfer Logic
  • Assert vs Require

Basics

Starting with the absolute basics:

Solidity’s code is encapsulated in contracts. A contract is the fundamental building block of Ethereum applications:

- All variables and functions belong to a contract, and this will be the starting point of all your projects.

- All solidity source code should start with a “version pragma” — a declaration of the version of the Solidity compiler this code should use.

pragma solidity >=0.5.0 <0.6.0;

contract HelloWorld {

}

- State variables are permanently stored in contract storage. This means they’re written to the Ethereum blockchain. Think of them like writing to a DB.

contract Example {

// This will be stored permanently in the blockchain

uint myUnsignedInteger = 100;

}

- When you want a collection of something, you can use an array. There are two types of arrays in Solidity: fixed arrays and dynamic arrays:

// Array with a fixed length of 2 elements:

uint[2] fixedArray;

// another fixed Array, can contain 5 strings:

string[5] string Array;

// a dynamic Array — has no fixed size, can keep growing:

uint[] dynamicArray;

- You can declare an array as public, and Solidity will automatically create a getter method for it.

Person[] public people;

  • storage — variable is a state variable (store on blockchain)
  • memory — variable is in memory and it exists while a function is being called
  • calldata — special data location that contains function arguments, only available for external functions

- We’re also providing instructions about where the _name variable should be stored- in memory. This is required for all reference types such as arrays, structs, mappings, and strings.

- If a function doesn’t change any values or write anything. We could declare it as a VIEW

function sayHello() public view returns (string memory){ … }

- Solidity also contains pure functions, which means you’re not even accessing any data in the app. Consider the following:

function _multiply(uint a, uint b) private pure returns (uint) {

return a * b;

}

Note: It may be hard to remember when to mark functions as pure/view. Luckily the Solidity compiler is good about issuing warnings to let you know when you should use one of these modifiers.

- Ethereum has the hash function keccak256 built in, which is a version of SHA3. A hash function basically maps an input into a random 256-bit hexadecimal number. A slight change in the input will cause a large change in the hash.

keccak256(abi.encodePacked(“aaaab”));

- Events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be ‘listening’ for certain events and take action when they happen.

- In Solidity, there are certain global variables that are available to all functions. One of these is msg.sender, which refers to the address of the person (or smart contract) who called the current function.

Note: In Solidity, function execution always needs to start with an external caller. A contract will just sit on the blockchain doing nothing until someone calls one of its functions. So there will always be a msg.sender.

- Internal is the same as private, except that it’s also accessible to contracts that inherit from this contract.

- External is similar to public, except that these functions can ONLY be called outside the contract

  • For our contract to talk to another contract on the blockchain that we don’t own, first we need to define an interface.

- In Solidity you can return more than one value from a function.

- uint is an alias for uint256

- Remember with strings, we have to compare their keccak256 hashes to check equality

- A function modifier looks just like a function, but uses the keyword modifier instead of the keyword function. And it can’t be called directly like a function can — instead we can attach the modifier’s name at the end of a function definition to change that function’s behavior.

Optimization

- If you have multiple uints inside a struct, using a smaller-sized uint when possible will allow Solidity to pack these variables together to take up less storage.

- You’ll also want to cluster identical data types together (i.e. put them next to each other in the struct) so that Solidity can minimize the required storage space. For example, a struct with fields uint c; uint32 a; uint32 b; will cost less gas than a struct with fields uint32 a; uint c; uint32 b; because the uint32 fields are clustered together.

Time units

Solidity also contains the time units seconds, minutes, hours, days, weeks and years. These will convert to a uint of the number of seconds in that length of time.

So, 1 minutes is 60, 1 hours is 3600 (60 seconds x 60 minutes), 1 days is 86400 (24 hours x 60 minutes x 60 seconds), etc.

Function modifiers

- You can see here that the olderThan modifier takes arguments just like a function does. And that the driveCar function passes its arguments to the modifier.

Saving Gas With ‘View’ Functions

- view functions don’t cost any gas when they’re called externally by a user.

- This is because view functions don’t actually change anything on the blockchain — they only read the data. So marking a function with view tells web3.js that it only needs to query your local Ethereum node to run the function, and it doesn’t actually have to create a transaction on the blockchain (which would need to be run on every single node, and cost gas).

Note: If a view function is called internally from another function in the same contract that is not a view function, it will still cost gas. This is because the other function creates a transaction on Ethereum, and will still need to be verified from every node. So view functions are only free when they’re called externally.

Note: memory arrays must be created with a length argument (in this example, 3). They currently cannot be resized like storage arrays can with array.push(), although this may be changed in a future version of Solidity.

REVIEW FIRST PART :

1) We have visibility modifiers that control when and where the function can be called from: private means it’s only callable from other functions inside the contract; internal is like private but can also be called by contracts that inherit from this one; external can only be called outside the contract; and finally public can be called anywhere, both internally and externally.

2) We also have state modifiers, which tell us how the function interacts with the BlockChain:

  • view tells us that by running the function, no data will be saved/changed.
  • Pure tells us that not only does the function not save any data to the blockchain, but it also doesn’t read any data from the blockchain.

Both of these don’t cost any gas to call if they’re called externally from outside the contract (but they do cost gas if called internally by another function).

3) Then we have custom modifiers: onlyOwner and aboveLevel, for example. For these we can define custom logic to determine how they affect a function.

function test() external view onlyOwner anotherModifier { /* … */}

The payable Modifier

- Payable functions are part of what makes Solidity and Ethereum so cool — they are a special type of function that can receive Ether.

Note: If a function is not marked payable and you try to send Ether to it as above, the function will reject your transaction.

- It is important to note that you cannot transfer Ether to an address unless that address is of type address payable.

- address(this).balance will return the total balance stored on the contract.

Random Numbers

- The best source of randomness we have in Solidity is the keccak256 hash function.

Explanation:

What this would do is take the timestamp of now, the msg.sender, and an incrementing nonce (a number that is only ever used once, so we don’t run the same hash function with the same input parameters twice).

It would then “pack” the inputs and use keccak to convert them to a random hash. Next, it would convert that hash to a uint, and then use % 100 to take only the last 2 digits. This will give us a totally random number between 0 and 99.

Tokens on Ethereum

A token on Ethereum is basically just a smart contract that follows some common rules — namely it implements a standard set of functions that all other token contracts share, such as:

transferFrom(address _from, address _to, uint256 _tokenId) and balanceOf(address _owner).

- Basically a token is just a contract that keeps track of who owns how much of that token, and some functions so those users can transfer their tokens to other addresses.

- ERC721 tokens are not interchangeable since each one is assumed to be unique, and are not divisible. You can only trade them in whole units, and each one has a unique ID. So these are a perfect fit for making our zombies tradeable.

Note: Note that using a standard like ERC721 has the benefit that we don’t have to implement the auction or escrow logic within our contract that determines how players can trade / sell our zombies. If we conform to the spec, someone else could build an exchange platform for crypto-tradable ERC721 assets, and our ERC721 zombies would be usable on that platform. So there are clear benefits to using a token standard instead of rolling your own trading logic.

Transfer Logic

1- ​​The first way is the token’s owner calls transferFrom with his address as the _from parameter, the address he wants to transfer to as the _to parameter, and the _tokenId of the token he wants to transfer.

2- The second way is the token’s owner first calls approve with the address he wants to transfer to, and the _tokenId . The contract then stores who is approved to take a token, usually in a mapping (uint256 => address). Then, when the owner or the approved address calls transferFrom, the contract checks if that msg.sender is the owner or is approved by the owner to take the token, and if so it transfers the token to him.

Assert vs Require

  • Assert is similar to require, where it will throw an error if false. The difference between assert and require is that require will refund the user the rest of their gas when a function fails, whereas assert will not. So most of the time you want to use require in your code; assert is typically used when something has gone horribly wrong with the code (like a uint overflow).

Congratulations, You now have an idea of how everything works, So in the next article (link below), we will see how you can interact with the smart contract from a front end application using web3.js.

--

--

Anass ELABBADI
Coinmonks

I spend my days with my hands in many different areas of web development from WEB 2 (frontend, backend, databases) toWEB 3 (blockchain, crypto, P2E,…)