Solidity Introduction

Table of Content:

Prerequisite

  1. Remix IDE - https://remix.ethereum.org/
  2. MetaMax Setup

First Smart Contract

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.1;

contract Contract01 {
    string public myString = "hello world";
}

Note: ‘public’ will generate automatic getter

Deploy your Smart Contract

  1. Open “Deploy & Run Transactions” plugin
  2. Injected web3
  3. Connect and Deploy
  4. Interact with contract

Blockchain Network

  1. Injected web3
  2. Javascript VM
  3. Web3 Provider

Solidity Basics- Variables

pragma solidity ^0.8.1;

contract Person {
    string public name;
    uint256 public age;
    address public paddress;

    function setDetail(string memory _name, uint256 _age)
        public
        returns (
            string memory,
            uint256,
            address
        )
    {
        name = _name;
        age = _age;
        paddress = msg.sender;
        return (name, age, paddress);
    }
}

Overflow and Underflow:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.7.0;

contract RolloverExample {
    uint8 public myUint8;

    function decrement() public {
        myUint8--;
    }

    function increment() public {
        myUint8++;
    }
}

Error:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;

contract RolloverExample2 {
    uint8 public myUint8;

    function decrement() public {
        myUint8--;
    }

    function increment() public {
        myUint8++;
    }
}

Unchecked:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.0;

contract RolloverExample2 {
    uint8 public myUint8;

    function decrement() public {
        unchecked {
            myUint8--;
        }
    }

    function increment() public {
        unchecked {
            myUint8++;
        }
    }
}

Mapping and Structs

pragma solidity ^0.8.4;

contract MappingsStructExample {
    struct Payment {
        uint256 amount;
        uint256 timestamp;
    }

    struct Balance {
        uint256 totalBalance;
        uint256 numPayments;
        mapping(uint256 => Payment) payments;
    }

    mapping(address => Balance) public balanceReceived;

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

Ethereum Denominations

  • A short reminder on Ethereum Denominations. Wei is the smallest, Ether = 10^18 Wei.
UnitWei ExpWei
wei11
Kwei10^31,000
Mwei10^61,000,000
Gwei10^91,000,000,000
Ether10^181,000,000,000,000,000,000

Deposit and withdraw

pragma solidity ^0.8.1;

contract SendMoneyExample {
    uint256 public balanceReceived;

    function receiveMoney() public payable {
        balanceReceived += msg.value;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

Withdraw

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.1;

contract SendMoneyExample1 {
    uint256 public balanceReceived;

    function receiveMoney() public payable {
        balanceReceived += msg.value;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /**withdrawMoney**/
    function withdrawMoney() public {
        address payable to = payable(msg.sender);
        to.transfer(getBalance());
    }
}

Withdraw to a account

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.1;

contract SendMoneyExample {
    uint256 public balanceReceived;

    function receiveMoney() public payable {
        balanceReceived += msg.value;
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function withdrawMoney() public {
        address payable to = payable(msg.sender);
        to.transfer(getBalance());
    }

    function withdrawMoneyTo(address payable _to) public {
        _to.transfer(getBalance());
    }
}

Smart Contract Life-cycle

Unsecure Smart Contract

pragma solidity ^0.8.1;

contract StartStopUpdateExample {
    function sendMoney() public payable {}

    function withdrawAllMoney(address payable _to) public {
        _to.transfer(address(this).balance);
    }
}

constructor and ownership

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;

contract StartStopUpdateExample {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function sendMoney() public payable {}

    function withdrawAllMoney(address payable _to) public {
        require(owner == msg.sender, "You cannot withdraw.");
        _to.transfer(address(this).balance);
    }
}

Pause and destroy

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.1;

contract StartStopUpdateExample {
    address public owner;
    bool public paused;

    constructor() {
        owner = msg.sender;
    }

    function sendMoney() public payable {}

    function setPaused(bool _paused) public {
        require(msg.sender == owner, "You are not the owner");
        paused = _paused;
    }

    function withdrawAllMoney(address payable _to) public {
        require(owner == msg.sender, "You cannot withdraw.");
        require(paused == false, "Contract Paused");
        _to.transfer(address(this).balance);
    }

    function destroySmartContract(address payable _to) public {
        require(msg.sender == owner, "You are not the owner");
        selfdestruct(_to);
    }
}

Complex Examples - Map and Struct

Send Money:

contract SendMoneyContract {
    struct Payment {
        uint256 amount;
        uint256 timestamp;
    }

    struct Balance {
        uint256 totalBalance;
        uint256 numPayments;
        mapping(uint256 => Payment) payments;
    }

    mapping(address => Balance) public balanceReceived;

    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /**
    new code
     */
    function sendMoney() public payable {
        balanceReceived[msg.sender].totalBalance += msg.value;

        Payment memory payment = Payment(msg.value, block.timestamp);
        balanceReceived[msg.sender].payments[
            balanceReceived[msg.sender].numPayments
        ] = payment;
        balanceReceived[msg.sender].numPayments++;
    }
}

Withdraw Money:**

contract SendMoneyWithWithdraw is SendMoneyContract {
    function withdrawMoney(address payable _to, uint256 _amount) public {
        require(
            _amount <= balanceReceived[msg.sender].totalBalance,
            "not enough funds"
        );
        balanceReceived[msg.sender].totalBalance -= _amount;
        _to.transfer(_amount);
    }

    function withdrawAllMoney(address payable _to) public {
        uint256 balanceToSend = balanceReceived[msg.sender].totalBalance;
        balanceReceived[msg.sender].totalBalance = 0;
        _to.transfer(balanceToSend);
    }
}

Known Facts

  • Mapping has no Length

  • Mappings have no length. It’s important to understand this. Arrays have a length, but, because how mappings are stored internally, they do not have a length.

Let’s say you have a mapping mapping(uint256 => uint) myMapping, then all elements myMapping[0], myMapping[1], myMapping[123123], … are already initialized with the default value. If you map uint256 to uint, then you map key-type “uint” to value-type “uint”.

  • Structs are initialized with their default value

Similar to anything else in Solidity, structs are initialized with their default value as well.

If you have a struct

struct Payment {
    uint256 amount;
    uint256 timestamp;
}

and you have a mapping mapping(uint256 => Payment) myMapping, then you can access already all possible uint256 keys with the default values. This would produce no error: myMapping[0].amount, or myMapping[123123].amount, or myMapping[5555].timestamp.

Similar, you can set any value for any mapping key:

myMapping[1].amount = 123 is perfectly fine.

Exception Handling

  • Require, Assert in Solidity
  • Try Catch
pragma solidity 0.6.12;

contract ExceptionExample {
    mapping(address => uint256) public balanceReceived;

    function receiveMoney() public payable {
        balanceReceived[msg.sender] += msg.value;
    }

    function withdrawMoney(address payable _to, uint256 _amount) public {
        if (_amount <= balanceReceived[msg.sender]) {
            balanceReceived[msg.sender] -= _amount;
            _to.transfer(_amount);
        }
    }
}

Add a Require

contract ExceptionRequireExample {
    mapping(address => uint256) public balanceReceived;

    function receiveMoney() public payable {
        balanceReceived[msg.sender] += msg.value;
    }

    function withdrawMoney(address payable _to, uint256 _amount) public {
        // added required
        require(
            _amount <= balanceReceived[msg.sender],
            "Not Enough Funds, aborting"
        );

        balanceReceived[msg.sender] -= _amount;
        _to.transfer(_amount);
    }
}

Add an Assert

pragma solidity 0.6.12;

contract ExceptionAssertExample {
    mapping(address => uint64) public balanceReceived;

    function receiveMoney() public payable {
        assert(msg.value == uint64(msg.value));
        balanceReceived[msg.sender] += uint64(msg.value);
        assert(balanceReceived[msg.sender] >= uint64(msg.value));
    }
}

Difference between require and assert

Use require() to:

  • Validate user inputs

  • Validate the response from an external contract ie. use require(external.send(amount))

  • Validate state conditions prior to executing state changing operations, for example in an owned contract situation

  • Generally, you should use require more often,

  • Generally, it will be used towards the beginning of a function.

    Use assert() to:

  • check for overflow/underflow

  • check invariants

  • validate contract state after making changes

  • avoid conditions which should never, ever be possible.

  • Generally, you should use assert less often

  • Generally, it will be use towards the end of your function.

  • Basically, assert is just there to prevent anything really bad from happening, but it shouldn’t be possible for the condition to evaluate to false.

Read more

Try/Catch

  • Sample code which throw error all time
//SPDX-License-Idenfitier: MIT
pragma solidity 0.8.4;

contract WillThrow {
    function aFunction() public pure {
        require(false, "Error message");
    }
}
  • Adding try-Catch: Recently introduced(v0.6+)
/* rest of the code*/
contract ErrorHandling {
    event ErrorLogging(string reason);

    function catchError() public {
        WillThrow will = new WillThrow();
        try will.aFunction() {
            //here we could do something if it works
        } catch Error(string memory reason) {
            emit ErrorLogging(reason);
        }
    }
}