Advanced Solidity Concepts — CryptoZombies Lesson 3 and 4

Connor Wiseman
Bitfwd
Published in
15 min readMay 14, 2018

Now that your DApp enables you to create zombies and feed on kittys and other zombies, it’s time to start learning some advanced Solidity concepts such as time units, modifiers, gas and ownable contracts. Being able to utilise these kinds of modifications and privacy measures will help your coding skills immensely and will give you the foundations for the architecture of a well oiled back end piece of code.

If you have just joined this series and do not have the code for lessons 1 and 2 please go to the first blog and follow through the tutorial.

https://medium.com/bitfwd/solidity-fundamentals-cryptozombies-lesson-one-and-two-64ccd2631cef

Chapter One — Immuatability of contracts

Remember in the last lesson we encoded a piece of the cryptoKitties contract so that our zombies can feed on kittys. However there was just one problem with our piece of code that we just inherited, The contract is holding a bug (bravo if you found it already!) that if exploited may be used to take down the game in the future or even worse used to steal funds.

You’re probably wondering what the bug is and how do we get around this problem? The bug is only minor but has the potential to be catastrophic and can be avoided with a simple function. We will need to give our smart contract the functionality of being able to change the cryptoKitties’s contract address, so that if in the future the current cryptoKitties address is compromised in anyway we can save our D-APP. This will require you to delete line23 of the smart contract, edit line25 and add a new function on line27.

The new function you will add the contract will set kittyContract equal to kittyInterface with one parameter.

AddressChange.excercise

Chapter Two and Three — Ownable contracts

Ownable contracts are contracts that are the base of permissions within a smart contract. The most widely known and probably most used ownable contract is from openZeppelin. The openZepplin smart contract provides the DApp with the ownable modifier functionalities. These kind of modifier contracts are used for many kinds situations in Ethereum development, such as payments, lifecycles and ownerships. In the tutorial you should have been taken the zombieFeeding.sol contract where you will be asked to import and inherit the ownable contract into zombieFeeding so that the functions can be used throughout the zombieFeeding.sol contract and all others that are linked to zombieFeeding.

Inheritance.exercise

Once you have imported the ownable contract you will be on chapter 3 where you only have you modify the function you wrote in chapter one setKittyContractAddress to take the onlyOwner modifer from the ownable.sol contract that whats inherited in chapter two.

Modifier.exercise

openZeppelins Github

https://github.com/OpenZeppelin/openzeppelin-solidity

Chapter Four — Gas

Its time to start thinking about gas cost when it comes to calling functions inside our contract. If you have no knowledge of gas I will just touch a little on why gas is needed for Ethereum and why you should think about how your data is structured in the contract so you can use as little as possible.

Gas in Ethereum is needed so that transactions for functions calls can be sent to the network, a certain amount of gas is required for every operation that happens on the network. Without gas, a transaction can not be added to the blockchain, gas is specified in two variables price and limit . Gas price is the price you are willing pay to get your transaction sent to the network. And gas Limit is total amount of gas a transaction can use until it is reverted and stopped from being executed. The average gas price is usually determined on how much is going on in the network. If a lot of the transactions are happening you will want to put your gas price higher than normal 2gwei which is about 8 cents. Gas limit is always around 2100000 gwei for function calls and relatively higher for contract deployment.

Table of global Ethereum gas calculations. (NOTE: gwei is the most commonly used denominator for gas)

In chapter four you will be asked to add two new variables to your Zombie struct, uint32 Level, uint32 readyTime. You notice that we are using uint32 instead of uint256 or uint (uint is a alias of uint256). This is because when data is pushed on the stack through the EVM. Having uint, uint32, unit32 uses less gas and takes up less storage on the EVM, than using uint, uint32, uint because allocating storage variables together inside a struct will allow for small storage inside the EVM and thus saving gas.

If uint, uint32, uint was pushed on to the stack, the first uint would take up a slot on its own then uint32 and uint would take up another two slots. But if you code the data in order of storage like the way we doing, it will use one slot for uint and one slot for both uint32. Which is why it pays to think about gas costs and how you are storing your variables within a struct.

CompactDataStructure.example
CompactDataStructure.exercise

Chapter Five — Time units

We are going to start using time units in our smart contract so our zombies can only do certain things like feed or battling after certain time periods. First you will need to give another state variable to the contract zombieFeeding.sol called cooldownTime and set it equal to 1 days. Solidity offers a few different time variables seconds, minutes, hours, days, weeks and years and you can use all these variables in any smart contract that you write.

One example is a contract that will hold funds and only allow a release of the funds every week, or year for a certain number of hours or minutes.

Once you have declared the state variable you must then add the level and readyTime variable from chapter four to the line of code that creates a zombie in the _createZombie function, readyTime will now + cooldownTime.

timeStamp.example
timeStamp.exercise
timeStamp.exercise

Chapters Six and Seven — CoolDowns & Security

Time to create some more functions that will refer to time units and to our zombies, you will be asked to have defined two new functions one called _triggerCooldown and _isReady both functions have a Zombie storage _zombie pointer as the parameter of the function. The body of the _triggerCooldown function should set _zombie.readyTime equal to uint32(now + cooldownTime) and _isReady should return _zombie.readyTime ≤= now which requires a (bool) in the declaration. Both should be internal functions as well so that only other functions in our contract can call them.

coolDown.example
coolDown.exercise

In chapter seven you will put the functions into use in the feedAndMultiply, first you will be asked to make feedAndMultiply internal. And add a requirement statement in the line40 for _isReady and call _triggerCooldown at the very end of feedAndMultiply.

Notice how you are using the functions to provide a set of rules or restrictions on another function so that it will only execute after certain time periods.

Security.exercise

Chapter Eight and Nine — Modifiers

The tutorial will give you a new contract called zombieHelper.sol which will already inherit zombiefeeding.sol, you will just have to create a modifier function with two parameters uint _level + uint _zombieId the body of the function should require that the _zombieId.level from the zombies struct is greater or equal to _level

Once the modifier is created, you will be on chapter nine which is going to get you to create two more new functions for the new contract called changeName and changeDna Both functions will be nearly identical apart from the second argument _zombieName, _zombieDna and the body of the functions will have a slight difference in the last line of each function where zombies[_zombieId].name = _newName will be .dna in the changeDna function. Both functions should be external and take the aboveLevel modifier in the declaration, while also requiring on the first line of the function that msg.sender == zombieToOwner[_zombieId]. note: that name will take aboveLevel 2 and dna will take aboveLevel 20

Chapters Ten and Eleven — Saving and Storing

View functions are functions that do not require any gas to be executed on the network as all they do is view some data that is already on the blockchain. We will need to use view as part of our function declaration in chapter ten because we want this function to return all zombies that are owned by the address calling the function. It shall also have to return an array [] of uints.

Note: If a view function is called internally from another function in the same contract that is not a view function, it will still cost gas. This is because the other function creates a transaction on Ethereum, and will still need to be verified from every node. So view functions are only free when they're called externally.

Chapter eleven will get you to build out the body the function by declaring a uint[] memory which will be called result and be set equal the amount of zombies owned by msg.sender in the ownerZombieCount mapping. The function should return result on the last line.

Storage.exercise

Chapter Twelve — For Loops

You may be wondering how to check which zombie is owned by which address, this is where for loops come in. for loops allow you to declare a variable and iterate it through all the objects saved in the specified memory slot. (i.e mapping, array). They can also have a if statement that compares each address once to each zombie. When the for loop has finished we can see if we have a match. Basic principles for using a loop is to check if data is saved on the blockchain, or to check which object is linked to which address, they are very handy when dealing with big data or a lot of variables. Before you start the loop you will have to define a variable uint counter = 0 then you can start by coding for (uint i = 0; i < zombies.length; I++) {} that is the iteration through the all the zombies in our array. After that you use if(ownerZombieCount[I] == _owner) to compare which zombie is owned by which address. Now the for loop has finished we can store result[counter] = I; and increment counter before returning our array.

ForLoop.example
ForLoop.exercise

That is it for Lesson three guys, Hope you all got through with ease. Scroll down for lesson 4 and more decentralised smart contract programming

CryptoZombies lesson 4

In the previous lessons we have covered Solidity basics and some advanced concepts of smart contracts. We have inherited contracts and functions from other open source smart contracts such as CryptoKitties, we’ve also added modifiers to some functions, allowed for zombies to coolDown after battling with time units and even added loops so our smart contract can check to see which zombies are attached to which address. Now in lesson 4 we are going to make our smart contract payable so that it can accept ether’s and also make it so you are able withdraw ethers if you are the contract owner. We are also going to develop more of the battle system that will be implemented within the DApp. This lesson is not as complex as the others but it will give you more clarity on how real-world gamified DApp’s that people use are built.

chapter 1 — payable

The first chapter in lesson four will ask you to define a levelUpFeethat is set to 0.001 'ether'. Once written in you must then create a function that allow’s users of the DApp to level up their zombie by paying the specified amount of ether that is set in the state variable. The function will take one parameter and be an external payable function. You must remember to define a function as payableif you want to make users pay to use that particular function of the contract. As always when it comes to paying for things. Certain circumstances will always change when a store of value is transferred between two parties. (in this instance its the smart contract address and the user of the DApp) So you must code into the body of the function what requirements need to be met for the changes to happen and then of course tell the contract to change the details of said circumstance’s. The first line of the body will use a requirestatement to make sure that the msg.valueis equal to the levelUpFee, then second the function should increment the level of the zombieIdthat was specified in the transaction.

Payable.example
Payable.exercise

Chapter 2 — Withdrawls

Chapter 2 is some what the fun part of allowing the contract to receive money, as you have to create a withdraw function with the the modifier onlyOwner()its a pretty straight forward function to create. As the syntax is given to you in the information bar on the left of the page. Once you have coded the withdrawfunction to the contract you will then be given the task of making a function that allows the owner of the contract to define a levelUpFeeof there choice. You are asked to do this because the price of Ethereum as you may already know is subject to volatile price movements, that may cause unnecessary discomfort for the users of our D-APP. The function will only take one parameter _fee, and will set levelUpFeeequal to the parameter.

Withdrawl.example
Withdrawl.exercise

Chapter 3 and 4 — Zombie battles & random numbers.

Now its time to start creating our battle contract in chapter 3, once you have created the new contract and inherited zombiehelper.sol. Random numbers are the next thing(chapter 4), we need random numbers so when our zombies battle, the outcome of the battle will be defined in a random keccak256hash by taking the timestamp of now, the address of the msg.senderand a random nonce. which will be incremented in the first line of the function. The second line of the function will return the uint256(keccak256 with the three properties described above that will also be divided by _modulus.

RandomNumber.example
RandomNumber.exercise

Chapters 5 — Zombie fighting

Chapter 5 will require you to define a new state variable in the battle contract called called attackVictoryProbability which should be set too 70and underneath our randMod function. You will have to code a external function called attack while passing it _zombieId, and _targetId. Leave the body of the function empty for now we will get back to that in a giffy.

ZombieFighting.exercise

Chapter 6 & 7 — reFactoring

Time for some reFactoring(chapter 6) of some of our other contracts, You will be asked to create a modifier in the ZombieFeeding contract called ownerOf which will have to require that the msg.sender is the owner of the _zombieId

reFactoring.example
reFactoring.excersise(1)
reFactoring.exercsie(2)

Once the modifier is created you can go to the feedAndMultiply function of the contract and remove the line that is identical to the requirement of the modifier (don't forget to add the modifier to the function declaration on the above line).

In chapter 7 you will be taken to zombiehelper.sol and you will be asked to perform the same action as chapter 6 on the changeNameand changeDna functions but will not ask you define the modifier in the contract (I’ll leave that one for you to work out :P ).

reFactoring.exercise — chapter 7

Chapter 8 — Back to attack!

Chapter 8 takes us back to the attack function in the ZombieBattle contract where the ownerOfmodifier will also need to be implemented in the function declaration. In the function you will need to input a storage pointer to the Zombies being used, so the contract can behave more fluently when a user has called the attack function. The pointers will be used on both [_zombieiD]and [_targetId]. Now you may be wondering how the outcome of the battle is going to be decided and, What we are going to do is. On the last line of the function we are going to define a uint randand set it equal to randModwith (100)as a argument.This will allow for the battles to be determined by math equation which is a percentage of 100.

Storage.exercise

chapter 9 — Wins & Losses

Now you will have to add more variables to the zombie struct we created in lesson 1 so that the contract will understand that the zombies win&loss count will be of value uint16. These values also have to be passed into the first line of the _createZombiefunction where the zombie is pushed into the array. So that when a zombie is created it will also have 0 wins and 0 losses when it is created.

Win&Losses.exercise(1)
Win&Losses.exercise(2)

Chapter 10 & 11 — Implementing wins & losses

Will show you how to increment a zombie’s win and loss count by using an ‘if’ statement that checks if “rand” is less than or equal to attackVictoryProbability. Once you have defined the code for a winning battle with your zombie (chapter 10) by incrementing myZombie’s winCount, level, and enemyZombies lossCount. You must then code in the feedAndMultiplyfunction so that when your zombie win’s a battle it will feed on the enemy zombie and use both, your zombie’s DNA and enemy’s DNA to create a new zombie. After you will be taken to chapter 11 where you will have to allow the smart contract to increment myZombie’s lossCount and the enemyZombie’s winCount for a battle result where the enemyZombie wins. By using a else statement after the feedAndmultiplyfunction. The syntax will be 90% similar to how you did the first incrementation. Although the only difference is that at the end of the else statement you will have to use the _triggerCooldownfunction so that you can not battle with your zombie for another 24 hours. (remember that _triggerCooldownis already implemented in feedAndMultiply) so there was no need to code the function in the with the first incrementation of a battle where myZombie would have won.

WinCount.exercise
else.example
LossCount.exercise

Conclusion

Congratulations !! you are now well on your way developing your first decentralised application that can accept payments, give users the opportunity to battle with other game players, and even keep a record of how many battles each user has won or lost. You have also given the smart contract a variety of security measures while also cutting the code into a more reasonably friendly easy to read smart contract. The important parts to take from these past two lessons are the changing of addresses with the kittieContract, the modifiers that allow only a ownerOf to access a certain zombie, The payable functions so your contract can accept ETH and how to keep track of how many times a person that plays the game has battled with their zombies.

WELL DONE!!!! It’s a lot to take in and I hope you enjoyed as always.

Chapters 5 & 6 will be out very soon so keep your eye peeled. Once again thank you and please don’t forget to give a clap or leave a review.

--

--

Connor Wiseman
Bitfwd
Writer for

Blockchain architect — interested in decentralised solutions that remove the currently centralised middlemen of many industries.