Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release Lesson 5 content #147

Merged
merged 1 commit into from
Mar 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions en/4/battle-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ material:
language: sol
startingCode:
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-03.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ material:
language: sol
startingCode:
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-04.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ material:
}
}
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-05.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ material:

}
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-06.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ material:
language: sol
startingCode:
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-07.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ material:

}
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-08.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ material:
language: sol
startingCode:
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
2 changes: 2 additions & 0 deletions en/4/battle-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ material:
language: sol
startingCode:
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
Expand Down
13 changes: 13 additions & 0 deletions en/5/00-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: ERC721 & Crypto-Collectibles
header: "Lesson 5: ERC721 & Crypto-Collectibles"
roadmap: roadmap5.jpg
---

Whew! Things are starting to heat up in here...

In this lesson, we're going to get a bit more advanced.

We're going to talk about **tokens**, the **ERC721** standard, and **crypto-collectible assets**.

In other words, we're going to **make it so you can trade your zombies with your friends.**
289 changes: 289 additions & 0 deletions en/5/01-erc721-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
---
title: Tokens on Ethereum
actions: ['checkAnswer', 'hints']
requireLogin: true
material:
editor:
language: sol
startingCode:
"zombieownership.sol": |
// Start here
"zombieattack.sol": |
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
uint randNonce = 0;
uint attackVictoryProbability = 70;

function randMod(uint _modulus) internal returns(uint) {
randNonce++;
return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
}

function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
Zombie storage enemyZombie = zombies[_targetId];
uint rand = randMod(100);
if (rand <= attackVictoryProbability) {
myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
myZombie.lossCount++;
enemyZombie.winCount++;
_triggerCooldown(myZombie);
}
}
}
"zombiehelper.sol": |
pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

uint levelUpFee = 0.001 ether;

modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}

function withdraw() external onlyOwner {
owner.transfer(this.balance);
}

function setLevelUpFee(uint _fee) external onlyOwner {
levelUpFee = _fee;
}

function levelUp(uint _zombieId) external payable {
require(msg.value == levelUpFee);
zombies[_zombieId].level++;
}

function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].name = _newName;
}

function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) {
zombies[_zombieId].dna = _newDna;
}

function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
uint counter = 0;
for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}

}
"zombiefeeding.sol": |
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
function getKitty(uint256 _id) external view returns (
bool isGestating,
bool isReady,
uint256 cooldownIndex,
uint256 nextActionAt,
uint256 siringWithId,
uint256 birthTime,
uint256 matronId,
uint256 sireId,
uint256 generation,
uint256 genes
);
}

contract ZombieFeeding is ZombieFactory {

KittyInterface kittyContract;

modifier ownerOf(uint _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
_;
}

function setKittyContractAddress(address _address) external onlyOwner {
kittyContract = KittyInterface(_address);
}

function _triggerCooldown(Zombie storage _zombie) internal {
_zombie.readyTime = uint32(now + cooldownTime);
}

function _isReady(Zombie storage _zombie) internal view returns (bool) {
return (_zombie.readyTime <= now);
}

function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
Zombie storage myZombie = zombies[_zombieId];
require(_isReady(myZombie));
_targetDna = _targetDna % dnaModulus;
uint newDna = (myZombie.dna + _targetDna) / 2;
if (keccak256(_species) == keccak256("kitty")) {
newDna = newDna - newDna % 100 + 99;
}
_createZombie("NoName", newDna);
_triggerCooldown(myZombie);
}

function feedOnKitty(uint _zombieId, uint _kittyId) public {
uint kittyDna;
(,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
feedAndMultiply(_zombieId, kittyDna, "kitty");
}
}
"zombiefactory.sol": |
pragma solidity ^0.4.19;

import "./ownable.sol";

contract ZombieFactory is Ownable {

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

uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;

struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}

Zombie[] public zombies;

mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;

function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;
NewZombie(id, _name, _dna);
}

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

function createRandomZombie(string _name) public {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}

}
"ownable.sol": |
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}


/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}


/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}

}
answer: |
pragma solidity ^0.4.19;

import "./zombieattack.sol";

contract ZombieOwnership is ZombieAttack {

}
---

Let's talk about **_tokens_**.

If you've been in the Ethereum space for any amount of time, you've probably heard people talking about tokens — specifically **_ERC20 tokens_**.

A **_token_** on Ethereum is basically just a smart contract that follows some common rules — namely it implements a standard set of functions that all other token contracts share, such as `transfer(address _to, uint256 _value)` and `balanceOf(address _owner)`.

Internally the smart contract usually has a mapping, `mapping(address => uint256) balances`, that keeps track of how much balance each address has.

So basically a token is just a contract that keeps track of who owns how much of that token, and some functions so those users can transfer their tokens to other addresses.

### Why does it matter?

Since all ERC20 tokens share the same set of functions with the same names, they can all be interacted with in the same ways.

This means if you build an application that is capable of interacting with one ERC20 token, it's also capable of interacting with any ERC20 token. That way more tokens can easily be added to your app in the future without needing to be custom coded. You could simply plug in the new token contract address, and boom, your app has another token it can use.

One example of this would be an exchange. When an exchange adds a new ERC20 token, really it just needs to add another smart contract it talks to. Users can tell that contract to send tokens to the exchange's wallet address, and the exchange can tell the contract to send the tokens back out to users when they request a withdraw.

The exchange only needs to implement this transfer logic once, then when it wants to add a new ERC20 token, it's simply a matter of adding the new contract address to its database.

### Other token standards

ERC20 tokens are really cool for tokens that act like currencies. But they're not particularly useful for representing zombies in our zombie game.

For one, zombies aren't divisible like currencies — I can send you 0.237 ETH, but transfering you 0.237 of a zombie doesn't really make sense.

Secondly, all zombies are not created equal. Your Level 2 zombie "**Steve**" is totally not equal to my Level 732 zombie "**H4XF13LD MORRIS 💯💯😎💯💯**". (Not even close, *Steve*).

There's another token standard that's a much better fit for crypto-collectibles like CryptoZombies — and they're called **_ERC721 tokens._**

**_ERC721 tokens_** are **not** interchangeable since each one is assumed to be unique, and are not divisible. You can only trade them in whole units, and each one has a unique ID. So these are a perfect fit for making our zombies tradeable.

> Note that using a standard like ERC721 has the benefit that we don't have to implement the auction or escrow logic within our contract that determines how players can trade / sell our zombies. If we conform to the spec, someone else could build an exchange platform for crypto-tradable ERC721 assets, and our ERC721 zombies would be usable on that platform. So there are clear benefits to using a token standard instead of rolling your own trading logic.

## Putting it to the Test

We're going to dive into the ERC721 implementation in the next chapter. But first, let's set up our file structure for this lesson.

We're going to store all the ERC721 logic in a contract called `ZombieOwnership`.

1. Declare our `pragma` version at the top of the file (check previous lessons' files for the syntax).

2. This file should `import` from `zombieattack.sol`.

3. Declare a new contract, `ZombieOwnership`, that inherits from `ZombieAttack`. Leave the body of the contract empty for now.
Loading