2018-09-15

Learn solidity by breeding zombies 🧟

Below you’ll find my notes from the CryptoZombies game, which is a fun introduction to Solidity. It’s the first resource mentioned in my previous article about how I want to learn more about the Blockchain. I’ll try to keep the notes up-to-date as I progress through the levels. You can use it as a top-level view of what you’ll learn by playing the game, but I recommend checking out the CryptoZombies website.

// version of solidity defined on top of each file: 
pragma solidity ^0.4.19;
 
// create a signle contract: 
contract ZombieFactory {
      
    // varaibles are stored permanently on the contract 
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
 
    struct Zombie {
        string name;
        uint dna;
    }
      
    // create dynamic arrays: 
    Zombie[] public zombies;
    
    // by convention, fn args name's start with a _underscore 
    // fns are public by default. A common convention is to 
    // make all fns private by default, and only intentionally 
    // make specific ones public later 
      // Solidity has two additional visibility types: 
    // - `internal` - like `private` but also accessible by 
    // contracts inheriting from the current one 
    // - `external` - like `public` but can't be called from 
    // within current contract 
    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }
      
    // Solidity lets you define types of returned values as 
      // well as the type of the function. A `pure` one doesn't 
    // access the outside world, while a `view` function  
    // only uses the outside world without modyfing it 
    function foo(uint a, uint b) pure returns (uint) {
        return a + b;
    }
      
      // `keccak256` is Ethernum's version of SHA3 has function 
       // below we typecast the result to an `uint` 
      function _generateRandomDna(string _str) private view returns (uint) {
        uint rand = uint(keccak256(_str));
        return rand % dnaModulus;
    }
 
      // Use `events` to inform frontend about changes: 
      event FooEvent(uint a, uint b, uint result).
    function foo(uint a, uint b) pure returns (uint) {
        uint result = a + b;
        FooEvent(a, b, result);
      return a + b;
    } 
 
       // key-value stores are called `mapping`s: 
      mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;
 
    // `msg.sender` is a global var with caller's id (the caller can be either another contract or a person 
      // `require` is used for input validation 
     function createRandomZombie(string _name) public {
        require(ownerZombieCount[msg.sender] == 0)
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }
 
      // Solidity has no built-in string comparison so  
    // you can do: 
      keccak256("string") == keccak256("string2")
        
}
 
// contract inheritance: 
contract Animal {
    ...
}
contract Cow is Animal {
    // Cow has access to all public functions of Animal 
}
 
// you can store things in storage (persistent) and  
// in memory (temporary). By default global scope vars  
// are stored permanently. You can also specify the dest 
SomeThing storage myThing = things[1]
// ^^^^ this becomes a pointer to the array of `things` 
// which means that `myThing.price = 100` will change the  
// value in permanent `storage` 
 
// analogically, using `memory` makes a copy of the value 
// and won't store anything persistently 
SomeThing memory myThing = things[1]
 
// Interact with third-party contract's APIs by implementing 
// an interface, which is just a bunch of functions  
// without a body mimicking the functions you want to use from 
// that contract: 
contract myInterface {
    function someFunction(uint x) public view returns (uint);
}
 
// You can then start using the interface by instatiating 
// it with contrat's address: 
myInterface xyz = myInterface("0x060...");
myInterface.someFunction(1);
 
// Handle multiple returns: 
uint a;
uint b;
uint c;
(a,b,c) = multipleReturns();
 
// or 
uint c;
(,,c) = multipleReturns();

A couple more notes to remember about contracts:

  • Once deployed, a contract is immutable. You might want to provide functions for setting contract’s insides if you ever want to change them in the future. To prevent unauthorized usage of the functions you can for example use Ownable from OpenZeppelin - a Solidity library.