Solidity

Feb 25th, 2022

References

Variables & Scopes

We can divide variables in Solidity into three classes:

// 1. Fixed Size Types
bool isTrue = true;
int8 number1 = 12;
uint number2 = 12;
address address = 0x.....;
bytes32 name1 = "name1";

// 2. Dynamic Size Types
string name2 = "name2";
bytes name1 = "name1";
uint[] array = [1,2,3,4,5];

mapping(uint => string) list1;
list1[3] = "value3";

mapping(uint => address) list2;
list2[4] = address;

mapping(uint => Human) list3;

// 3. User Defined Value Types
struct Human {
  uint ID;
  string name;
  string surname;
  uint age;
  address address;
}

Human person1;
person1.ID = 2121212212121;
person1.name = "firstname";
person1.surname = "lastname";
person1.age = 25;
person1.address = 0x.....;


enum trafficLights {
  RED,
  YELLOW,
  GREEN
} 

  1. Sabit Boyutlu Türler (Fixed Size Types)
    • bool: Mantık değişkenidir. true ve false değerlerini tutar. Varsayılan olarak false‘dur.
    • int: Sayıları tuttuğumuz değişkendir. boyutu 256 byte’dır. int256 ile aynıdır, -2^256 to 2^256
    • uint: Sayıları tuttuğumuz değişkendir. int‘den farkı negatif sayıların dahil olmamasıdır. 0 to 2^256
    • address: Cüzdan adreslerin tuttuğumuz değişkendir. Boyutu sabit 20 bytes’dır.
    • bytes32: String değerleri hexadecimal olarak tutan değişkendir. 1-32 aralığında tanımlayabiliriz.
  2. Dinamik Boyutlu Türler (Dynamic Size Types)
    • string: String değerleri tuttuğumuz değişkendir.
    • bytes: String değerleri hexadecimal olarak tutan değişkendir.
    • type[]: Verileri liste olarak tuttuğumuz değişkendir. Örneğin: [1,2,3,4,5]
    • mapping(type => type): Verileri key,value olarak tutmamızı sağlayan değişkendir.
  3. Kullanıcı Tanımlı Türler (User Defined Value Types)
    • struct
    • enum

Operatörler:

  • ! (mantıksal olumsuzlama)
  • && (mantıksal bağlaç, “ve”)
  • || (mantıksal ayrılma, “veya”)
  • == (eşitlik)
  • != (eşitsizlik)

Ether Birimleri

  • 1 wei = 1
  • 1 gwei = 10^9 wei = 1000000000
  • 1 ether = 10^18 wei = 1000000000000000000

Zaman Birimleri

  • 1 seconds = 1
  • 1 minutes = 60
  • 1 hours = 3600
  • 1 days = 86400
  • 1 weeks = 604800

Solidity’de 3 tür değişken vardır:

  1. Yerel (Local)
    • Blockchain’de saklanmaz.
    • Fonksiyon içerisinde bildirilir.
  2. Durum (State)
    • Fonksiyon dışında bildirilir.
    • Blockchain’de saklanır.
  3. Global
    • Blockchain ile ilgili değişkenler
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Variables {

    // State Variables

    string public bestClub = "itu blockchain";
    uint256 public founded = 2018;
    bool private rich = false;

    function showLocalVariables() public pure returns (uint) {
        // Local Variables
        uint number = 17;
        return number;
    }

    function showGlobalVariablesBlockNumber() public view returns (uint) {
        return block.number;
    }

    function showBlockVariablesMsgSender() public view returns (address) {
        return msg.sender;
    }

    function add(int256 _number) public pure returns (int256) {
        // Local Variables
        int256 minus = -1;
        int256 lucky = 33;

        return _number + minus + lucky;
    }
    
    // Global Variables
    // block.difficulty (uint)	Current block difficulty
    // block.gaslimit (uint)	Current block gaslimit
    // block.number (uint)	Current block number
    // block.timestamp (uint)	Current block timestamp as seconds since unix epoch
    // msg.data (bytes calldata)	Complete calldata
    // msg.sender (address payable)
}

Functions, Function Modifiers and Function Visibility

Function Modifiers, Fonksiyon Niteleyicileri

view: Fonksiyonun blokchain’den veri okuyacağını fakat üzerinde değişiklik yapmayacağını bildirir.

contract View {
    uint x = 7;

    function show()
        public
        view
        returns(uint)
    {
        return x;
    }
}

pure: Fonksiyonun blokchain’den hem veri okumayacağını hem de değişiklik yapmayacağını bildirir. State içerisinde herhangi bir şey okumayacak ve yazmayacak, sadece Contract içinde çalışacak bir işlev.

contract Pure {
    function add(uint x, uint y)
        public
        pure
        returns(uint)
    {
        return x + y;
    }
}

Function Visibility, Fonksiyon Görünürlükleri

  • public: Herhangi bir akıllı kontrat ve hesap çağırabilir.
  • private: Yalnızca fonksiyonun tanımlı olduğu kontratın içerisinde çağırılabilir.
  • internal: Tanımlı olduğu kontrat ile birlikte miras olarak alındığı kontrat tarafından da çağırılabili.,
  • external: Yalnızca diğer akıllı kontratlar ve hesaplar tarafından çağıralabilir.
// Functions
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Functions {

   // doğrudan erişebiliyorsun
   uint public luckyNumber1 = 3;

   uint luckyNumber = 7;

   function showNumber() public view returns(uint) {
      return luckyNumber;
   }

   function setNumber( uint _newNumber) public {
      luckyNumber = _newNumber;
   }

   function nothing() public pure returns(uint, bool, bool) {
      return (5, true, false);
   }

   function nothing2() public pure returns(uint x, bool y, bool z) {
      // değişkenler tanımlandığı için return kullanmaya gerek yok
      x = 5;
      y = true;
      z = false;
   }
}
// Function Modifiers
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Functions {
    
   //View

   uint public number = 3;

   function addToX(uint y) public view returns(uint){
      return number + y;
   }

   function addAndView(uint a, uint b) public view returns (uint) {
      return a * (b + 42) + block.timestamp;
   }

   //Pure

   function addAndPure(uint a, uint b) public pure returns (uint) {
      return a * (b + 42);
   }

   function hello() pure public returns(string memory) {
      return 'Hello World!';
   }

}
// Function Visibility
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Functions {
    
   // Public, External, Internal, Private
   // Public: Bu fonksiyonu hem dışarıdan kullanıcılar çağırabilir hemde kontrat çağırabilir

   function add(uint a, uint b) public pure returns(uint){
      return a + b;
   }

   function add2(uint c, uint d) public pure returns(uint){
      return add(c,d);
   }

   function publicKeyword() public pure returns(string memory) {
      return "Bu bir public fonksiyondur";
   }

   function callPublicKeyword() public pure returns(string memory) {
      return publicKeyword();
   }

   // Private: Bu fonksiyona sadece bu kontrat ulaşabilir. Dışarıdan kimse bu fonksiyona ulaşamaz

   function privateKeyword() private pure returns(string memory) {
      return "Bu bir private fonskiyondur";
   }

   function callPrivateKeyword() public pure returns(string memory) {
      return privateKeyword();
   }

   // Internal: sadece miras alan kontratlar bu fonksiyonu çağırabilir. Dışarıdan kullanıcı çağıramaz.

   function internalKeyword() internal pure returns(string memory) {
      return "bu bir internal fonskiyondur...";
   }

   function callInternalKeyword() public pure returns(string memory) {
      return internalKeyword();
   }

   // External : Burada ise dışarıdan kullanıcalar çağırabilir fakat kontrat içerisnde çağrılmaz.

   // function externalKeyword() external pure returns(string memory) {
   //     return "bu bir external fonskiyondur...";
   // }

   // function callExternalKeyword() public pure returns(string memory) {
   //     return externalKeyword();
   // }
}

Constructor, Constant & Immutable

constructor: Kontrat deploy edilirken yalnızca bir kere çalışan, daha sonrada bir daha çağırılamayan isteğe bağlı bir fonksiyondur.

contract Constructor {
    uint x;

    constructor(uint _x) {
        x = _x;
    }
}

constant: Değeri değiştirilemeyen değişkenlerdir. Atanan değer kontrat deploy edildikten sonra bir daha değiştirilemez. Gaz maaliyetinden tasarruf sağlayabilir.

immutable: Değeri değiştirilemeyen değişkenlerdir. Constant’tan farkı immutable ile işaretlenmiş değişkenin değerinin constructor ile başlangıçta değiştirilebiliyor oluşudur.

// Constructor
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Constructor{
    
    string public tokenName;
    uint public totalSupply;

    constructor(string memory name, uint number) {
        tokenName = name;
        totalSupply = number;
    }

    function set(uint number) public {
        totalSupply = number;
    }
}
// Constant
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Constants {
    
    address public constant MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc;
    uint public constant MY_UINT = 123;

}
// Immutable
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Immutable {
    
    address public immutable MY_ADDRESS;
    uint public immutable MY_LUCKYNUMBER;

    constructor(uint _myNumber) {
        MY_ADDRESS = msg.sender;
        MY_LUCKYNUMBER = _myNumber;
    }
}

Control Structures

// If Else Koşul Yapıları Kontrat Örneği
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract IfElse {
    bytes32 private hashedPassord;
    uint256 private loginCount;

    constructor(string memory _password) {
        hashedPassord = keccak256(abi.encode(_password));
    }

    function login(string memory _password) public returns (bool) {
        if (hashedPassord == keccak256(abi.encode(_password))) {
            loginCount++;
            //loginCount += 1;
            return true;
        } else {
            return false;
        }
        // return (hashedPassord == keccak256(abi.encode(_password)));
    }

    function loginlogin(string memory _password) public view returns (uint256) {
        if (hashedPassord == keccak256(abi.encode(_password))) {
            return 1;
        } else {
            return 0;
        }
        // return (hashedPassord == keccak256(abi.encode(_password)) ? 1 : 0);
    }

    function loginStatus() public view returns (uint256) {
        if (loginCount == 0) {
            return 0;
        } else if (loginCount > 0 && loginCount != 1) {
            return 1;
        } else if (loginCount == 1) {
            return 2;
        } else {
            return 3;
        }
    }

    /*
        && -> ve
        true && false -> false
        || -> veya
        true || false -> true
        >, <, >=, <=
    */
}
// For - While Döngüleri Kontrat Örneği
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Loops {
    uint256[15] public numbers0;
    uint256[15] public numbers1;
    bool public state = true;
    int256 public num = 0;

    function listByFor() public {
        uint256[15] memory nums = numbers0;

        for (uint256 i = 0; i < nums.length; i++) {
            //if(i == 13) continue;
            //if(i == 14) break;
            nums[i] = i;
        }

        numbers0 = nums;
    }

    function getArr0() public view returns (uint256[15] memory) {
        return numbers0;
    }

    function listByWhile() public {
        uint256 i = 0;
        while (i < numbers1.length) {
            numbers1[i] = i;
            i++;
        }
    }

    function getArr1() public view returns (uint256[15] memory) {
        return numbers1;
    }

    function crashByWhile() public {
        while (state) {
            num++;
            num--;
        }
    }
}

Mapping

Mapping, Solidity’e özel bir veri tipidir. Diğer dillerdeki Dictionary, Map, HashTable veri tiplerine benzetebiliriz.

Eğer doğrudan iterate etmemiz gereken durumlar yoksa veya array’de ardışık şekilde sıralamamız gerekmiyorsa Mapping kullanmalıyız. Mapping kullanmak büyük gas tasarrufları yapmamıza ve kolayca verilere erişmemize yarıyor.

// Mapping
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Mapping {
    mapping(address => bool) public registered;
    mapping(address => int256) public favNums;

    function register(int256 _favNum) public {
        //require(!registered[msg.sender], "Kullanıcınız daha önce kayıt yaptı.");
        require(!isRegistered(), "Kullaniciniz daha once kayit yapti.");
        registered[msg.sender] = true;
        favNums[msg.sender] = _favNum;
    }

    function isRegistered() public view returns (bool) {
        return registered[msg.sender];
    }

    function deleteRegistered() public {
        require(isRegistered(), "Kullaniciniz kayitli degil.");
        delete (registered[msg.sender]);
        delete (favNums[msg.sender]);
    }
}

contract NestedMapping {
    mapping(address => mapping(address => uint256)) public debts;

    function incDebt(address _borrower, uint256 _amount) public {
        debts[msg.sender][_borrower] += _amount;
    }

    function decDebt(address _borrower, uint256 _amount) public {
        require(debts[msg.sender][_borrower] >= _amount, "Not enough debt.");
        debts[msg.sender][_borrower] -= _amount;
    }

    function getDebt(address _borrower) public view returns (uint256) {
        return debts[msg.sender][_borrower];
    }
}

Struct and Enum

Struct ve Enum veri tipleri.

memory, is a temporary place to store data. memory is used to make a transient reference to something in storage. Veriyi saklamak için kullanılan geçiçi bir yerdir. Sadece function scope içerisinde yer alır.

storage, holds data between function calls. Fonksiyon bittikten sonra da contract içerisinde yaşamaya devam eder. Global değişkenleri tanımlarken kullanılır. Diğer programlama dillerindeki pointer kavramına benzetebiliriz.

// Struct and Enum
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract StructEnum {

    enum Status {
        Taken, // 0
        Preparing, // 1
        Boxed, // 2
        Shipped // 3
    }

    struct Order {
        address customer;
        string zipCode;
        uint256[] products;
        Status status;
    }

    Order[] public orders;
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function createOrder(string memory _zipCode, uint256[] memory _products) external returns(uint256) {
        require(_products.length > 0, "No products.");

        Order memory order;
        order.customer = msg.sender;
        order.zipCode = _zipCode;
        order.products = _products;
        order.status = Status.Taken;
        orders.push(order);

        // orders.push(
        //     Order({
        //         customer: msg.sender,
        //         zipCode: _zipCode,
        //         products: _products,
        //         status: Status.Taken
        //     })
        // );

        // orders.push(Order(msg.sender, _zipCode, _products, Status.Taken));
        
        return orders.length - 1; // 0 1 2 3
    }

    function advanceOrder(uint256 _orderId) external {
        require(owner == msg.sender, "You are not authorized.");
        require(_orderId < orders.length, "Not a valid order id.");

        Order storage order = orders[_orderId];
        require(order.status != Status.Shipped, "Order is already shipped.");

        if (order.status == Status.Taken) {
            order.status = Status.Preparing;
        } else if (order.status == Status.Preparing) {
            order.status = Status.Boxed;
        } else if (order.status == Status.Boxed) {
            order.status = Status.Shipped;
        }
    }

    function getOrder(uint256 _orderId) external view returns (Order memory) {
        require(_orderId < orders.length, "Not a valid order id.");
        
        /*
        Order memory order = orders[_orderId];
        return order;
        */

        return orders[_orderId];
    }

    function updateZip(uint256 _orderId, string memory _zip) external {
        require(_orderId < orders.length, "Not a valid order id.");
        Order storage order = orders[_orderId];
        require(order.customer == msg.sender, "You are not the owner of the order.");
        order.zipCode = _zip;
    }

}

Modifiers

A modifier aims to change the behaviour of the function to which it is attached. Modifiers are code that can be run before and / or after a function call.

Modifiers can be used to:

  • Restrict access
  • Validate inputs
  • Guard against reentrancy hack

Modifiers genelde contract sonuna yazılır (not necessary).

// Modifiers
//SPDX-License-Identifier: Unlicensed
//@notice it's advanced version of previous parts example
pragma solidity ^0.8.0;

contract Modifier {

    enum Status {
        Taken,
        Preparing,
        Boxed,
        Shipped
    }

    struct Order {
        address customer;
        string zipCode;
        uint256[] products;
        Status status;
    }

    Order[] public orders;
    address public owner;
    uint256 public txCount;

    constructor() {
        owner = msg.sender;
    }

    function createOrder(string memory _zipCode, uint256[] memory _products) checkProducts(_products) incTx external returns(uint256) {
        // require(_products.length > 0, "No products.");

        Order memory order;
        order.customer = msg.sender;
        order.zipCode = _zipCode;
        order.products = _products;
        order.status = Status.Taken;
        orders.push(order);
        
        return orders.length - 1;
    }

    function advanceOrder(uint256 _orderId) checkOrderId(_orderId) onlyOwner external {
        // require(owner == msg.sender, "You are not authorized.");
        // require(_orderId < orders.length, "Not a valid order id.");

        Order storage order = orders[_orderId];
        require(order.status != Status.Shipped, "Order is already shipped.");

        if (order.status == Status.Taken) {
            order.status = Status.Preparing;
        } else if (order.status == Status.Preparing) {
            order.status = Status.Boxed;
        } else if (order.status == Status.Boxed) {
            order.status = Status.Shipped;
        }
    }

    function getOrder(uint256 _orderId) checkOrderId(_orderId) external view returns (Order memory) {
        // require(_orderId < orders.length, "Not a valid order id.");
    
        return orders[_orderId];
    }

    function updateZip(uint256 _orderId, string memory _zip) checkOrderId(_orderId) onlyCustomer(_orderId) incTx external {
        // require(_orderId < orders.length, "Not a valid order id.");
        Order storage order = orders[_orderId];
        // require(order.customer == msg.sender, "You are not the owner of the order.");
        order.zipCode = _zip;
    }

    modifier checkProducts(uint256[] memory _products) {
        require(_products.length > 0, "No products.");
        _;
    }

    modifier checkOrderId(uint256 _orderId) {
        require(_orderId < orders.length, "Not a valid order id.");
        _;
    }

    modifier incTx {
        _;
        txCount++;
    }

    modifier onlyOwner {
        require(owner == msg.sender, "You are not authorized.");
        _;
    }

    modifier onlyCustomer(uint256 _orderId) {
        require(orders[_orderId].customer == msg.sender, "You are not the owner of the order.");
        _;
    }

}

Events

Standalone çalışan contract içerisinde önemli olmasa da DApp’ lerde önemli bir işlevi vardır. Akıllı sözleşmelerdeki işlemlerin sonuçlarını ve dışarı çıkarmak istedeğimiz bilgileri events yardımı ile yapabiliyoruz.

indexed eğer yayınladığımız bir event’de bir değişkeni indexed olarak belirlersek sonrasında blockchain’den bu indexed değerlerini sorgulayabiliyoruz.

emit event yayınlamak için kullanıyoruz.

// Events
//SPDX-License-Identifier: Unlicensed
//@notice it's advanced version of previous parts example
pragma solidity ^0.8.0;

contract Events {

    enum Status {
        Taken,
        Preparing,
        Boxed,
        Shipped
    }

    struct Order {
        address customer;
        string zipCode;
        uint256[] products;
        Status status;
    }

    Order[] public orders;
    address public owner;
    uint256 public txCount;

    event OrderCreated(uint256 _orderId, address indexed _consumer);
    event ZipChanged(uint256 _orderId, string _zipCode);

    constructor() {
        owner = msg.sender;
    }

    function createOrder(string memory _zipCode, uint256[] memory _products) checkProducts(_products) incTx external returns(uint256) {
        Order memory order;
        order.customer = msg.sender;
        order.zipCode = _zipCode;
        order.products = _products;
        order.status = Status.Taken;
        orders.push(order);
        
        emit OrderCreated(orders.length - 1, msg.sender);

        return orders.length - 1;
    }

    function advanceOrder(uint256 _orderId) checkOrderId(_orderId) onlyOwner external {
        Order storage order = orders[_orderId];
        require(order.status != Status.Shipped, "Order is already shipped.");

        if (order.status == Status.Taken) {
            order.status = Status.Preparing;
        } else if (order.status == Status.Preparing) {
            order.status = Status.Boxed;
        } else if (order.status == Status.Boxed) {
            order.status = Status.Shipped;
        }
    }

    function getOrder(uint256 _orderId) checkOrderId(_orderId) external view returns (Order memory) {
        return orders[_orderId];
    }

    function updateZip(uint256 _orderId, string memory _zip) checkOrderId(_orderId) onlyCustomer(_orderId) incTx external {
        Order storage order = orders[_orderId];
        order.zipCode = _zip;

        emit ZipChanged(_orderId, _zip);
    }

    modifier checkProducts(uint256[] memory _products) {
        require(_products.length > 0, "No products.");
        _;
    }

    modifier checkOrderId(uint256 _orderId) {
        require(_orderId < orders.length, "Not a valid order id.");
        _;
    }

    modifier incTx {
        _;
        txCount++;
    }

    modifier onlyOwner {
        require(owner == msg.sender, "You are not authorized.");
        _;
    }

    modifier onlyCustomer(uint256 _orderId) {
        require(orders[_orderId].customer == msg.sender, "You are not the owner of the order.");
        _;
    }

}

Sending Ethers

payable: Ether transfer edebileceğimiz fonksiyon ve adresleri bildirir. payable kullanmaz ve fonksiyon içerisinde msg.value kullanmaya çalışırsak hata alırız.

Bir fonksiyona ether göndermek için fonksiyonun payable olarak nitelenmiş olması gerekir

function fName() public payable {
    /// @dev fonksiyonun işlevleri...
}

Bir adrese ether gönderebilmek için adresin payable olarak nitelenmiş olması gerekir.

function fName() public  {
    payable(0x...).transfer(amount);
}

Ether göndermenin 3 farklı yolu:

  • transfer: 2300 gas, throws error
  • send: 2300 gas, bool döndürür
  • call: gas ayarlanabilir, bool döndürür. İki tane değer döndürür (bool ve data)

Kontratların fonksiyonlar dışında ether alabilmesi için receive ya da fallback fonksiyonlarından en az birisinin tanımlanmış olması gerekir.

receive() external payable {}

fallback() external payable {}
  • Eğer kontratta receive veya fallback fonksiyonu yoksa kontrata ETH gönderemeyiz.
  • receive ve fallback fonksiyonlarının her ikisi de varsa
    • Verinin olmadığı durumlarda (msg.data boş ise) receive fonksiyonu çalışır
    • Verinin olduğu durumlarda (msg.data dolu ise) fallback fonksiyonu çalışır
  • Sadece fallback fonksiyonu varsa her iki durumda da (msg.data dolu/boş ise) fallback fonksiyonu çalışır.
/*
Hangi fonksiyon çağırılacak, fallback() or receive()?

          Ether gönder
               |
         msg.data boş mu?
              / \
           evet hayır
            /     \
receive() var mı?  fallback()
         /   \
       evet hayır
        /      \
    receive()   fallback()
*/
// Bank.sol
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Bank {
    
    mapping(address => uint ) balances;

    function sendEtherToContract() payable external {
        balances[msg.sender] = msg.value;
    }

    function showBalance() external view returns(uint) {
        return balances[msg.sender];
    }

    function withdraw(address payable to, uint amount) external  {
        // require(balances[msg.sender] >= amount,"yetersiz bakiye");
        to.transfer(amount);
        balances[to] += amount;
        balances[msg.sender] -= amount;
    }

    function withdrawWithCall(address payable to, uint amount) external returns(bool) {
        (bool sent, bytes memory data) = to.call{value: amount, gas: 424242}("data message");
        balances[msg.sender] -= amount;
        return sent;
    }

    // Transfer()
    // Revert

    // Send()
    // true, false

    // Call()
    // bool, data

    uint public receiveCount = 0;
    uint public fallbackCount = 0;
    
    receive() external payable {
        receiveCount +=1;
    }

    fallback() external payable {
        fallbackCount += 1;
    }
}

Errors

  • error
  • revert function will undo all state changes.
  • require function should be used to check return values from calls to external contracts or to guarantee that valid conditions, such as inputs or contract state variables, are satisfied. Kullanıcı sebebi ile oluşacak hataları engellemek için kullanılır.
  • assert function should only be used to examine invariants and test for internal problems.

Use require() to:

  • Validate user inputs ie. require(input<20);
  • Validate the response from an external contract ie. require(external.send(amount));
  • Validate state conditions prior to execution, ie. require(block.number > SOME_BLOCK_NUMBER) or require(balance[msg.sender]>=amount)
  • Generally, you should use require most often
  • Generally, it will be used towards the beginning of a function

Use revert() to:

  • Handle the same type of situations as require(), but with more complex logic.

Use assert() to:

  • Check for overflow/underflow, ie. c = a+b; assert(c > b)
  • Check invariants, ie. assert(this.balance >= totalSupply);
  • Validate state after making changes
  • Prevent conditions which should never, ever be possible
  • Generally, you will probably use assert less often
  • Generally, it will be used towards the end of a function.
// Errors.sol
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Errors {
    uint256 public totalBalance;
    mapping(address => uint256) public userBalances;

    error ExceedingAmount(address user, uint256 exceedingAmount);
    error Deny(string reason);

    receive() external payable {
        revert Deny("No direct payments.");
    }

    fallback() external payable {
        revert Deny("No direct payments.");
    }

    function pay() noZero(msg.value) external payable {
        require(msg.value == 1 ether, "Only payments in 1 ether");

        totalBalance += 1 ether; // 1e18
        userBalances[msg.sender] += 1 ether; // 10000...0000
    }

    function withdraw(uint256 _amount) noZero(_amount) external {
        uint256 initalBalance = totalBalance;

        //require(userBalances[msg.sender] >= _amount, "Insufficient balance.");

        if(userBalances[msg.sender] < _amount) {
            //revert("Insufficient balance.");
            revert ExceedingAmount(msg.sender, _amount - userBalances[msg.sender]);
        }

        totalBalance -= _amount;
        userBalances[msg.sender] -= _amount;
        // address => address payable
        payable(msg.sender).transfer(_amount); // Doğrudan transfer methodunu çağrılırsa bir Re-Entrancy güvenlik açığı doğar. Bir fonksiyonu dışarıdan çağırmadan önce kendi kontratımızda yapılması gereken tüm değişikliklerin yapılması gerekiyor. Bu nedenle burada balance değişiklikleri yapılıyor.

        assert(totalBalance < initalBalance);
    }

    modifier noZero(uint256 _amount) {
        require(_amount != 0);
        _;
    }

}

Library

library: Kontratlara benzer fakat ether alamaması ve durum değişkeni tutamaması ile farklılaşır. Kontratların içerisine gömülü fonksiyonların eklenmesine benzetilebilir.

using <libraryName> for <type> tanımlaması ile de kullanılabilir.

// Library.sol
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

library Math {

    function plus(uint x, uint y) public pure returns(uint) {
        return x + y;
    }

    function minus(uint x, uint y) public pure returns(uint) {
        return x - y;
    }
    
    function multi(uint x, uint y) public pure returns(uint) {
        return x * y;
    }

    function divide(uint x, uint y) public pure returns(uint) {
        require(y != 0,"bu sayisi begenmedim!");
        return x / y;
    }

    function min(uint x, uint y) public pure returns(uint) {
        if( x <= y){
            return x;
        }else {
            return y;
        }
    }

    function max(uint x, uint y) public pure returns(uint) {
        if( x >= y){
            return x;
        }else {
            return y;
        }
    }

}

library Search {
    function indexOf(uint[] memory list, uint data) public pure returns(uint) {
        for (uint i = 0; i < list.length; i++) {
            if (list[i] == data) {
                return i;
            }
        }
        return list.length;
    }
}

contract Library {

    using Math for uint;
    using Search for uint[];

    function trial1(uint[] memory x, uint y) public pure returns(uint) {
        return x.indexOf(y); // Search.indexOf(x,y)  Math.plus(x,y);
    }
    
}

library Human {
    struct Person {
        uint age;
    }

    function birthday(Person storage _person) public {
        _person.age += 1;
    }

    function showAge(Person storage _person) public view returns(uint) {
        return _person.age;
    }
}



contract HumanContract {
    mapping(uint => Human.Person) people;

    function newYear() public {
        Human.birthday(people[0]);
    }

    function show() public view returns(uint) {
        return Human.showAge(people[0]);
    }
}

Data Locations

EVM (Ethereum Virtual Machine) ‘de 3 çeşit hafıza alanı (data location) bulunur.

  • storage: Bu veriler blokzincirde tutulur.
  • memory : Bu veriler fonksiyon çağrıldıktan itibaren EVM tarafından ayrılan özel bir bölgede tutulur ve fonksiyon bittiğinde silinir.
  • calldata: Bu veriler fonksiyon çağrılırken, çağrının (transaction) içerisinde tutulur (msg.data). Bu veriler sadece okunabilir (read-only).

bytes, string, uint256[], struct gibi referans tipleri fonksiyonlarda kullanılırken bu verilerin hangi hafıza alanından alınacağı belirtilmelidir.

calldata sadece fonksiyon parametreleri için kullanılabilir. Eğer fonksiyon içerisinde verilen değerleri doğrudan okumak isterseniz bunu kullanın.
memory yi fonksiyon içerisinde eğer bir değişiklik yapmayacaksanız, sadece okuma yapacaksaksanız kullanın.
storage sadece fonksiyon gövdesinde kullanılabilir. Eğer fonksiyon içerisinde bir değişiklik yapacaksaksanız bunu kullanın.

Bunu yapmanız hem daha okunabilir, anlaşılabilir kodlar yazmanızı hem de gas tasarrufu yapmanızı sağlar. storage üzerinde yapılan işlemler daha pahalı iken memory üzerinde yapılan daha ucuzdur, calldata daha da ucuzdur.

function(string memory/calldata parameterString) external {
    string memory/storage bodyString = "";
}
// DataLocations.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
           Kontrat           <----                  Kontrata yapılan çağrı
           -------                                      -------------
    Kontrat depolama alanı           Fonksiyon için ayrılan hafıza ve çağrıdaki data alanı

    memory:          Geçici hafıza
    storage:         Kalıcı hafıza
    calldata:        Çağrıdaki argümanlar

    bytes, string, array, struct

    * Değer tipleri (uint, int, bool, bytes32) kontrat üzerinde storage, 
      fonksiyon içinde memory'dir
    
    * mapping'ler her zaman kontrat üzerinde tanımlanır ve storage'dadır.
*/

struct Student {
    uint8 age;
    uint16 score;
    string name;
}

contract School {
    uint256 totalStudents = 0;              // storage
    mapping(uint256 => Student) students;   // storage

    function addStudent(string calldata name, uint8 age, uint16 score) external {
        uint256 currentId = totalStudents++;
        students[currentId] = Student(age, score, name); 
    }

    function changeStudentInfoStorage(
        uint256 id,                 // memory
        string calldata newName,    // calldata
        uint8 newAge,               // memory
        uint16 newScore             // memory
    ) external {
        Student storage currentStudent = students[id];

        currentStudent.name = newName;
        currentStudent.age = newAge;
        currentStudent.score = newScore;
    }

    /**
        @dev Bu işe yaramayacaktır, çünkü oluşturulan currentStudent ömrü
        fonksiyonun bitişine kadar olan bir değişken ve fonksiyon tamamlandığında
        silinecektir
    */
    function changeStudentInfoMemory(
        uint256 id,                 // memory
        string calldata newName,    // calldata
        uint8 newAge,               // memory
        uint16 newScore             // memory
    ) external {
        Student memory currentStudent = students[id];

        currentStudent.name = newName;
        currentStudent.age = newAge;
        currentStudent.score = newScore;
    }

    function getStudentName(uint256 id) external view returns(string memory) { 
        return students[id].name;
    }
}

Inheritance

is: Solidity supports both single as well as multiple inheritance. Solidity çoklu kalıtımı destekleyen bir programlama dilidir. Kontratlar is anahtar sözcüğü ile diğer kontratları miras olarak alabilir.

virtual: Bir alt kontrat tarafından fonksiyonun geçersiz kılınabileceğini bildiren niteleyicidir. (Bir kontratı miras olarak aldığımızda virtual işaretli fonksiyonu tekrar düzenleyebilir ve içeriğini değiştirebiliriz.)

override: Bir üst kontratta bulunan virtual ile işaretlenmiş fonksiyonları geçersiz kılmamızı ve tekrardan tanımladığımızı bildiren niteleyicidir. (Miras aldığımız kontrakt içerisindeki özelliğini değiştirmek istediğimiz fonksiyon override olarak işaretlenmeli.)

super: Miras sırası önemlidir. C3-linearization kurallarına göre super anahtar sözcüğü ile miras alınan kontrata erişebiliriz.

// Inheritance.sol
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract A {
    uint public x;

    uint public y;

    function setX(uint _x) virtual public {
        x = _x;
    }

    function setY(uint _y) virtual public {
        y = _y;
    }
}

// Derived Contract
contract B is A {

    uint public z;

    function setZ(uint _z) public {
        z = _z;
    }

    function setX(uint _x) override public {
        x = _x + 2;
    }

}
// SuperHuman.sol
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Human {
    function sayHello() public pure virtual returns(string memory) {
        return "itublockchain.com adresi uzerinden kulube uye olabilirsiniz :)";
    }
}

contract SuperHuman is Human {
    function sayHello() public pure override returns(string memory) {
        return "Selamlar itu blockchain uyesi, nasilsin ? :)";
    }

    function welcomeMsg(bool isMember) public pure returns(string memory) {
        return isMember ? sayHello() : super.sayHello();
    }
}

import: to import local and external files. You can also import from GitHub by simply copying the url.

Example below imports Openzeppelin Ownable Contract.

// Import.sol
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/access/Ownable.sol"; // Method1: import from npm package
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol"; // Method2: import from github repository

contract Wallet is Ownable {
    fallback() payable external {}

    function sendEther(address payable to, uint amount) onlyOwner public {
        to.transfer(amount);
    }

    function showBalance() public view returns(uint) {
        return address(this).balance;
    }
}

Interacting with Other Contracts/Addresses

//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Interact {
    address public caller;
    mapping(address => uint256) public counts;

    function callThis() external {
        caller = msg.sender;
        counts[msg.sender]++;
    }
}

contract Pay {
    mapping(address => uint256) public userBalances;

    // receive,  fallback

    function payEth(address _payer) external payable {
        userBalances[_payer] += msg.value;
    }
}

// msg.sender -> A (mesaj yollayan: msg.sender) -> B (mesaj yollayan: A adresi)

contract Caller {
    Interact interact;
    
    constructor(address _interactContract) {
        interact = Interact(_interactContract);
    }

    function callInteract() external {
        interact.callThis();
    }

    function readCaller() external view returns (address) {
        return interact.caller();
    }

    function readCallerCount() public view returns (uint256) {
        return interact.counts(msg.sender);
    }
    
    function payToPay(address _payAddress) public payable {
        Pay pay = Pay(_payAddress);
        pay.payEth{value: msg.value}(msg.sender);

        // Pay(_payAddress).payEth{value: msg.value}(msg.sender);
    }

    function sendEthByTransfer() public payable {
        payable(address(interact)).transfer(msg.value);
    }
}

Interfaces

Interface’ler (Arayüzler) çalışma mantığı farklı olan ama yaptığı iş aynı olan kontratların (örneğin token kontratları) ortak bir standarda sahip olmasını böylece bu kontratlarla çalışmak isteyen birinin her bir kontrata özgü kod yazmak yerine bu standarda uygun tek bir kod yazmasını sağlar.

ERC20, ERC721, ERC1155 gibi standartlar aslında bir interface şeklinde tanımlanmıştır.

// Interfaces.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
    Interfaces (Arayüz):
    OOP'deki Polymorphism konseptinin uygulanma biçimlerinden biridir.

    Parent a
    Child1 ac, Child2 ab, Child3 ad

    Araç
    Kamyon, Otomobil, Karavan

    fn tekerlekSayısıAl (Araç a) -> int tekerlekSayısı:
        return a.tekerlekSayısı;

    Sınıflar arasında etkileşim için bir standard oluşturur.
    Solidity'de sınıflar -> kontratlar
*/

contract EthSender {
    function _send(address payable to, uint256 amount) private {
        to.transfer(amount);
    }

    function sendWithStrategy(address strategyAddress) external {
        (address payable to, uint256 amount) = IStrategy(strategyAddress).getAddressAndAmount();
        _send(to, amount);
    }

    receive() external payable { }
}

abstract contract Strategy {
    // Virtual: Benden sonra gelen bu fonksiyonu değiştirebilir
    function getAddressAndAmount() external virtual view returns(address payable, uint256);
}

interface IStrategy {
    function getAddressAndAmount() external view returns(address payable, uint256);
}

contract AddressStrategy1 {
    uint256 constant ETHER_AMOUNT = 0.1 ether;

    function getAddressAndAmount() external pure returns(address payable, uint256) {
        return(payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), ETHER_AMOUNT);
    }
}

contract AddressStrategy2 {
    uint256 constant ETHER_AMOUNT = 0.1 ether;

    function getAddressAndAmountV2() external pure returns(address payable, uint256) {
        uint256 amount = ETHER_AMOUNT * 5;
        return(payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), amount);
    }
}

contract AddressStrategy3 is Strategy {
    uint256 constant ETHER_AMOUNT = 0.1 ether;

    // Override: Benden önce gelen fonksiyonu değiştiriyorum
    function getAddressAndAmount() external pure override returns(address payable, uint256) {
        uint256 amount = ETHER_AMOUNT * 5;
        return(payable(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2), amount);
    }
}
// Vault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IToken.sol";

/// @title Vault
/// @notice Allows users to deposit and withdraw any token (?)
contract Vault {
    /// @dev User -> Token -> Balance
    mapping(address => mapping(IToken => uint256)) tokenBalances;

    function depositToken(IToken token, uint256 amount) external {
        token.transferFrom(msg.sender, address(this), amount);
        tokenBalances[msg.sender][token] += amount;
    }

    function withdrawToken(IToken token, uint256 amount) external {
        tokenBalances[msg.sender][token] -= amount;
        token.transfer(msg.sender, amount);
    }

    function getBalanceOf(address user, IToken token) external view returns(uint256) {
        return token.balanceOf(user);
    }
}
// IToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IToken {
    // Fonksiyonlarda override gerekli
    function transferFrom(address from, address to, uint256 amount) external;
    function transfer(address to, uint256 amount) external;
    function balanceOf(address user) external view returns(uint256);

    // Eventlerde override gerekli değil
    event Transfer(address indexed from, address indexed to, uint256 amount);
}
// TokenA.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IToken.sol";

contract TokenA is IToken {
    uint256 constant private OWNER_BALANCE = 1000 * 10**18;

    mapping(address => uint256) balances;

    // Allower -> Allowee -> Amount
    mapping(address => mapping(address => uint256)) allowances;

    constructor() {
        balances[msg.sender] = OWNER_BALANCE;
    }

    function transfer(address recipient, uint256 amount) external override {
        require(recipient != address(0), "XFER_TO_ZERO");
        require(balances[msg.sender] >= amount, "INSUFFICENT_BAL");
    
        balances[msg.sender] -= amount;
        balances[recipient] += amount;

        emit Transfer(msg.sender, recipient, amount);
    } 

    function transferFrom(address from, address to, uint256 amount) external override {
        require(to != address(0), "XFER_TO_ZERO");
        require(allowances[from][msg.sender] >= amount, "INSUFFICIENT_ALLOWANCE");
        require(balances[from] >= amount, "INSUFFICENT_BALANCE");

        allowances[from][msg.sender] -= amount;
        balances[from] -= amount;
        balances[to] += amount;
    }

    function increaseAllowance(address recipient, uint256 amount) external {
        require(recipient != address(0), "APPR_TO_ZERO");
        allowances[msg.sender][recipient] += amount;
    }

    function decreaseAllowance(address recipient, uint256 amount) external {
        require(recipient != address(0), "APPR_TO_ZERO");
        allowances[msg.sender][recipient] -= amount;
    }

    function balanceOf(address user) external view override returns(uint256) {
        return balances[user];
    }
}
// TokenB.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IToken.sol";

contract TokenB is IToken{
    uint256 constant private OWNER_BALANCE = 1000 * 10**18;

    mapping(address => uint256) balances;

    // Allower -> Allowee -> Amount
    mapping(address => mapping(address => uint256)) allowances;

    constructor() {
        balances[msg.sender] = OWNER_BALANCE;
    }

    function transfer(address recipient, uint256 amount) external override {
        require(recipient != address(0), "XFER_TO_ZERO");
        require(balances[msg.sender] >= amount, "INSUFFICENT_BAL");
    
        balances[msg.sender] -= amount;
        balances[recipient] += amount;

        emit Transfer(msg.sender, recipient, amount);
    }

    function transferFrom(address from, address to, uint256 amount) external override {
        require(to != address(0), "XFER_TO_ZERO");
        require(allowances[from][msg.sender] >= amount, "INSUFFICIENT_ALLOWANCE");
        require(balances[from] >= amount, "INSUFFICENT_BALANCE");

        allowances[from][msg.sender] -= amount;
        balances[from] -= amount;
        balances[to] += amount;
    }

    function increaseAllowance(address recipient, uint256 amount) external {
        require(recipient != address(0), "APPR_TO_ZERO");
        allowances[msg.sender][recipient] += amount;
    }

    function decreaseAllowance(address recipient, uint256 amount) external {
        require(recipient != address(0), "APPR_TO_ZERO");
        allowances[msg.sender][recipient] -= amount;
    }

    function balanceOf(address user) external view override returns(uint256) {
        return balances[user];
    }
}

Call

call methodu aracılığıyla low-level çağrılar yapılıyor. Diğer akıllı kontratlar ile etkileşmemize yarar. Diğer akıllı kontratların fallback methodlarını tetiklemek veya doğrudan sadece bir ETH değeri göndermek için kullanılması öneriliyor. call methodu aracılığı ile diğer akıllı kontratlardaki fonksiyonu, eğer adını ve parametrelerini doğru şekilde biliyorsak çağırabiliyoruz. Bu da kontratın ABI bilgisine ve kontratın kendisine ihtiyacımız olmadan akıllı kontratları çağırmaya yarıyor.

call methoduna parametreleri doğrudan giremeyiz. abi.encodeWithSignature() methodu yardımı ile hash’leyerek girebiliyoruz. abi.encodeWithSignature() methodunun ilk parametresi çalıştırmak istediğimiz fonksiyon ve fonksiyona ait parametrelerin tipleri, diğer parametreler ise fonksiyona gönderilecek değerlerdir. Eğer ilk parametre olan fonksiyonu bilmiyorsak veya yanlış fonksiyon çağırırsak, bu fallback methodunu tetikler. Bu doğru bir kullanım değildir.

_contract.call(abi.encodeWithSignature("inc(uint256, string)", _amount, _incrementer));

call methodu iki değer döndürüyor. Değerlerden birisi bool, diğeri bytes. İkinci değeri doğrudan değil hash’leyerek döndürüyor. abi.decode() ile bu değere erişebiliyoruz.

uint256 _total = abi.decode(res, (uint256));

call methodu ile bir kontratın fallback methodunu tetiklemek için süslü parantez kullanarak msg.value değerini veya herhangi bir değer (1 ether vs.) gönderebiliyoruz. Eğer bu boş bir çağrıysa, yani herhangi bir fonksiyonu tetiklemek istemiyorsak parantez içerisine boş tırnak işareti koymamız gerekiyor.

call methodunun döndürdüğü ikinci değişkeni kullanmak istemiyorsak, ilk değerden sonra virgül yazıp gerisini boş bırakıyoruz.

(bool err,) = _contract.call{value: msg.value}("");
// Call.sol
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract Test {
    uint256 public total = 0;
    uint256 public fallbackCalled = 0;
    string public incrementer;

    fallback() external payable {
        fallbackCalled += 1;
    }

    function inc(uint256 _amount, string memory _incrementer) external returns(uint256) {
        total += _amount;
        incrementer = _incrementer;

        return total;
    } 
}

contract Caller {
    
    function testCall(address _contract, uint256 _amount, string memory _incrementer) external returns (bool, uint256) {
       (bool err, bytes memory res) = _contract.call(abi.encodeWithSignature("inc(uint256, string)", _amount, _incrementer));
        uint256 _total = abi.decode(res, (uint256));
        return (err, _total);
    }

    function payToFallback(address _contract) external payable {
        (bool err,) = _contract.call{value: msg.value}("");

        if(!err) revert();
    }
}

Creating Contracts

Akıllı kontratlar aracılığıyla farklı akıllı kontratlar deploy edilmesi. Bunlara Factory Contract adı veriliyor.

Yeni bir kontrat deploy etme işlemi başka bir akıllı kontrat ile yapılsa bile çok gas harcayan maliyetli bir iştir. Ne kadar maliyetli olduğu kontratın ne kadar karmaşık olduğu ile ilgili. O yüzden bu tarz örnekleri mümkün olduğunca kullanmaktan kaçınmalıyız.

// Factory Contract
//SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.0;

contract VaultFactory {
    mapping(address => Vault[]) public userVaults;

    function createVault() external {
        Vault vault = new Vault(msg.sender);
        userVaults[msg.sender].push(vault);
    }

    function createVaultWithPayment() external payable {
        Vault vault = (new Vault){value: msg.value}(msg.sender);
        userVaults[msg.sender].push(vault);
    }
}

contract Vault {
    address public owner;
    uint256 private balance;

    constructor(address _owner) payable {
        owner = _owner;
        balance += msg.value;
    }

    fallback() external payable {
        balance += msg.value;
    }

    receive() external payable {
        balance += msg.value;
    }

    function getBalance() external view returns (uint256) {
        return balance;
    }

    function deposit() external payable {
        balance += msg.value;
    }

    function withdraw(uint256 _amount) external {
        require(msg.sender == owner, "You are not authorized.");
        balance -= _amount;
        payable(owner).transfer(_amount);
    }
}