DEV Community

Swaraj Papadkar
Swaraj Papadkar

Posted on • Originally published at swaraj70.com on

Learning to Code Blockchain DApps from CryptoZombies 🧟‍♂️

The popularity of Blockchain technology is increasing day by day and so are the reasons to learn it.

This is the first article of the series Learning to Code Blockchain DApps from CryptoZombies in which I will be documenting my journey as I go through all the lessons present in the CryptoZombies curriculum.

Before getting into the lessons, Let's first understand what is CryptoZombies?

So, What is CryptoZombies?

CryptoZombies is a free resource for aspiring blockchain dApp developers in which users get to learn all the important concepts of dApp development and the Solidity programming language by building their own NFT-style Zombie game.

CryptoZombies is an interactive school that teaches you all things technical about blockchains. Learn to make smart contracts in Solidity by making your own crypto-collectibles game.

Why CryptoZombies?

When learning something new I generally try to find resources which are well structured and which walks you through the concepts in a progressive way, so that I am clear with the basics before jumping into more advanced concepts and after browsing through CryptoZombies curriculum I found out it follows a similar structure.

The best part about learning from CryptoZombies is that it's completely browser based and you don't have to install or configure anything.

So, after this little introduction of CryptoZombies let's try to understand the concepts explained in Lesson 1.

Lesson 1 - Making the Zombie Factory

Chapter 1: Lesson Overview

In this lesson, we are supposed to build a Zombie Factory to make a lot of zombies for our army and in order to do that we have to,

  • maintain a database of all zombies in our army.
  • make a function for creating new zombies.
  • make sure each zombie has a random and unique appearance.

lesson1a.png

But before we do that, We should first understand how zombie DNA works as the zombie's appearance is based on its Zombie DNA.

Zombie DNA is just a simple 16-digit integer, like:

8356281049284737

Enter fullscreen mode Exit fullscreen mode

So, just like real DNA different parts of this number maps to different traits. The first 2 digits map to the zombie's head type, the second 2 digits to the zombie's eyes, etc. The zombie can have only 7 different types of heads and 8, 3 are the first two digits in the above Zombie DNA sample, to map that to the zombie's head type, we do

83 % 7 + 1 = 7

Enter fullscreen mode Exit fullscreen mode

Which means this Zombie would have the 7th zombie head type (The Santa hat).

headgene7.png

As we now have a better understanding of what Zombie DNA is, let's see how we can build our own Zombie army.

Chapter 2: Contracts

Solidity's code is encapsulated in contracts and to start creating our Zombie army, we have to create a base Contract called ZombieFactory.

What are 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.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

}

Enter fullscreen mode Exit fullscreen mode

In the above code, the first line pragma solidity >=0.5.0 <0.6.0; is known as a Version Pragma.

What is a Version Pragma?

All solidity source code starts with a "version pragma". It simply tells the source code is written for which solidity version. This is to prevent issues with future compiler versions potentially introducing changes that would break the code.

Our contract uses solidity version which is greater than or equal to 0.5.0 and less than 0.6.0. Therefore,

pragma solidity >=0.5.0 <0.6.0;

Enter fullscreen mode Exit fullscreen mode

Chapter 3: State Variables and Integeres

Now, as our Zombie DNA is going to be determined by a 16-digit number we will create a State Variable named dnaDigits and set it equal to 16.

What are State Variables?

Variables which are permanently stored in contract storage is known as State Variables. These variables are permanently written to the Ethereum blockchain. Think of it as storing values in a database.

We will be using the uint data type while defining our state variable dnaDigits which means its value must not be negative.

Note: uint is a data type which stands for unsigned integer. There's also an int data type for signed integers.

Chapter 4: Math Operations

To make sure our Zombie's DNA is only 16 characters we will make another uint named dnaModulus and set it equal to 10^dnaDigits, since dnaDigits = 16. That way we can later use the modulus operator % to shorten an integer to 16 digits.

Note: Math in solidity is pretty straightforward and the operations are almost the same as in most programming languages.

Our solidity code should now look something like this, 👇

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

}

Enter fullscreen mode Exit fullscreen mode

Chapter 5: Structs

Just like us humans, our zombies will also have multiple properties like name (a string), dna (a uint) and to club these properties into one single data type solidity provides us with a tool known as structs.

What are Structs?

Structs allow us to create more complicated data types that have multiple properties.

As we are going to want to create some zombies for our app, we will create a struct named Zombie with two properties name (a string) and dna (a uint).

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Struct
    struct Zombie {
        string name;
        uint dna;
    }

}

Enter fullscreen mode Exit fullscreen mode

Note: String is a data type which can store a sequence of characters. Ex:

string greeting = "Hello world!"

Chapter 6: Arrays

In order to store an army of zombies we will need a collection of zombies and in solidity, arrays are used to create a collection of something.

What are Arrays?

Arrays are used to store a collection of something. In solidity, there are two types of arrays - Fixed arrays and Dynamic arrays.

Ex:

// Array with a fixed length of 2 uint elements:
uint[2] fixedArray;

// A dynamic Array - has no fixed size, can keep growing:
uint[] dynamicArray;

Enter fullscreen mode Exit fullscreen mode

We can also declare an array as public which enables other contracts to read from this array, but can not write to this array.

Ex:

// Person struct
struct Person {
    uint age;
    string name;
}

// Dynamic Public Array named people which can store a lot of persons.
Person[] public people;

Enter fullscreen mode Exit fullscreen mode

So, we will now create a public array of Zombie struct, and name it zombies.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

}

Enter fullscreen mode Exit fullscreen mode

Chapter 7: Function Declarations

Now, we will create a public function named createZombie for creating more zombies. It will take two parameters: _name (a string), and _dna (a uint).

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie public function
    function createZombie(string memory _name, uint _dna) public {

    }

}

Enter fullscreen mode Exit fullscreen mode

Note: It's convention (but not required) to start function parameter variable names with an underscore (_) in order to differentiate them from global variables.

For now the body of the function is empty. Note that we're specifying the function visibility as public. 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.

There are two ways in which you can pass an argument to a Solidity function:

  • By value, which means that the Solidity compiler creates a new copy of the parameter's value and passes it to your function. This allows your function to modify the value without worrying that the value of the initial parameter gets changed.
  • By reference, which means that your function is called with a reference to the original variable. Thus, if your function changes the value of the variable it receives, the value of the original variable gets changed.

Chapter 8: Working With Structs and Arrays

In the last chapter, we created an empty function named createZombie with two parameters: _name (a string), and _dna (a uint). So, in this chapter we will try to fill the function so it can actually create a zombie whose name and dna will come from the function arguments and add it to the zombies array.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie public function
    function createZombie(string memory _name, uint _dna) public {
        // Create a new zombie and push it at the end of zombies array
        zombies.push(Zombie(_name, _dna));
    }

}

Enter fullscreen mode Exit fullscreen mode

Note: array.push() adds something to the end of the array.

Chapter 9: Private / Public Functions

In solidity, functions are public by default which means anyone or any contract can call our contract's function and execute its code this can make our contract vulnerable to attacks.

Our contract's createZombie function is currently public and anyone could call it and create a new Zombie in our contract which is not desirable. So, we will modify our createZombie function and make it private. This will only allow other functions within our contract to call createZombie function and add new zombies to the zombies array.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie PRIVATE function
    function _createZombie(string memory _name, uint _dna) private {
        // Create a new zombie and push it at the end of zombies array
        zombies.push(Zombie(_name, _dna));
    }

}

Enter fullscreen mode Exit fullscreen mode

Note:

  • As with function parameters, it's convention to start private function names with an underscore (_).
  • It is a good practice to mark your functions as private by default, and then only make those functions public which you want to expose to the world.

Chapter 10: More on Functions

In this chapter, we have to create a private view function called _generateRandomDna which takes a single string parameter _str and it should return a uint. We will fill the function body in the next chapter.

A return value in solidity functions is a value which gets returned when the function is called. In our case, a uint will be returned.

A view is a function modifier for a function that only views the data present in our smart and does not modify the data.

Note: Solidity also contains pure functions, which means the function does not access any data in the app and doesn't even read from the state of the app. Its return value depends only on its function parameters.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie PRIVATE function
    function _createZombie(string memory _name, uint _dna) private {
        // Create a new zombie and push it at the end of zombies array
        zombies.push(Zombie(_name, _dna));
    }

    // create private view function _generateRandomDna
    function _generateRandomDna(string memory _str) private view returns (uint) {

    }

}

Enter fullscreen mode Exit fullscreen mode

Chapter 11: Keccak256 and Typecasting

Keccak256 is a hash function in Ethereum which is a version of SHA3. A hash function basically converts an input into a random 256-bit hexadecimal number and even if there is just a slight change in the input it will cause a large change in the hash. We will use it for pseudo-random number generation.

keccak256 expects a single parameter of type bytes. This means that we have to "pack" any parameters before calling keccak256.

Typecasting is a process to convert between data types. For Eg:

uint8 a = 5;
uint b = 6;
// throws an error because a * b returns a uint, not uint8:
uint8 c = a * b;
// we have to typecast b as a uint8 to make it work:
uint8 c = a * uint8(b);

Enter fullscreen mode Exit fullscreen mode

Now, ln our _generateRandomDna function we have to call the keccak256 hash function by passing abi.encodePacked(_str) as its parameter to generate a pseudo-random hexadecimal, after that typecast it as a uint, and finally store the result in a uint called rand. Also we want our DNA to be only 16 digits long so we will use the dnaModulus variable that we created earlier in the lesson and in the end return the result.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie PRIVATE function
    function _createZombie(string memory _name, uint _dna) private {
        // Create a new zombie and push it at the end of zombies array
        zombies.push(Zombie(_name, _dna));
    }

    // create private view function _generateRandomDna
    function _generateRandomDna(string memory _str) private view returns (uint) {
        // Keccak256 and Typecasting
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

}

Enter fullscreen mode Exit fullscreen mode

Chapter 12: Putting It Together

To complete our random Zombie generator we will create a public function named createRandomZombie that takes one parameter named _name (a string), then inside this function we will call _generateRandomDna by passing _name as its parameter and store it in a uint named randDna. Next, we will call the _createZombie function and pass _name and randDna as its parameters for creating a new zombie.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie PRIVATE function
    function _createZombie(string memory _name, uint _dna) private {
        // Create a new zombie and push it at the end of zombies array
        zombies.push(Zombie(_name, _dna));
    }

    // create private view function _generateRandomDna
    function _generateRandomDna(string memory _str) private view returns (uint) {
        // Keccak256 and Typecasting
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    // create public function createRandomZombie
    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

Enter fullscreen mode Exit fullscreen mode

Chapter 13: Events

We want an event to let our front-end know every time a new zombie was created, so the app can display it.

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

So, to do that we will declare an event called NewZombie and pass zombieId (a uint), name (a string), dna (a uint) as its parameters and then modify the _createZombie function to fire the NewZombie event after adding the new Zombie to our zombies array.

We are also going to need the zombie's id and array.push() returns a uint of the new length of the array - and since the first item in an array has index 0, array.push() - 1 will be the index of the zombie we just added. So, we will store the result of zombies.push() - 1 in a uint called id, and then use this in the NewZombie event.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // declare our NewZombie event
    event NewZombie(uint zombieId, string name, uint name);

    // This will be stored permanently in the blockchain
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits; // equal to 10^16

    // Zombie struct
    struct Zombie {
        string name;
        uint dna;
    }

    // zombies array
    Zombie[] public zombies;

    // createZombie PRIVATE function
    function _createZombie(string memory _name, uint _dna) private {
        // Create a new zombie, 
        // push it at the end of zombies array and 
        // get the id of the zombie we just added.
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        // fire NewZombie event
        emit NewZombie(id, _name, _dna);
    }

    // create private view function _generateRandomDna
    function _generateRandomDna(string memory _str) private view returns (uint) {
        // Keccak256 and Typecasting
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

    // create public function createRandomZombie
    function createRandomZombie(string memory _name) public {
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

}

Enter fullscreen mode Exit fullscreen mode

Chapter 14: Web3.js

We have completed our solidity contract. Now we need to write a javascript frontend that interacts with the contract.

Ethereum has a Javascript library called Web3.js. In later lessons we will go over in depth how to deploy a contract and set up Web3.js. For now CryptoZombies have provided us with some sample code that will help us understand how Web3.js would interact with our deployed contract.

// Here's how we would access our contract:
var abi = /* abi generated by the compiler */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` has access to our contract's public functions and events

// some sort of event listener to take the text input:
$("#ourButton").click(function(e) {
  var name = $("#nameInput").val()
  // Call our contract's `createRandomZombie` function:
  ZombieFactory.createRandomZombie(name)
})

// Listen for the `NewZombie` event, and update the UI
var event = ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  generateZombie(result.zombieId, result.name, result.dna)
})

// take the Zombie dna, and update our image
function generateZombie(id, name, dna) {
  let dnaStr = String(dna)
  // pad DNA with leading zeroes if it's less than 16 characters
  while (dnaStr.length < 16)
    dnaStr = "0" + dnaStr

  let zombieDetails = {
    // first 2 digits make up the head. We have 7 possible heads, so % 7
    // to get a number 0 - 6, then add 1 to make it 1 - 7. Then we have 7
    // image files named "head1.png" through "head7.png" we load based on
    // this number:
    headChoice: dnaStr.substring(0, 2) % 7 + 1,
    // 2nd 2 digits make up the eyes, 11 variations:
    eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
    // 6 variations of shirts:
    shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
    // last 6 digits control color. Updated using CSS filter: hue-rotate
    // which has 360 degrees:
    skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
    eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
    clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
    zombieName: name,
    zombieDescription: "A Level 1 CryptoZombie",
  }
  return zombieDetails
}

Enter fullscreen mode Exit fullscreen mode

What this javascript code then does is take the values generated in zombieDetails above, and use some browser-based javascript magic to swap out the images and apply CSS filters. For Eg:

swaraj70.png

Conclusion

We have now successfully completed Lesson 1 of CryptoZombies. In order to complete Lesson 1 of CryptoZombies we have learnt -

  • How to write Ethereum smart contracts.
  • Basics of Ethereum's smart contract programming language - Solidity

If you are interested in learning how to create your own blockchain dapps on Ethereum like me for FREE, CryptoZombies could be a good place to start.

Thank you for reading this article. I am learning more about web 3.0 and blockchain development, if you're interested in being a part of this learning journey feel free to follow me here or on twitter.

Top comments (0)