TIL: Solidity - Making the Zombie Factory

TIL: Solidity - Making the Zombie Factory

w/ CryptoZombies

ยท

5 min read

  • Short Note: My first encounter with CryptoZombies was actually several years back. But I took a different career path. And here I am again!
  • Purpose: Refresh myself with Solidity basics. Dev-logging so that I can check back anytime.

Contracts

// Contracts

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

}

State Variables & Integers

/*
State Variables & Integers
> state variables are permanently stored in contract storage (like DB)
> uint is alias for uint256 (256-bit unsigned integer) => 'non-negative'
*/

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;

}

Structs

/*
Structs
> for complex data types
*/

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

}

Arrays

/*
Arrays
> 2 types: fixed or dynamic
> > ex) uint[2] fixedArray vs uint[] dynamicArray

> array of structs are also possible
> declare public => getter it auto-created
*/
pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

}

Function Declarations

/*
Function Declarations
# convention for function parameter to start with _
> function parameters are by 'reference types' or 'values'
> use 'memory' instruction for variable, especially when it's reference types
*/
pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

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

    }

}

Structs & Arrays

/*
Structs & Arrays
> push() structs into array (like js)
*/
pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

   uint dnaDigits = 16;
   uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function createZombie (string memory _name, uint _dna) public {
        zombies.push(Zombie(_name, _dna));
    }

}

Private / Public Functions

/*
Private / Public Functions
> functions are public by default, anyone can call & execute them
> so, it's a good practice to mark functions as private by default => only mark the ones to be exposed as public
# convention to mark private functions by starting with _
*/
~~~

function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

Return Values, Modifiers in Functions

/*
Return Values, Modifiers in Functions
> function declaration also includes type of return
> mark function as 'view' if it's only viewing data and not modifying
> mark function as 'pure' if it's not even accessing any data
*/
~~~

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

    }

Keccak256 and Typecasting

/*
Keccak256 and Typecasting
> keccak expects single param type of 'bytes' => pack params before calling keccak256
> > ex) keccak256(abi.encodePacked("aaaab"));

> typecast to store result in the form wanted
~~~

function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }
*/
/*
> (Just making a new function that uses the functions above)
*/
~~~

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

Events

/*
Events
> a way for the contract to communicate with the front-end (that may be listening)
*/
pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    event NewZombie(uint zombieId, string name, uint dna);

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string memory _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1; // array.push() returns the 'new length' of array
        emit NewZombie(id, _name, _dna);
    }

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

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

}

Web.js Sample

/*
Web3.js Sample
*/
// 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
}

End of Lesson 1

image.png share.cryptozombies.io/en/lesson/1/share/He..

ย