diff --git a/.gitignore b/.gitignore index 41f6b8b305..4512c35d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ public/ build/ .tmp .idea/ +/.vs/ \ No newline at end of file diff --git a/en/3/02-ownable.md b/en/3/02-ownable.md index 6baee5e9fc..3997d6bfe0 100644 --- a/en/3/02-ownable.md +++ b/en/3/02-ownable.md @@ -189,7 +189,7 @@ To handle cases like this, one common practice that has emerged is to make contr ## OpenZeppelin's `Ownable` contract -Below is the `Ownable` contract taken from the **_OpenZeppelin_** Solidity library. OpenZeppelin is a library of secure and community-vetted smart contracts that you can use in your own DApps. After this lesson, while you anxiously await the release of Lesson 4, we highly recommend you check out their site to further your learning! +Below is the `Ownable` contract taken from the **_OpenZeppelin_** Solidity library. OpenZeppelin is a library of secure and community-vetted smart contracts that you can use in your own DApps. After this lesson, we highly recommend you check out their site to further your learning! Give the contract below a read-through. You're going to see a few things we haven't learned yet, but don't worry, we'll talk about them afterward. diff --git a/en/3/08-functionmodifiers.md b/en/3/08-functionmodifiers.md index 58886c3472..8460dc4411 100644 --- a/en/3/08-functionmodifiers.md +++ b/en/3/08-functionmodifiers.md @@ -188,7 +188,7 @@ mapping (uint => uint) public age; // Modifier that requires this user to be older than a certain age: modifier olderThan(uint _age, uint _userId) { - require (age[_userId] >= _age); + require(age[_userId] >= _age); _; } diff --git a/en/4/battle-02.md b/en/4/battle-02.md index 27abafa410..de097de432 100644 --- a/en/4/battle-02.md +++ b/en/4/battle-02.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -203,6 +205,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-03.md b/en/4/battle-03.md index 8306f6254e..2cf810c13f 100644 --- a/en/4/battle-03.md +++ b/en/4/battle-03.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -211,6 +213,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-04.md b/en/4/battle-04.md index b4a55333cb..a62501363e 100644 --- a/en/4/battle-04.md +++ b/en/4/battle-04.md @@ -66,6 +66,8 @@ material: } } "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-05.md b/en/4/battle-05.md index 7e5aee6664..18fcfd76fc 100644 --- a/en/4/battle-05.md +++ b/en/4/battle-05.md @@ -59,6 +59,8 @@ material: } "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -238,6 +240,11 @@ material: 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; diff --git a/en/4/battle-06.md b/en/4/battle-06.md index 72b94ddeff..8e19ce88fb 100644 --- a/en/4/battle-06.md +++ b/en/4/battle-06.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -216,6 +218,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-07.md b/en/4/battle-07.md index 49bfc7c89c..dbd29fae44 100644 --- a/en/4/battle-07.md +++ b/en/4/battle-07.md @@ -54,6 +54,8 @@ material: } "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-08.md b/en/4/battle-08.md index 674244e950..a6cdb2da5e 100644 --- a/en/4/battle-08.md +++ b/en/4/battle-08.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -220,6 +222,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/4/battle-09.md b/en/4/battle-09.md index a64ba536e9..38990c31d9 100644 --- a/en/4/battle-09.md +++ b/en/4/battle-09.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -225,6 +227,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/en/5/00-overview.md b/en/5/00-overview.md new file mode 100644 index 0000000000..1f92e9f6f7 --- /dev/null +++ b/en/5/00-overview.md @@ -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.** diff --git a/en/5/01-erc721-1.md b/en/5/01-erc721-1.md new file mode 100644 index 0000000000..c1088acb80 --- /dev/null +++ b/en/5/01-erc721-1.md @@ -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. diff --git a/en/5/02-erc721-2.md b/en/5/02-erc721-2.md new file mode 100644 index 0000000000..6ed7ed9756 --- /dev/null +++ b/en/5/02-erc721-2.md @@ -0,0 +1,310 @@ +--- +title: ERC721 Standard, Multiple Inheritance +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + // Import file here + + // Declare ERC721 inheritance here + contract ZombieOwnership is ZombieAttack { + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + } +--- + +Let's take a look at the ERC721 standard: + +``` +contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; +} +``` + +This is the list of methods we'll need to implement, which we'll be doing over the coming chapters in pieces. + +It looks like a lot, but don't get overwhelmed! We're here to walk you through it. + +> Note: The ERC721 standard is currently a *draft*, and there is no officially agreed-upon implementation yet. For this tutorial we're using the current version from OpenZeppelin's library, but it is possible it will change in the future before its official release. So consider this **one** possible implementation, but don't take it as the official standard for ERC721 tokens. + +### Implementing a token contract + +When implementing a token contract, the first thing we do is copy the interface to its own Solidity file and import it, `import ./erc721.sol`. Then we have our contract inherit from it, and we override each method with a function definition. + +But wait — `ZombieOwnership` is already inheriting from `ZombieAttack` — how can it also inherit from `ERC721`? + +Luckily in Solidity, your contract can inherit from multiple contracts as follows: + +``` +contract SatoshiNakamoto is NickSzabo, HalFinney { + // Omg, the secrets of the universe revealed! +} +``` + +As you can see, when using multiple inheritance, you just separate the multiple contracts you're inheriting from with a comma, `,`. In this case, our contract is inheriting from `NickSzabo` and `HalFinney`. + +Let's give it a try. + +## Putting it to the Test + +We've already created `erc721.sol` with the interface above for you. + +1. Import `erc721.sol` into `zombieownership.sol` + +2. Declare that `ZombieOwnership` inherits from `ZombieAttack` AND `ERC721` diff --git a/en/5/03-erc721-3.md b/en/5/03-erc721-3.md new file mode 100644 index 0000000000..5b6ab88d50 --- /dev/null +++ b/en/5/03-erc721-3.md @@ -0,0 +1,338 @@ +--- +title: balanceOf & ownerOf +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + // 1. Return the number of zombies `_owner` has here + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + // 2. Return the owner of `_tokenId` here + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Great, let's dive into the ERC721 implementation! + +We've gone ahead and copied the empty shell of all the functions you'll be implementing in this lesson. + +In this chapter, we're going to implement the first two methods: `balanceOf` and `ownerOf`. + +### `balanceOf` + +``` + function balanceOf(address _owner) public view returns (uint256 _balance); +``` + +This function simply takes an `address`, and returns how many tokens that `address` owns. + +In our case, our "tokens" are Zombies. Do you remember where in our DApp we stored how many zombies an owner has? + +### `ownerOf` + +``` + function ownerOf(uint256 _tokenId) public view returns (address _owner); +``` + +This function takes a token ID (in our case, a Zombie ID), and returns the `address` of the person who owns it. + +Again, this is very straightforward for us to implement, since we already have a `mapping` in our DApp that stores this information. We can implement this function in one line, just a `return` statement. + +> Note: Remember, `uint256` is equivalent to `uint`. We've been using `uint` in our code up until now, but we're using `uint256` here because we copy/pasted from the spec. + +## Putting it to the Test + +I'll leave it to you to figure out how to implement these 2 functions. + +Each function should simply be 1 line of code, a `return` statement. Take a look at our code from previous lessons to see where we're storing this data. If you can't figure it out, you can hit the "show me the answer" button for some help. + +1. Implement `balanceOf` to return the number of zombies `_owner` has. + +2. Implement `ownerOf` to return the address of whoever owns the zombie with ID `_tokenId`. diff --git a/en/5/04-erc721-4.md b/en/5/04-erc721-4.md new file mode 100644 index 0000000000..db65c6cbb2 --- /dev/null +++ b/en/5/04-erc721-4.md @@ -0,0 +1,355 @@ +--- +title: Refactoring +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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; + + // 1. Change modifier name to `onlyOwnerOf` + 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); + } + + // 2. Change modifier name here as well + 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"); + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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; + } + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } +--- + +Uh oh! We've just introduced an error in our code that will make it not compile. Did you notice it? + +In the previous chapter we defined a function called `ownerOf`. But if you recall from Lesson 4, we also created a `modifier` with the same name, `ownerOf`, in `zombiefeeding.sol`. + +If you tried compiling this code, the compiler would give you an error saying you can't have a modifier and a function with the same name. + +So should we just change the function name in `ZombieOwnership` to something else? + +No, we can't do that!!! Remember, we're using the ERC721 token standard, which means other contracts will expect our contract to have functions with these exact names defined. That's what makes these standards useful — if another contract knows our contract is ERC721-compliant, it can simply talk to us without needing to know anything about our internal implementation decisions. + +So that means we'll have to refactor our code from Lesson 4 to change the name of the `modifier` to something else. + +## Putting it to the Test + +We're back in `zombiefeeding.sol`. We're going to change the name of our `modifier` from `ownerOf` to `onlyOwnerOf`. + +1. Change the name of the modifier definition to `onlyOwnerOf` + +2. Scroll down to the function `feedAndMultiply`, which uses this modifier. We'll need to change the name here as well. + +> Note: We also use this modifier in `zombiehelper.sol` and `zombieattack.sol`, but so we don't spend too much of this lesson refactoring, we've gone ahead and changed the modifier names in those files for you. diff --git a/en/5/05-erc721-5.md b/en/5/05-erc721-5.md new file mode 100644 index 0000000000..875f4afd35 --- /dev/null +++ b/en/5/05-erc721-5.md @@ -0,0 +1,347 @@ +--- +title: "ERC721: Transfer Logic" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + // Define _transfer() here + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Great, we've fixed the conflict! + +Now we're going to continue our ERC721 implementation by looking at transfering ownership from one person to another. + +Note that the ERC721 spec has 2 different ways to transfer tokens: + +``` +function transfer(address _to, uint256 _tokenId) public; +function approve(address _to, uint256 _tokenId) public; +function takeOwnership(uint256 _tokenId) public; +``` + +1. The first way is the token's owner calls `transfer` with the `address` he wants to transfer to, and the `_tokenId` of the token he wants to transfer. + +2. The second way is the token's owner first calls `approve`, and sends it the same info as above. The contract then stores who is approved to take a token, usually in a `mapping (uint256 => address)`. Then when someone calls `takeOwnership`, the contract checks if that `msg.sender` is approved by the owner to take the token, and if so it transfers the token to him. + +If you'll notice, both `transfer` and `takeOwnership` will contain the same transfer logic, just in reverse order. (In one case the sender of the token calls the function; in the other the receiver of the token calls it). + +So it makes sense for us to abstract this logic into its own private function, `_transfer`, which is then called by both functions. That way we don't repeat the same code twice. + +## Putting it to the Test + +Let's define the logic for `_transfer`. + +1. Define a function named `_transfer`. It will take 3 arguments, `address _from`, `address _to`, and `uint256 _tokenId`. It should be a `private` function. + +2. We have 2 mappings that will change when ownership changes: `ownerZombieCount` (which keeps track of how many zombies an owner has) and `zombieToOwner` (which keeps track of who owns what). + + The first thing our function should do is increment `ownerZombieCount` for the person **receiving** the zombie (`address _to`). Use `++` to increment. + +3. Next, we'll need to **decrease** the `ownerZombieCount` for the person **sending** the zombie (`address _from`). Use `--` to decrement. + +4. Lastly, we'll want to change `zombieToOwner` mapping for this `_tokenId` so it now points to `_to`. + +5. I lied, that wasn't the last step. There's one more thing we should do. + + The ERC721 spec includes a `Transfer` event. The last line of this function should fire `Transfer` with the correct information — check `erc721.sol` to see what arguments it's expecting to be called with and implement it here. diff --git a/en/5/06-erc721-6.md b/en/5/06-erc721-6.md new file mode 100644 index 0000000000..3425813c34 --- /dev/null +++ b/en/5/06-erc721-6.md @@ -0,0 +1,327 @@ +--- +title: "ERC721: Transfer Cont'd" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + // 1. Add modifier here + function transfer(address _to, uint256 _tokenId) public { + // 2. Define function here + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Great! That was the difficult part — now implementing the public `transfer` function will be easy, since our `_transfer` function already does almost all the heavy lifting. + +## Putting it to the Test + +1. We want to make sure only the owner of a token/zombie can transfer it. Remember how we can limit access to a function to only its owner? + + Yep, that's right, we already have a modifier that does this. So add the `onlyOwnerOf` modifier to this function. + +2. Now the body of the function really only needs to be one line... It just needs to call `_transfer`. + + Make sure to pass in `msg.sender` for the `address _from` argument. diff --git a/en/5/07-erc721-7.md b/en/5/07-erc721-7.md new file mode 100644 index 0000000000..766c8bed13 --- /dev/null +++ b/en/5/07-erc721-7.md @@ -0,0 +1,342 @@ +--- +title: "ERC721: Approve" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + // 1. Define mapping here + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + // 2. Add function modifier here + function approve(address _to, uint256 _tokenId) public { + // 3. Define function here + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Now, let's implement `approve`. + +Remember, with `approve` / `takeOwnership`, the transfer happens in 2 steps: + +1. You, the owner, call `approve` and give it the `address` of the new owner, and the `_tokenId` you want him to take + +2. The new owner calls `takeOwnership` with the `_tokenId`, the contract checks to make sure he's already been approved, and then transfers him the token. + +Because this happens in 2 function calls, we need a data structure to store who's been approved for what in between function calls. + +## Putting it to the Test + +1. First, let's define a mapping `zombieApprovals`. It should map a `uint` to an `address`. + + This way, when someone calls `takeOwnership` with a `_tokenId`, we can use this mapping to quickly look up who is approved to take that token. + +2. On the `approve` function, we want to make sure only the owner of the token can give someone approval to take it. So we need to add the `onlyOwnerOf` modifier to `approve` + +3. For the body of the function, set `zombieApprovals` for `_tokenId` equal to the `_to` address. + +4. Finally, there's an `Approval` event in the ERC721 spec. So we should fire this event at the end of this function. Check `erc721.sol` for the arguments, and be sure to use `msg.sender` as `_owner`. diff --git a/en/5/08-erc721-8.md b/en/5/08-erc721-8.md new file mode 100644 index 0000000000..f6582b640c --- /dev/null +++ b/en/5/08-erc721-8.md @@ -0,0 +1,340 @@ +--- +title: "ERC721: takeOwnership" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + // 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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +Great, now let's finish up our ERC721 implementation with the last function! (Don't worry, there's still more to cover in Lesson 5 after this 😉) + +The final function, `takeOwnership`, should simply check to make sure the `msg.sender` has been approved to take this token / zombie, and call `_transfer` if so. + +## Putting it to the Test + +1. First, we want to use a `require` statement to check that `zombieApprovals` for `_tokenId` is equal to `msg.sender`. + + That way if `msg.sender` hasn't been approved to take this token, it will throw an error. + +2. In order to call `_transfer`, we need to know the address of the person who owns the token (it requires `_from` as an argument). Luckily we can look this up with our function `ownerOf`. + + So declare an `address` variable named `owner`, and set it equal to `ownerOf(_tokenId)`. + +3. Finally, call `_transfer`, and pass it all the required information. (Here you can use `msg.sender` for `_to`, since the person calling this function is the one the token should be sent to). + +> Note: We could have done steps 2 and 3 in one line of code, but splitting it up makes things a bit more readable. Personal preference. diff --git a/en/5/09-safemath-1.md b/en/5/09-safemath-1.md new file mode 100644 index 0000000000..809002c736 --- /dev/null +++ b/en/5/09-safemath-1.md @@ -0,0 +1,444 @@ +--- +title: Preventing Overflows +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + // 1. Import here + + contract ZombieFactory is Ownable { + + // 2. Declare using safemath here + + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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); + } + + } +--- + +Congratulations, that completes our ERC721 implementation! + +That wasn't so tough, was it? A lot of this Ethereum stuff sounds really complicated when you hear people talking about it, so the best way to understand it is to actually go through an implementation of it yourself. + +Keep in mind that this is only a minimal implementation. There are extra features we may want to add to our implementation, such as some extra checks to make sure users don't accidentally transfer their zombies to address `0` (which is called "burning" a token — basically it's sent to an address that no one has the private key of, essentially making it unrecoverable). Or to put some basic auction logic in the DApp itself. (Can you think of some ways we could implement that?) + +But we wanted to keep this lesson manageable, so we went with the most basic implementation. If you want to see an example of a more in-depth implementation, you can take a look at the OpenZeppelin ERC721 contract after this tutorial. + +### Contract security enhancements: Overflows and Underflows + +We're going to look at one major security feature you should be aware of when writing smart contracts: Preventing overflows and underflows. + +What's an **_overflow_**? + +Let's say we have a `uint8`, which can only have 8 bits. That means the largest number we can store is binary `11111111` (or in decimal, 2^8 - 1 = 255). + +Take a look at the following code. What is `number` equal to at the end? + +``` +uint8 number = 255; +number++; +``` + +In this case, we've caused it to overflow — so `number` is counterintuitively now equal to `0` even though we increased it. (If you add 1 to binary `11111111`, it resets back to `00000000`, like a clock going from `23:59` to `00:00`). + +An underflow is similar, where if you subtract `1` from a `uint8` that equals `0`, it will now equal `255` (because `uint`s are unsigned, and cannot be negative). + +While we're not using `uint8` here, and it seems unlikely that a `uint256` will overflow when incrementing by `1` each time (2^256 is a really big number), it's still good to put protections in our contract so that our DApp never has unexpected behavior in the future. + +### Using SafeMath + +To prevent this, OpenZeppelin has created a **_library_** called SafeMath that prevents these issues by default. + +But before we get into that... What's a library? + +A **_library_** is a special type of contract in Solidity. One of the things it is useful for is to attach functions to native data types. + +For example, with the SafeMath library, we'll use the syntax `using SafeMath for uint256`. The SafeMath library has 4 functions — `add`, `sub`, `mul`, and `div`. And now we can access these functions from `uint256` as follows: + +``` +using SafeMath for uint256; + +uint256 a = 5; +uint256 b = a.add(3); // 5 + 3 = 8 +uint256 c = a.mul(2); // 5 * 2 = 10 +``` + +We'll look at what these functions do in the next chapter, but for now let's add the SafeMath library to our contract. + +## Putting it to the Test + +We've already included OpenZeppelin's `SafeMath` library for you in `safemath.sol`. You can take a quick peek at the code now if you want to, but we'll be looking at it in depth in the next chapter. + +First let's tell our contract to use SafeMath. We'll do this in ZombieFactory, our very base contract — that way we can use it in any of the sub-contracts that inherit from this one. + +1. Import `safemath.sol` into `zombiefactory.sol`. + +2. Add the declaration `using SafeMath for uint256;`. diff --git a/en/5/10-safemath-2.md b/en/5/10-safemath-2.md new file mode 100644 index 0000000000..ff4a2c1e61 --- /dev/null +++ b/en/5/10-safemath-2.md @@ -0,0 +1,469 @@ +--- +title: SafeMath Part 2 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + // 1. Replace with SafeMath's `add` + ownerZombieCount[_to]++; + // 2. Replace with SafeMath's `sub` + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[_from] = ownerZombieCount[_from].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +Let's take a look at the code behind SafeMath: + +``` +library SafeMath { + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} +``` + +First we have the `library` keyword — libraries are similar to `contract`s but with a few differences. For our purposes, libraries allow us to use the `using` keyword, which automatically tacks on all of the library's methods to another data type: + +``` +using SafeMath for uint; +// now we can use these methods on any uint +uint test = 2; +test = test.mul(3); // test now equals 6 +test = test.add(5); // test now equals 11 +``` + +Note that the `mul` and `add` functions each require 2 arguments, but when we declare `using SafeMath for uint`, the `uint` we call the function on (`test`) is automatically passed in as the first argument. + +Let's look at the code behind `add` to see what SafeMath does: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} +``` + +Basically `add` just adds 2 `uint`s like `+`, but it also contains an `assert` statement to make sure the sum is greater than `a`. This protects us from overflows. + +`assert` is similar to `require`, where it will throw an error if false. The difference between `assert` and `require` is that `require` will refund the user the rest of their gas when a function fails, whereas `assert` will not. So most of the time you want to use `require` in your code; `assert` is typically used when something has gone horribly wrong with the code (like a `uint` overflow). + +So, simply put, SafeMath's `add`, `sub`, `mul`, and `div` are functions that do the basic 4 math operations, but throw an error if an overflow or underflow occurs. + +### Using SafeMath in our code. + +To prevent overflows and underflows, we can look for places in our code where we use `+`, `-`, `*`, or `/`, and replace them with `add`, `sub`, `mul`, `div`. + +Ex. Instead of doing: + +``` +myUint++; +``` + +We would do: + +``` +myUint = myUint.add(1); +``` + +## Putting it to the Test + +We have 2 places in `ZombieOwnership` where we used math operations. Let's swap them out with SafeMath methods. + +1. Replace `++` with a SafeMath method. + +2. Replace `--` with a SafeMath method. diff --git a/en/5/11-safemath-3.md b/en/5/11-safemath-3.md new file mode 100644 index 0000000000..f5c43c8aea --- /dev/null +++ b/en/5/11-safemath-3.md @@ -0,0 +1,508 @@ +--- +title: SafeMath Part 3 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + // 1. Declare using SafeMath32 for uint32 + // 2. Declare using SafeMath16 for uint16 + + 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 { + // Note: We chose not to prevent the year 2038 problem... So don't need + // worry about overflows on readyTime. Our app is screwed in 2038 anyway ;) + uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; + zombieToOwner[id] = msg.sender; + // 3. Let's use SafeMath's `add` here: + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath32 + * @dev SafeMath library implemented for uint32 + */ + library SafeMath32 { + + function mul(uint32 a, uint32 b) internal pure returns (uint32) { + if (a == 0) { + return 0; + } + uint32 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint32 a, uint32 b) internal pure returns (uint32) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint32 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint32 a, uint32 b) internal pure returns (uint32) { + assert(b <= a); + return a - b; + } + + function add(uint32 a, uint32 b) internal pure returns (uint32) { + uint32 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath16 + * @dev SafeMath library implemented for uint16 + */ + library SafeMath16 { + + function mul(uint16 a, uint16 b) internal pure returns (uint16) { + if (a == 0) { + return 0; + } + uint16 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint16 a, uint16 b) internal pure returns (uint16) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint16 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint16 a, uint16 b) internal pure returns (uint16) { + assert(b <= a); + return a - b; + } + + function add(uint16 a, uint16 b) internal pure returns (uint16) { + uint16 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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); + } + + } +--- + +Great, now our ERC721 implementation is safe from overflows & underflows! + +Going back through the code we wrote in previous lessons, there's a few other places in our code that could be vulnerable to overflows or underflows. + +For example, in ZombieAttack we have: + +``` +myZombie.winCount++; +myZombie.level++; +enemyZombie.lossCount++; +``` + +We should prevent overflows here as well just to be safe. (It's a good idea in general to just use SafeMath instead of the basic math operations. Maybe in a future version of Solidity these will be implemented by default, but for now we have to take extra security precautions in our code). + +However we have a slight problem — `winCount` and `lossCount` are `uint16`s, and `level` is a `uint32`. So if we use SafeMath's `add` method with these as arguments, it won't actually protect us from overflow since it will convert these types to `uint256`: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} + +// If we call `.add` on a `uint8`, it gets converted to a `uint256`. +// So then it won't overflow at 2^8, since 256 is a valid `uint256`. +``` + +This means we're going to need to implement 2 more libraries to prevent overflow/underflows with our `uint16`s and `uint32`s. We can call them `SafeMath16` and `SafeMath32`. + +The code will be exactly the same as SafeMath, except all instances of `uint256` will be replaced with `uint32` or `uint16`. + +We've gone ahead and implemented that code for you — go ahead and look at `safemath.sol` to see the code. + +Now we need to implement it in ZombieFactory. + +## Putting it to the Test + +Assignment: + +1. Declare that we're using `SafeMath32` for `uint32`. + +2. Declare that we're using `SafeMath16` for `uint16`. + +3. There's one more line of code in ZombieFactory where we should use a SafeMath method. We've left a comment to indicate where. diff --git a/en/5/12-safemath-4.md b/en/5/12-safemath-4.md new file mode 100644 index 0000000000..c9b61090a9 --- /dev/null +++ b/en/5/12-safemath-4.md @@ -0,0 +1,381 @@ +--- +title: SafeMath Part 4 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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) { + // Here's one! + randNonce++; + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + // Here's 3 more! + myZombie.winCount++; + myZombie.level++; + enemyZombie.lossCount++; + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + // ...annnnd another 2! + myZombie.lossCount++; + enemyZombie.winCount++; + _triggerCooldown(myZombie); + } + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 = zombies[_zombieId].level.add(1); + } + + function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 = randNonce.add(1); + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + myZombie.winCount = myZombie.winCount.add(1); + myZombie.level = myZombie.level.add(1); + enemyZombie.lossCount = enemyZombie.lossCount.add(1); + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + myZombie.lossCount = myZombie.lossCount.add(1); + enemyZombie.winCount = enemyZombie.winCount.add(1); + _triggerCooldown(myZombie); + } + } + } +--- + +Great, now we can implement SafeMath on all the types of `uint`s we used in our DApp! + +Let's fix all those potential issues in `ZombieAttack`. (There was also one `zombies[_zombieId].level++;` that needed to be fixed in `ZombieHelper`, but we've taken care of that one for you so we don't take an extra chapter to do so 😉). + +## Putting it to the Test + +Go ahead and implement SafeMath methods on all the `++` increments in `ZombieAttack`. We've left comments in the code to make them easy to find. diff --git a/en/5/13-comments.md b/en/5/13-comments.md new file mode 100644 index 0000000000..c2de1b2218 --- /dev/null +++ b/en/5/13-comments.md @@ -0,0 +1,465 @@ +--- +title: Comments +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + /// TODO: Replace this with natspec descriptions + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +The Solidity code for our zombie game is finally finished! + +In the next lessons, we'll look at how to deploy the code to Ethereum, and how to interact with it with Web3.js. + +But one final thing before we let you go in Lesson 5: Let's talk about **commenting your code**. + +## Syntax for comments + +Commenting in Solidity is just like JavaScript. You've already seen some examples of single line comments throughout the CryptoZombies lessons: + +``` +// This is a single-line comment. It's kind of like a note to self (or to others) +``` + +Just add double `//` anywhere and you're commenting. It's so easy that you should do it all the time. + +But I hear you — sometimes a single line is not enough. You are born a writer, after all! + +Thus we also have multi-line comments: + +``` +contract CryptoZombies { + /* This is a multi-lined comment. I'd like to thank all of you + who have taken your time to try this programming course. + I know it's free to all of you, and it will stay free + forever, but we still put our heart and soul into making + this as good as it can be. + + Know that this is still the beginning of Blockchain development. + We've come very far but there are so many ways to make this + community better. If we made a mistake somewhere, you can + help us out and open a pull request here: + https://github.com/loomnetwork/cryptozombie-lessons + + Or if you have some ideas, comments, or just want to say + hi - drop by our Telegram community at https://t.me/loomnetwork + */ +} +``` + +In particular, it's good practice to comment your code to explain the expected behavior of every function in your contract. This way another developer (or you, after a 6 month hiatus from a project!) can quickly skim and understand at a high level what your code does without having to read the code itself. + +The standard in the Solidity community is to use a format called **_natspec_**, which looks like this: + +``` +/// @title A contract for basic math operations +/// @author H4XF13LD MORRIS 💯💯😎💯💯 +/// @notice For now, this contract just adds a multiply function +contract Math { + /// @notice Multiplies 2 numbers together + /// @param x the first uint. + /// @param y the second uint. + /// @return z the product of (x * y) + /// @dev This function does not currently check for overflows + function multiply(uint x, uint y) returns (uint z) { + // This is just a normal comment, and won't get picked up by natspec + z = x * y; + } +} +``` + +`@title` and `@author` are straightforward. + +`@notice` explains to a **user** what the contract / function does. `@dev` is for explaining extra details to developers. + +`@param` and `@return` are for describing what each parameter and return value of a function are for. + +Note that you don't always have to use all of these tags for every function — all tags are optional. But at the very least, leave a `@dev` note explaining what each function does. + +# Put it to the test + +If you haven't noticed by now, the CryptoZombies answer-checker ignores comments when it checks your answers. So we can't actually check your natspec code for this chapter ;) + +However, by now you're a Solidity whiz — we're just going to assume you've got this! + +Give it a try anyway, and try adding some natspec tags to `ZombieOwnership`: + +1. `@title` — E.g. A contract that manages transfering zombie ownership + +2. `@author` — Your name! + +3. `@dev` — E.g. Compliant with OpenZeppelin's implementation of the ERC721 spec draft diff --git a/en/5/14-wrappingitup.md b/en/5/14-wrappingitup.md new file mode 100644 index 0000000000..9040e18f83 --- /dev/null +++ b/en/5/14-wrappingitup.md @@ -0,0 +1,38 @@ +--- +title: Wrapping It Up +actions: ['checkAnswer', 'hints'] +requireLogin: true +skipCheckAnswer: true +material: + saveZombie: false + zombieDeck: + zombie: + lesson: 5 + hideSliders: true + answer: 1 +--- + +Congratulations! That concludes Lesson 5. + +As a reward, we've transferred you your very own Level 10 **H4XF13LD MORRIS 💯💯😎💯💯** zombie! + +(Omg, the legendary **H4XF13LD MORRIS 💯💯😎💯💯** zombie!!!!111) + +Now you have 4 zombies in your army. + +Before you move on, you have the option to rename any of them if you'd like by clicking on them to the right and entering a new name. (Though I don't know why you would ever want to rename **H4XF13LD MORRIS 💯💯😎💯💯**, clearly the best name ever). + +## Let's recap: + +In this lesson we learned about: + +- Tokens, the ERC721 standard, and tradable assets/zombies +- Libraries and how to use them +- How to prevent overflows and underflows using the SafeMath library +- Commenting your code and the natspec standard + +This lesson concludes our game's Solidity code! (For now — we may add even more lessons in the future). + +In the next 2 lessons, we're going to look at how to deploy your contracts and interact with them using **_web3.js_** (so you can build a front-end for your DApp). + +Go ahead and rename any of your zombies if you like, then proceed to the next chapter to complete the lesson. diff --git a/en/5/15-lessoncomplete.md b/en/5/15-lessoncomplete.md new file mode 100644 index 0000000000..c2bde12987 --- /dev/null +++ b/en/5/15-lessoncomplete.md @@ -0,0 +1,7 @@ +--- +title: Lesson 5 Complete! +actions: ['checkAnswer', 'hints'] +material: + lessonComplete: + answer: 1 +--- diff --git a/en/index.json b/en/index.json index e229fd88cb..3f6a150f96 100644 --- a/en/index.json +++ b/en/index.json @@ -109,12 +109,28 @@ "Share this URL so your friends can check out your army:" ], "footer": [ - "Lesson 5 coming in 1-2 weeks", + "Enjoying CryptoZombies? Let us know!", + "Join the conversation on {telegramLink}, or follow us on {twitterLink}", + "When you're ready, click the button below to access Lesson 5:" + ], + "zombieDesc": "A Level {levelNum} CryptoZombie", + "shareLinkText": "I just completed #CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Try it out yourself:" + }, + "lesson5": { + "achievementsUnlocked": "Achievements Unlocked:", + "newZombieAdded": "You received {zombieName}, a level {levelNum} CryptoZombie!", + "header": [ + "Congratulations! You have completed {lesson5} of CryptoZombies!", + "Show off your zombie army to your friends!", + "Share this URL so your friends can check out your full zombie army:" + ], + "footer": [ + "Lesson 6 will be out in the next few weeks!", "You'll get an email from us as soon as it's ready.", "In the meantime, join us on {telegramLink}, or follow our {twitterLink} to join the conversation!" ], "zombieDesc": "A Level {levelNum} CryptoZombie", - "shareLinkText": "I just completed #CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Try it out yourself:" + "shareLinkText": "I just completed #CryptoZombies Lesson 5 and got a level 10 CryptoZombie! Check out my zombie army:" } }, "zombieBattle": { @@ -233,6 +249,24 @@ "Want to build your own CryptoZombie army and join the ranks?", "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" ] + }, + "lesson5": { + "zombieDesc": "{name} CryptoZombie", + "achievementPreamble": [ + "Your friend has completed CryptoZombies Lesson 5, and received a level 10 CryptoZombie!", + "Check out your friend's zombie army to the left. Pretty formidable, huh?", + "In order to complete Lesson 5 of CryptoZombies, your friend learned about:" + ], + "achievements": [ + "Tokens, the ERC721 standard, and creating tradable tokens / assets", + "Solidity libraries and how to use them", + "The SafeMath library and preventing uint overflows and underflows", + "Commenting your Solidity code and the natspec standard" + ], + "callToAction": [ + "Want to build your own CryptoZombie army and join the ranks?", + "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" + ] } } } diff --git a/en/index.ts b/en/index.ts index 239dbf4d57..1dc2dfcd45 100644 --- a/en/index.ts +++ b/en/index.ts @@ -70,6 +70,24 @@ import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' +// lesson5 +import l5_overview from './5/00-overview.md' +import l5_ch1 from './5/01-erc721-1.md' +import l5_ch2 from './5/02-erc721-2.md' +import l5_ch3 from './5/03-erc721-3.md' +import l5_ch4 from './5/04-erc721-4.md' +import l5_ch5 from './5/05-erc721-5.md' +import l5_ch6 from './5/06-erc721-6.md' +import l5_ch7 from './5/07-erc721-7.md' +import l5_ch8 from './5/08-erc721-8.md' +import l5_ch9 from './5/09-safemath-1.md' +import l5_ch10 from './5/10-safemath-2.md' +import l5_ch11 from './5/11-safemath-3.md' +import l5_ch12 from './5/12-safemath-4.md' +import l5_ch13 from './5/13-comments.md' +import l5_ch14 from './5/14-wrappingitup.md' +import l5_complete from './5/15-lessoncomplete.md' + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -142,5 +160,23 @@ export default { l4_ch9, l4_ch10, l4_complete - ] -} \ No newline at end of file + ], + 5: [ + l5_overview, + l5_ch1, + l5_ch2, + l5_ch3, + l5_ch4, + l5_ch5, + l5_ch6, + l5_ch7, + l5_ch8, + l5_ch9, + l5_ch10, + l5_ch11, + l5_ch12, + l5_ch13, + l5_ch14, + l5_complete + ], +} diff --git a/en/share-template-msgs.json b/en/share-template-msgs.json index aa3b9f382d..87b5528fa6 100644 --- a/en/share-template-msgs.json +++ b/en/share-template-msgs.json @@ -14,5 +14,9 @@ "4": { "pageTitle": "My CryptoZombie defeated the evil IOTA zombie!", "ogDesc": "I just completed CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Come relive the battle..." + }, + "5": { + "pageTitle": "My CryptoZombie Army Has Grown!", + "ogDesc": "I just completed CryptoZombies Lesson 5, and got a level 10 H4XF13LD MORRIS 💯💯😎💯💯 zombie OMG!" } } diff --git a/es/3/07-zombiecooldowns2.md b/es/3/07-zombiecooldowns2.md index 77e48deee0..ca9d897386 100644 --- a/es/3/07-zombiecooldowns2.md +++ b/es/3/07-zombiecooldowns2.md @@ -220,4 +220,4 @@ Si nos damos cuento, esta función solo necesita ser llamada por `feedOnKitty()` 2. Vamos a hacer que `feedAndMultiply` utilice nuestro `cooldownTime` en la cuenta. Primero, despues de buscar `myZombie`, vamos a añadirle una sentencia `require` que compruebe `_isReady()` y le pase `myZombie`. De esta manera el usuario solo podrá ejecutar esta función si el enfriamiento del zombi ha terminado. -3. At the end of the function let's call `_triggerCooldown(myZombie)` so that feeding triggers the zombie's cooldown time. +3. Al final de la función, vamos a llamar a `_triggerCooldown(myZombie)` para que al alimentarse ejecute el trigger del enfriamiento del zombi. diff --git a/es/index.json b/es/index.json index 410bcad502..b78e302239 100644 --- a/es/index.json +++ b/es/index.json @@ -109,9 +109,9 @@ "Comparte esta URL para que tus amigos puedan ver tu ejército:" ], "footer": [ - "La Lección 5 vendrá en 1-2 semanas", - "Recibirás un email tan pronto como esté disponible.", - "¡De mientras, uneté en {telegramLink}, o síguenos en {twitterLink} para unirte a la conversación!" + "¿Estás drisfrutando CryptoZombies? ¡Háznoslo saber!", + "Únete a la conversación en {telegramLink}, o síguenos en {twitterLink}", + "Cuando estés preparado, clica en el botón de abajo para acceder a la Lección 5:" ], "zombieDesc": "Un CryptoZombie de Nivel {levelNum}", "shareLinkText": "¡He completado la Lección 4 de #CryptoZombies, y he derrotado al malvado zombi IOTA! Pruébalo tu mismo:" diff --git a/es/index.ts b/es/index.ts index 980127a8d7..239dbf4d57 100644 --- a/es/index.ts +++ b/es/index.ts @@ -53,7 +53,7 @@ import l3_ch11 from './3/11-savinggasstorage.md' import l3_ch12 from './3/12-forloops.md' import l3_ch13 from './3/13-wrappingitup.md' import l3_complete from './3/14-lessoncomplete.md' -/* + // lesson4 import l4_overview from './4/00-overview.md' import l4_payable from './4/payable.md' @@ -69,7 +69,7 @@ import l4_ch8 from './4/battle-08.md' import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' -*/ + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -126,7 +126,7 @@ export default { l3_ch12, l3_ch13, l3_complete - ]/*, + ], 4: [ l4_overview, l4_payable, @@ -142,5 +142,5 @@ export default { l4_ch9, l4_ch10, l4_complete - ]*/ + ] } \ No newline at end of file diff --git a/fr/index.json b/fr/index.json index 9c8e632449..595c473c81 100644 --- a/fr/index.json +++ b/fr/index.json @@ -7,7 +7,7 @@ "syncing": "Synchronisation...", "telegram": { "name": "Telegram", - "openChat": "Open Chat", + "openChat": "Ouvrir le chat", "action": "Rejoins-nous sur Telegram", "link": "https://t.me/loomnetwork" }, @@ -73,7 +73,7 @@ "{zombieName} est passé au Niveau 2 !", "Vous avez ajouté NoName, un Zombie-Chat à votre armée ! (Ne vous inquiétez pas, vous apprendrez comment changer son nom à la Leçon 3)", "Montrer votre tueur-de-CryptoKitty à vos amis !", - "Partagez cette URL afin que vos amis puissent exterminer des CryptoKitties avec votre zombie :" + "Partagez cette URL pour que vos amis puissent exterminer des CryptoKitties avec votre zombie :" ], "footer": [ "Vous aimez CryptoZombies? Laissez-nous savoir !", @@ -89,7 +89,7 @@ "header": [ "Félicitations ! Vous avez complété la {lesson3} de CryptoZombies !", "Montrez votre armée de zombie à vos amis !", - "Partagez cette URL afin que vos amis puissent voir votre armée :" + "Partagez cette URL pour que vos amis puissent voir votre armée :" ], "footer": [ "Vous aimez CryptoZombies? Laissez-nous savoir !", @@ -98,6 +98,23 @@ ], "zombieDesc": "Un CryptoZombie de Niveau 3", "shareLinkText": "Je viens juste de finir la Leçon 3 de #CryptoZombies ! Viens voir mon armée de zombie :" + }, + "lesson4": { + "achievementsUnlocked": "Accomplissements débloqués :", + "zombieUpgraded": "{zombieName} est passé au niveau {levelNum} !", + "newZombieAdded": "Vous avez ajouté un nouveau zombie de niveau 1 à votre armée !", + "header": [ + "Félicitations ! Vous avez complété la {lesson4} de CryptoZombies !", + "Montrez votre armée de zombie à vos amis !", + "Partagez cette URL pour que vos amis puissent voir votre armée :" + ], + "footer": [ + "Vous aimez CryptoZombies? Laissez-nous savoir !", + "Rejoignez la conversation sur {telegramLink}, ou suivez-nous sur {twitterLink}", + "Quand vous êtes prêt, cliquez sur le bouton pour accéder à la Leçon 5 :" + ], + "zombieDesc": "Un CryptoZombie de niveau {levelNum}", + "shareLinkText": "Je viens juste de finir la Leçon 4 de #CryptoZombies, et j'ai battu le zombie maléfique IOTA ! A ton tour d'essayer :" } }, "zombieBattle": { @@ -130,21 +147,27 @@ "nameInputPlaceholder": "Choisissez un nom", "defaultZombieDesc": "Un CryptoZombie de Niveau 1" }, + "battleArena": { + "IOTAslain": "IOTA a été tué au combat !", + "winCount": "Le nombre de victoire de votre zombie :", + "iotaLose": "Le nombre de défaite de IOTA", + "newZombie": "Nouveau zombie créé" + }, "sharePage": { "pageTitle": "Venez voir mon CryptoZombie {zombieName} !", "presents": "Présente", - "zombieDesc": "{name}, un CryptoZombie de Niveau {levelNum}", + "zombieDesc": "{name}, un CryptoZombie de niveau {levelNum}", "lesson1": { - "zombieDesc": "un CryptoZombie de Niveau 1", + "zombieDesc": "un CryptoZombie de niveau 1", "randomZombieGenerator": "générateur de zombie aléatoire", "achievementPreamble": [ - "Votre ami a appris les bases pour développer un jeu vidéo Ethereum, et a créé {zombieName}, un CryptoZombie de Niveau 1 comme preuve !", + "Votre ami a appris les bases pour développer un jeu vidéo Ethereum, et pour le prouver, a créé {zombieName}, un CryptoZombie de niveau 1 !", "Pour compléter la Leçon 1 de CryptoZombies, votre ami à construit un {randomZombieGenerator} et a appris :" ], "achievements": [ "Comment écrire des smart contracts Ethereum", "Programmer en Solidity, le langage de programmation des smart contracts Ethereum", - "Construit des jeux sur Ethereum" + "Construire des jeux sur Ethereum" ], "tryItOut": "Essayez le générateur de zombie aléatoire.", "below": "dessous", @@ -153,7 +176,7 @@ "nameInputPlaceholder": "Entrez un nom pour générer un nouveau zombie", "callToAction": [ "Vous voulez créer votre propre CryptoZombie et rejoindre les rangs ?", - "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies! Commencez maintenant :" + "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies ! Commencez maintenant :" ] }, "lesson2": { @@ -175,13 +198,13 @@ ], "callToAction": [ "Vous voulez créer votre propre CryptoZombie et rejoindre les rangs ?", - "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies! Commencez maintenant :" + "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies ! Commencez maintenant :" ] }, "lesson3": { "achievementPreamble": [ "Votre ami a complété la Leçon 3 de CryptoZombies, et a améliorer son armée de zombie !", - "Afin de terminer la Leçon 2 de CryptoZombies, votre ami a appris :" + "Afin de terminer la Leçon 3 de CryptoZombies, votre ami a appris :" ], "achievements": [ "Comment faire des smart contracts modifiable", @@ -190,8 +213,25 @@ "Modificateurs de fonction et contrôles de sécurité" ], "callToAction": [ - "Vous voulez créer votre propre CryptoZombie et rejoindre les rangs ?", - "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies! Commencez maintenant :" + "Vous voulez créer votre propre armée de CryptoZombie et rejoindre les rangs ?", + "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies ! Commencez maintenant :" + ] + }, + "lesson4": { + "achievementPreamble": [ + "Votre ami a complété la Leçon 4 de CryptoZombies, et a battu le zombie maléfique IOTA !", + "A votre tour d'essayer en utilisant la section de gauche. Servez-vous de l'armée de votre ami pour engager le combat !", + "Afin de terminer la Leçon 4 de CryptoZombies, votre ami a appris :" + ], + "achievements": [ + "Les fonctions Payables, et comment gagner de l'argent avec un jeu basé sur Ethereum", + "Retirer des ETH depuis des smart contracts", + "Génération de nombre aléatoire et la sécurité sur Ethereum", + "Et plus encore !" + ], + "callToAction": [ + "Vous voulez créer votre propre armée de CryptoZombie et rejoindre les rangs ?", + "Apprenez à créer vos propres jeux sur Ethereum GRATUITEMENT avec CryptoZombies ! Commencez maintenant :" ] } } diff --git a/jp/1/keccak256.md b/jp/1/keccak256.md index ad97743a21..ad94ddcb23 100644 --- a/jp/1/keccak256.md +++ b/jp/1/keccak256.md @@ -96,4 +96,4 @@ uint8 c = a * uint8(b); 1. コードの最初の行は `_str`の`keccak256`ハッシュを取得し、擬似乱数の16進数を生成し、それを`uint`に型キャストして、 `rand`という`uint`に格納せよ。 -2. DNAは16桁になるようにしたい(`dnaModulus`を覚えているか?)。そこで次の行では`dnaModulus`という余剰(`%`)の値を `return`するようにせよ。 +2. DNAは16桁になるようにしたい(`dnaModulus`を覚えているか?)。そこで次の行では上で求めた値の`dnaModulus`による剰余(`%`)を `return`するようにせよ。 diff --git a/jp/3/08-functionmodifiers.md b/jp/3/08-functionmodifiers.md index 1d12d8a9f1..28f8f4508d 100644 --- a/jp/3/08-functionmodifiers.md +++ b/jp/3/08-functionmodifiers.md @@ -208,7 +208,7 @@ function driveCar(uint _userId) public olderThan(16, _userId) { 1. `ZombieHelper`で、 `aboveLevel`という`modifier` を作成せよ。また、`_level` (`uint`) と `_zombieId` (`uint`)の2つの引数を取得するように設定せよ。 -2. その中で、 `zombies[_zombieId].level`が、`_level`よりも大きいことを確認できるようにせよ。 +2. その中で、 `zombies[_zombieId].level`が、`_level`以上であることを確認できるようにせよ。 3. 修飾子の 最後に`_;`をつけて残りの関数を呼び出すことを忘れるなよ。 diff --git a/jp/5/00-overview.md b/jp/5/00-overview.md new file mode 100644 index 0000000000..a3a0e048f8 --- /dev/null +++ b/jp/5/00-overview.md @@ -0,0 +1,13 @@ +--- +title: ERC721とクリプト収集物 +header: "レッスン5: ERC721とクリプト収集物" +roadmap: roadmap5.jpg +--- + +このチュートリアルもかなり熱くなってきたな・・・すごい! + +今回のレッスンは少し高度になるぞ。 + + これから **トークン**、 **ERC721** 規格、そして **収集可能なクリプト資産** について説明していく。 + +つまり、このレッスンでは **お主のゾンビを友達とトレードできるようにする** からな! diff --git a/jp/5/01-erc721-1.md b/jp/5/01-erc721-1.md new file mode 100644 index 0000000000..a25ebe6f3f --- /dev/null +++ b/jp/5/01-erc721-1.md @@ -0,0 +1,289 @@ +--- +title: イーサリアム上トークン +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + // ここから始めるのだ + "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 { + + } +--- + +**_トークン_** について説明していこう。 + +お主がイーサリアムの界隈に足を踏み入れたことがあるなら、トークン、特に **_ERC20トークン_** が話題になってるのを恐らく聞いたことがあるだろう。 + +イーサリアム上の**_トークン_** は、基本的にいくつかの共通ルールに従ったスマート・コントラクトだ。具体的に言うと、`transfer(address _to, uint256 _value)` や `balanceOf(address _owner)` といった関数のスタンダードセットを実装しているものだ。 + +通常スマートコントラクトは、内部に各アドレスにどれだけの残高があるかを記録する `mapping(address => uint256) balances`を持っている。 + +つまり基本的には、トークンとは、誰がトークンをどれくらいを所有しているのかを記録するコントラクトと、ユーザーが自分のトークンを他のアドレスに送ることができるようにする機能のことなのだ。 + +### なぜトークンが重要なのか? + +すべてのERC20トークンは同じ名前の同じ関数セットを共用しているから、同じ方法で相互に作用することが可能となっている。 + +つまり、とあるERC20トークンとやりとりするアプリケーションを作った場合、他のERC20トークンとやりとりすることも可能なのだ。こうすることでカスタムコーディングをせずとも、将来もっと多くのトークンをアプリに追加することができるぞ。ただ新しいトークンのコントラクト・アドレスを入力するだけで、もうアプリは別のトークンを使えるようになる。 + +この一例として挙げられるのは取引所であろう。取引所が新たなERC20トークンを追加するのに必要なのは、トークンとやり取りするためのスマート・コントラクトをただ追加することだ。ユーザーはトークンを取引所のウォレットアドレスに送るようコントラクトに指示でき、取引所はユーザーが引き出しを要求した場合にトークンを彼らに送り返すようコントラクトに指示することができる。 + +取引所はこのトランスファー・ロジックを一度実装すれば、新たにERC20トークンを追加したい場合に、新しいコントラクト・アドレスをデータベースに追加するだけで良いということだ。 + +### 別のトークン規格 + +ERC20トークンは、通貨のような働きをする非常に素晴らしいトークンだ。 ただし、この企画で我らがゾンビ・ゲーム内のゾンビを表すのは非常に不便である。 + +理由としてまず、ゾンビは通貨のように分けることができないことが挙げられる。例えば0.237ETHをお主に送ることはできても、0.237のゾンビを送るだなんて訳がわからないからな。 + +次に、全てのゾンビが同じように出来ていないということもその理由だ。例えば、お主のレベル2のゾンビ"**Steve**"が、わしのレベル732のゾンビ"**H4XF13LD MORRIS 💯💯😎💯💯**"と同じものだなんてことは全くない!(*Steve* よ、わしのゾンビと近付くすらないぞ) + +なのでそれとは別に、CryptoZombiesのようなクリプト収集物により適したトークン規格がある。 **_ERC721トークン_** と呼ばれるものだ。 + +**_ERC721トークン_** は、それぞれがユニークであると仮定され、分割出来ないので **相互に交換可能でない** 。一つの単位ごとの取引のみ可能で、それぞれが特有のIdを持っている。だからゾンビをトレード可能にするのに完璧に適したものなのだ。 + +> 注: ERC721のような標準を使用するメリットとして、オークション及びプレイヤーがゾンビをトレード/販売するやり方を決定するエスクロー・ロジックをコントラクト内で実装する必要がなくなるという点があります。仕様に準拠すれば、他の誰かが収集可能なERC721クリプト資産の交換プラットフォームを作ることができ、私たちのERC721ゾンビはそのプラットフォームで使用できるようになります。そのため、独自の取引ロジックを展開する代わりにトークン規格を使用するのは明らかにメリットがあります。 + +## さあテストだ + +次のチャプターでERC721の実装をやっていくが、まずはこのレッスン向けにファイル構成をセットアップしていくぞ。 + +`ZombieOwnership`というコントラクトに全てのERC721メソッドを格納していこう。 + +1. `pragma`のバージョンをファイルの冒頭で宣言せよ。(シンタックスは前のレッスンでやったファイルをチェックすること) + +2. このファイルに`zombieattack.sol`を`import`すること。 + +3. `ZombieOwnership`という新たなコントラクトを宣言し、`ZombieAttack`を継承させよ。まだコントラクトの中身は空のままでよい。 diff --git a/jp/5/02-erc721-2.md b/jp/5/02-erc721-2.md new file mode 100644 index 0000000000..276f8093a8 --- /dev/null +++ b/jp/5/02-erc721-2.md @@ -0,0 +1,310 @@ +--- +title: ERC721規格と多重継承 +actions: ['答え合わせ', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + // ここでファイルをインポートするのだ + + // ここでERC721の継承を宣言するのだ + contract ZombieOwnership is ZombieAttack { + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + } +--- + +ERC721規格について見ていこう: + +``` +contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; +} +``` + +これは実装が必要なメソッドのリストだ。これからのチャプターでそれぞれやっていくからな。 + +たくさんあるように見えるかもしれないが、怖気付くんじゃないぞ!お主がちゃんとやれるようにわしはここにいるんだからな。 + +> 注: ERC721規格は現在 *ドラフト* であり、まだ正式に合意された実装ではありません。このチュートリアルではOpenZeppelinのライブラリの現バージョンを使用していますが、将来正式な規格リリース前に変更される可能性があります。この実装については可能なものの **ひとつ** であり、ERC721規格の正式なものとは考えないでください。 + +### トークン・コントラクトの実装 + +トークン・コントラクト実装の際、初めにすべきはインターフェースをSolidityファイルにコピー/インポートすることだ( `import ./erc721.sol`)。すると我々のコントラクトはそれを継承し、関数定義で各メソッドをオーバーライドする。 + +だがちょっと待てよ、`ZombieOwnership`はもう`ZombieAttack`を継承している。どうやって`ERC721`も継承できるのか? + +Solidityでは幸い、コントラクトは次のように複数コントラクトを継承可能だ: + +``` +contract SatoshiNakamoto is NickSzabo, HalFinney { + // Omg, the secrets of the universe revealed! +} +``` + +見ての通り複数の継承をするときは、継承している複数のコントラクトをコンマ( `,`)で区切るだけだ。この場合、`SatoshiNakamoto`は`NickSzabo`と` HalFinney`を継承している。 + +早速試してみよう。 + +## さあテストだ + +お主のために `erc721.sol`を上に作っておいたぞ。 + +1. `erc721.sol` を `zombieownership.sol`にインポートせよ。 + +2. `ZombieOwnership`が`ZombieAttack`と`ERC721`を継承することを宣言せよ。 diff --git a/jp/5/03-erc721-3.md b/jp/5/03-erc721-3.md new file mode 100644 index 0000000000..e7ffb19874 --- /dev/null +++ b/jp/5/03-erc721-3.md @@ -0,0 +1,339 @@ +--- +title: balanceOf と ownerOf +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + // 1.ここで`_owner`がもつゾンビの数を返すのだ + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + // 2. `_tokenId`の所有者をここで返すのだ + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +よくできた、ではERC721の実装に入っていくぞ! + +中身は空のままだが、我々はこのレッスンで実装する全ての関数を写し書いてきた。 + +このチャプターではまず`balanceOf`と`ownerOf`の2つのメソッドを実装していこう。 + +### `balanceOf` + +``` + function balanceOf(address _owner) public view returns (uint256 _balance); +``` + +この関数は単に`address`を受け取り、その`address`のトークン保有量を返す。 + +我らが場合、『トークン』はゾンビだ。このDAppsで所有者のゾンビ保有数をどこに保存しているか覚えているか? + +### `ownerOf` + +``` + function ownerOf(uint256 _tokenId) public view returns (address _owner); +``` + +この関数はトークンID (この場合ゾンビのIDだ)を受け取り、その所有者の `address`を返す。 + + +このDAppにはすでにこの情報を格納している `mapping`があるから、実装はとても簡単だ。この関数は`return`ステートメントの1行で実装できる。 + +> 注: Remember, `uint256` は `uint`に相当すると覚えておいてください。これまでコード中`uint`を用いてきましたが規格からコピー&ペーストしているので、ここでは`uint256`を使用しています。 + +## さあテストだ + +これら2つの関数の実装方法を理解しよう。 + +各関数はシンプルに`return`ステートメントのコード1行だけにするのだ。このデータをどこで保存しているか、前のレッスンを見てみるといいぞ。もしわからなければ、『答えを見る』ボタンでヒントを得ても良いからな。 + +1. `balanceOf`を実装し、`_owner`のゾンビ保有数を返すようにせよ。 + +2. `ownerOf`を実装し、`_tokenId`をIDに持つゾンビを保有する者のアドレスを返せ。. diff --git a/jp/5/04-erc721-4.md b/jp/5/04-erc721-4.md new file mode 100644 index 0000000000..aae62b0b60 --- /dev/null +++ b/jp/5/04-erc721-4.md @@ -0,0 +1,355 @@ +--- +title: リファクタリング +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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; + + // 1. 修飾詞名を`onlyOwnerOf`に変更せよ + 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); + } + + // 2. ここも同じように修飾詞名を変更せよ + 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"); + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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; + } + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } +--- + +おっと! コンパイルできなくなるエラーをコードに導入してしまっている!お主は気づいただろうか? + +前チャプターで、`ownerOf`という関数を定義した。だがレッスン4を振り返ると、同じ`ownerOf`という名前の`modifier`も`zombiefeeding.sol`の中に作ったのだ。 + +このコードをコンパイルしようとしても、コンパイラは同じ名前の修飾詞と関数を持つことはできないとエラーを出す。 + +では`ZombieOwnership`中の関数名をただ変更すれば良いのだろうか? + +答えはノーだ!!! 覚えておいてほしいが、ERC721トークン規格を使用するということは、定義されたある名前の関数を我々のコントラクトが持つことを、他のコントラクトから予期されるということになる。これが規格を便利にしている理由だ。我々のコントラクトがERC721に準拠していると他のコントラクトが知っていれば、内部の実装を何も知る必要なく簡単にやり取りすることができるのだ。 + +つまり`modifier`名を別のものに変更して、レッスン4のコードをリファクタリングしなくてはならない。 + +## さあテストだ + +`zombiefeeding.sol`へ戻ろう。`modifier`名を`ownerOf`から`onlyOwnerOf`へ変更していくぞ。 + +1. 修飾詞名の定義を`onlyOwnerOf`に変更せよ。 + +2. 下へスクロールしていき、この修飾詞を使っている`feedAndMultiply`関数を見つけたら、ここでも同じように名前を変更せよ。 + +> 注: この修飾詞は`zombiehelper.sol`と`zombieattack.sol`の中でも使っていますが、レッスンの多くをリファクタリングに割くのを避けるため、これらファイル内での修飾詞名は変更しておきます。 diff --git a/jp/5/05-erc721-5.md b/jp/5/05-erc721-5.md new file mode 100644 index 0000000000..d4e018672a --- /dev/null +++ b/jp/5/05-erc721-5.md @@ -0,0 +1,347 @@ +--- +title: "ERC721: トランスファーのロジック" +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + // _transfer()をここで定義するのだ + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +よし、コンフリクトは解消した! + +では今度は、ある人から別の人への所有権の移転を見ていって、ERC721の実装を続けていこう。 + +注意してほしいが、ERC721規格は2つの異なるトークン移転法を持つ: + +``` +function transfer(address _to, uint256 _tokenId) public; +function approve(address _to, uint256 _tokenId) public; +function takeOwnership(uint256 _tokenId) public; +``` + +1. 一番目の方法はトークン所有者が送り先の`address`、そして送りたいトークンの`_tokenId`を送って`transfer`関数を呼び出すものだ。 + +2. 二番目の方法は、トークン所有者がまず`approve`関数を呼び出し、一番目と同じ情報を関数に送る。すると、コントラクトが誰がトークン受け取りを許可されたのかを、通常は`mapping (uint256 => address)`にて記録する。さらに誰かが`takeOwnership`を呼び出すと、コントラクトはその`msg.sender`がトークンを受け取ることを所有者から承認されているかをチェックし、承認済みの場合は彼にトークンを移転する。 + +`transfer`と` takeOwnership`のどちらも同じ転送ロジックを含むことに気付くことになるだろうが、順序が逆になる。(ひとつはトークンの送り手が関数を呼び出すケース、もうひとつはトークンの受け手が関数を呼び出すケース)。 + +したがって、このロジックを独自のプライベート関数 `_transfer`に抽象化し、これを両方の関数で呼び出すようにしよう。そうすれば、同じコードを2回繰り返さなくて済むからな! + +## さあテストだ + +`_transfer`のロジックを定義しよう。 + +1. `_transfer`という名前の関数を定義せよ。これは`address _from`、`address _to`そして`uint256 _tokenId`の3つの引数を受け取り、`private`関数とすること。 + +2. オーナーシップが変わるとき、2つのマッピングが更新される: `ownerZombieCount` (所有者のゾンビ保有数を記録する)と、`zombieToOwner` (誰がどのゾンビを所有しているか記録する)。 + + ますこの関数で行うべきは、ゾンビを **受け取る** 者(`address _to`)の`ownerZombieCount`を加算することだ。加算には`++`を使うように。 + +3. 次にゾンビを **送る** 者(`address _from`)の`ownerZombieCount`を **減らす** ことが必要だ。`--`を使うこと。 + +4. 最後に`zombieToOwner`マッピングを変更したい。この`_tokenId`が今度は`_to`を指し示すようにせよ。 + +5. おっと、まだ最後ではなかった!もう一つやらねばならぬことがある。 + + ERC721規格は、`Transfer`イベントを含む。この関数の最終行で、適切な情報を渡して`Transfer`イベントを起こさなくてはならない。`erc721.sol`を見て、呼び出しに際してどんな引数を求められているのかをチェックして実装せよ。 diff --git a/jp/5/06-erc721-6.md b/jp/5/06-erc721-6.md new file mode 100644 index 0000000000..bd11478eb4 --- /dev/null +++ b/jp/5/06-erc721-6.md @@ -0,0 +1,327 @@ +--- +title: "ERC721: トランスファーの続き" +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + // 1. ここに修飾詞を加えるのだ + function transfer(address _to, uint256 _tokenId) public { + // 2. ここで関数を定義せよ + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +難しいチャプターだったがよくできたな!今度はパブリックの`transfer`関数を実装していくが、これは簡単だ。さっき作った`_transfer`関数がほとんどのことはやってくれているからな! + +## さあテストだ + +1. トークン/ゾンビの所有者だけがそれを移転できるよう確認したい。所有者だけに関数へアクセスできるよう制限するやり方を覚えているだろうか? + + そうだ、お主が覚えているように`onlyOwnerOf`修飾詞がそれを行う。この関数に追加しよう。 + +2. 今度は関数の中身についてだが、1行でなくてはならない...ただ`_transfer`を呼び出すだけだ。 + + `address _from`の引数に`msg.sender`が渡されているようにすること。 diff --git a/jp/5/07-erc721-7.md b/jp/5/07-erc721-7.md new file mode 100644 index 0000000000..78a15b74f6 --- /dev/null +++ b/jp/5/07-erc721-7.md @@ -0,0 +1,342 @@ +--- +title: "ERC721: Approve" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + // 1. マッピングをここで定義せよ + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + // 2. ここに関数修飾詞を追加すること + function approve(address _to, uint256 _tokenId) public { + // 3. ここに関数を定義すること + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +では`approve`を実装していこう。 + +`approve` / `takeOwnership`の2つの関数で、トランスファーは2段階で発生することを思い出して欲しい: + +1. トークン所有者が`approve`関数を呼び出し、それに新たな所有者の`address`と彼に送る`_tokenId`の情報を与える。 + +2. 新たな所有者が`takeOwnership`関数を`_tokenId`で呼び出すと、コントラクトは彼が承認済みの新たな所有者であるかを確認してから、彼にトークンを移転する。 + +これは2度の関数呼び出しで発生するから、その2度の間で誰がどのトークンについて受け取りを承認されているか記録するデータ構成が必要だ。 + +## さあテストだ + +1. まず初めに、`zombieApprovals`というマッピングを定義せよ。`uint`から`address`を指し示すマップにすること。 + + こうすることで、誰かが`_tokenId`で`takeOwnership`関数を呼び出すときに、マッピングで誰がトークン受け取りを承認されているかをさっとチェック可能だ。 + +2. `approve`関数では、トークン所有者のみが、受け手にトークンを受け取る許可を与えられるようにしておきたい。 なので、`approve`関数に`onlyOwnerOf`修飾詞を加えよう。 + +3. 関数の中身に、`_tokenId`がキーの`zombieApprovals`が`_to`アドレスと同等となるよう設定せよ。 + +4. 最後に、ERC721規格には`Approval`イベントがあるから、関数の最後でこれを発生させ流ようにせよ。`erc721.sol`をチェックして`_owner`の引数に`msg.sender`を使うよう確かめること。 diff --git a/jp/5/08-erc721-8.md b/jp/5/08-erc721-8.md new file mode 100644 index 0000000000..d257252dd3 --- /dev/null +++ b/jp/5/08-erc721-8.md @@ -0,0 +1,340 @@ +--- +title: "ERC721: takeOwnership" +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + // ここから始めるのだ + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +よし、今度の関数でERC721実装は終わりだ。(Lesson5はこの後もまだまあだあるから、心配しなくていいぞ!😉) + +最後の関数`takeOwnership`は、`msg.sender`がそのトークン/ゾンビを受け取ることを承認されているか確認し、承認されていれば`_transfer`関数を呼び出すだけで良い。 + +## さあテストだ + +1. 初めに、`require`ステートメントを使って、`zombieApprovals`でキーが`_tokenId`の場合に`msg.sender`と同等となるようにせよ。 + + こうしてもし`msg.sender`がトークンを受け取ることが承認されていなかった場合、エラーが投げられる。 + +2. `_transfer`関数を呼び出すには、トークン所有者のアドレスを知る必要がある。(`_from`の引数として必要だ)。幸い、このアドレスは`ownerOf`関数で参照可能だ。 + + では`owner`という`address`変数を宣言し、それが`ownerOf(_tokenId)`と同等となるようにせよ。 + +3. 最後に、`_transfer`関数を呼び出して、必要な情報全てを渡せ。(ここでは、`_to`の引数に`msg.sender`を使う。なぜなら関数を呼び出しているのは、トークンが送られるべき者だからだ)。 + +> 注: 上の2と3のステップは1行のコードにて行うことができるが、少しばかり読みやすくするために分割している。 diff --git a/jp/5/09-safemath-1.md b/jp/5/09-safemath-1.md new file mode 100644 index 0000000000..53102d5150 --- /dev/null +++ b/jp/5/09-safemath-1.md @@ -0,0 +1,445 @@ +--- +title: オーバーフロー対策 +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + // 1. ここでインポートするのだ + + contract ZombieFactory is Ownable { + + // 2. safemathの使用をここで宣言せよ + + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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); + } + + } +--- + +ERC721の実装が完了したぞ、おめでとう! + +そんなに大変じゃなかっただろう?イーサリアム関連のことは、人の話を聞いているだけだと非常に複雑に思えるだろう。だから理解するには実際に自分で実装してみるのが一番ベストなのだ! + +だがこれは最小限の実装であると心しておくように。例えばユーザーがアドレス`0`にうっかりゾンビを送らないよう確認するよう、さらなるチェックを実装に加える方が良いかもしれない(これはトークン『焼却』と呼ばれ、誰もプライベート・キーを持たないアドレスにトークンは送られ、基本的にそれはリカバーできない)。 +それか基本的なオークションロジックをDApp自体に取り入れるのも良いだろう(これを実装する方法をお主は考えられるだろうか?)。 + +しかしこのレッスンを扱いやすくしておくために、最も基本的な実装を行った。もしお主がもっとディープな実装例を見たければ、このチュートリアルの後にOpenZeppelinのERC721コントラクトをチェックすると良いぞ。 + +### コントラクトのセキュリティ強化: オーバーフローとアンダーフロー + +スマート・コントラクトを書く際、気をつけなくてはならない重要なセキュリティの特徴がある。オーバーフローとアンダーフローの回避だ。 + +**_オーバーフロー_** とは? + +例として、`uint8`は8ビットのみを持つが、つまりここに格納できる最大数値はバイナリの`11111111`ということだ(またはデシマルだと 2^8 - 1 = 255となる)。 + +次のコードを見て欲しい。最後は`number`はどうなっているだろうか? + +``` +uint8 number = 255; +number++; +``` + +この場合、オーバーフローの原因となってしまうとなってしまう。つまり`number`を増やしても、`0`になるという通常の感覚に反することが起こる(もしバイナリの`11111111`に1を足すと、`00000000`に戻ってしまう。時計が`23:59`から`00:00`になってしまうような感じだ)。 + +アンダーフローも同様に、`0`と等しい`uint8`から`1`を引くと、`255`となる(`uint`は符号なしであるので、マイナスとなれないからだ)。 + +我々はここで`uint8`は使っていないし、`uint256`を毎回`1`ずつ増やしてオーバーフローとなることはなさそうであるが(2^256 はかなり大きな数になる)、このDAppが将来予期せぬ動きを起こさないよう、コントラクト内で対策を講じておくのが良いだろう。 + +### SafeMathの使用 + +これらの問題をデフォルトで回避するために、OpenZeppelinはSafeMathという **_ライブラリ_** を作成している。 + +だがそのテーマに入る前に・・・ライブラリとは何なのか? + +**_library_** とは、Solidityにおける特別なタイプのコントラクトだ。便利なことの一つとして、ネイティブデータ型への関数アタッチができることがある。 + +例えばSafeMathライブラリで、`using SafeMath for uint256`のシンタックスを使うとしよう。SafeMathライブラリは`add`、`sub`、`mul`さらに`div`の4つの関数を持つ。そして以下のように、`uint256`からこれらの関数にアクセス可能だ: + +``` +using SafeMath for uint256; + +uint256 a = 5; +uint256 b = a.add(3); // 5 + 3 = 8 +uint256 c = a.mul(2); // 5 * 2 = 10 +``` + +次のチャプターでこれらの関数が何を行うか見ていくが、今はまずコントラクトにSafeMathライブラリを取り入れよう。 + +## さあテストだ + +お主のために、OpenZeppelinの`SafeMath`ライブラリを`safemath.sol`に入れておいたぞ。もし見たければそのコードを見ても良いが、次のチャプターで深くやって行くからな。 + +まずSafeMathを使うことを我らがコントラクトに伝えよう。非常に基礎的なコントラクトZombieFactoryの中でこれを行う。こうすることで、それを継承するどのサブコントラクト内でもライブラリを使えるようになるからな。 + +1. `safemath.sol`を`zombiefactory.sol`へインポートせよ。 + +2. `using SafeMath for uint256;`という宣言を加えよ。 diff --git a/jp/5/10-safemath-2.md b/jp/5/10-safemath-2.md new file mode 100644 index 0000000000..9f5f59daca --- /dev/null +++ b/jp/5/10-safemath-2.md @@ -0,0 +1,469 @@ +--- +title: SafeMathパート2 +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + // 1. SafeMathの`add`で置きかえるのだ + ownerZombieCount[_to]++; + // 2. SafeMathの`sub`で置き換えるのだ + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[_from] = ownerZombieCount[_from].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +SafeMathライブラリの中のコードを見てみよう: + +``` +library SafeMath { + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} +``` + +初めに、`library`というキーワードが出てきたが、ライブラリは`contract`に似ているが少し違いがある。我々の目的だと、ライブラリは`using`というキーワードを使えるようにするが、これで自動的にライブラリの全メソッドを別のデータ型に追加することができるのだ。 + +``` +using SafeMath for uint; +// now we can use these methods on any uint +uint test = 2; +test = test.mul(3); // test now equals 6 +test = test.add(5); // test now equals 11 +``` + +`mul`と`add`関数はそれぞれ2つの引数を必要とするが、`using SafeMath for uint`を宣言する際、関数上で呼び出す`uint`(`test`)は自動的に一つ目の引数として渡されることに気をつけたい。 + +今度はSafeMathが何を行なっているか`add`の中身のコードを見てみよう: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} +``` + +基本的に`add`は2つの`uint`を`+`のようにただ足しているが、その合計が`a`より大きいことを確認する`assert`ステートメントを含んでいる。こうして我々をオーバーフローから守ってくれるのだ。 + +`assert`は`require`と同じようなものだが、偽の場合はエラーを投げる。 `assert`と`require`の違いは、`require`は関数呼び出しが失敗した場合にユーザーにガスの残りを返却するが、このとき`assert`はそうしない。 なのでコード中ではほとんど`require`を使いたい。`assert`はコードにひどい間違いがおこった場合に一般的に使用される(`uint`のオーバーフローのようにである)。 + +なのでSafeMathの`add`、`sub`、`mul`そして`div`関数をシンプルに使えば、基本的な四則演算を行うし、オーバーフローやアンダーフローの際にはエラーを投げてくれるのだ。 + +### SafeMathをコード中で使ってみよう + +オーバーフローやアンダーフローを回避するため、コード中で`+`、`-`、`*`そして`/`を使った箇所を探し、`add`、`sub`、`mul`、`div`で置きかえよう。 + +例) こうする代わりに: + +``` +myUint++; +``` + +こうしていこう: + +``` +myUint = myUint.add(1); +``` + +## さあテストだ + +`ZombieOwnership`の中で演算を行なった箇所が2つある。そこをSafeMathメソッドに換えてみよう。 + +1. `++`をSafeMathメソッドで置きかえよ。 + +2. `--`をSafeMathメソッドで置きかえよ。 diff --git a/jp/5/11-safemath-3.md b/jp/5/11-safemath-3.md new file mode 100644 index 0000000000..0ebf5b314f --- /dev/null +++ b/jp/5/11-safemath-3.md @@ -0,0 +1,508 @@ +--- +title: SafeMathパート3 +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + // 1. uint32にSafeMath32を使用することを宣言せよ + // 2. uint16にSafeMath16を使用することを宣言せよ + + 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 { + // Note: We chose not to prevent the year 2038 problem... So don't need + // worry about overflows on readyTime. Our app is screwed in 2038 anyway ;) + uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; + zombieToOwner[id] = msg.sender; + // 3. ここでSafeMathの`add`を使うのだ: + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1) + ownerZombieCount[msg.sender] = ownerZombieCountmsg.sender[].sub(1) + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath32 + * @dev SafeMath library implemented for uint32 + */ + library SafeMath32 { + + function mul(uint32 a, uint32 b) internal pure returns (uint32) { + if (a == 0) { + return 0; + } + uint32 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint32 a, uint32 b) internal pure returns (uint32) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint32 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint32 a, uint32 b) internal pure returns (uint32) { + assert(b <= a); + return a - b; + } + + function add(uint32 a, uint32 b) internal pure returns (uint32) { + uint32 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath16 + * @dev SafeMath library implemented for uint16 + */ + library SafeMath16 { + + function mul(uint16 a, uint16 b) internal pure returns (uint16) { + if (a == 0) { + return 0; + } + uint16 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint16 a, uint16 b) internal pure returns (uint16) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint16 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint16 a, uint16 b) internal pure returns (uint16) { + assert(b <= a); + return a - b; + } + + function add(uint16 a, uint16 b) internal pure returns (uint16) { + uint16 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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); + } + + } +--- + +よし、これまでERC721を実装してきたが、オーバーフローやアンダーフローについても安全になった! + +前のレッスンで書いたコードに戻るが、オーバーフローやアンダーフローに対して脆弱になり得る箇所が他にもいくつかある。 + +例えば、ZombieAttackでは: + +``` +myZombie.winCount++; +myZombie.level++; +enemyZombie.lossCount++; +``` + +ここでも同じく、安全であるようオーバーフローを回避すべきだ(通常の演算の代わりに全てにSafeMathを使うのは良い考えだ。おそらく将来のSolidityのバージョンではこれらはデフォルトで実装されるかもしれないが、今のところはコード中でセキュリティ上の注意を払わなくてはならない)。 + +しかし些細な問題がある — `winCount`と`lossCount`は`uint16`であり、`level`は`uint32`なのだ。つまり、もしこれらの引数でSafeMathの`add`メソッドを使用すると、これらのタイプは`uint256`に変換されるので実際にはオーバーフローを防げてはいない: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} + +// If we call `.add` on a `uint8`, it gets converted to a `uint256`. +// So then it won't overflow at 2^8, since 256 is a valid `uint256`. +``` + +つまり`uint16`と`uint32`でオーバーフロー/アンダーフローを回避するには、さらに2つのライブラリを実装することが必要なのだ。それらライブラリを、`SafeMath16`、`SafeMath32`と呼ぼう。 + +このコードは、`uint256`の全インスタンスが`uint32`や`uint16`で置きかえられること以外SafeMathと全く同じとなる。 + +お主のために前もってそのコードは実装しておいたから、`safemath.sol`で見てみるのだ。 + +ではこれをZombieFactoryで実装しよう。 + +## さあテストだ + +お主の課題は: + +1. `uint32`に`SafeMath32`を使用することを宣言せよ。 + +2. `uint16`に`SafeMath16`を使用することを宣言せよ。 + +3. ZombieFactoryには、SafeMathメソッドを使うべき箇所がもう1行ある。どこかわかるようにコメントしておいたからな。 diff --git a/jp/5/12-safemath-4.md b/jp/5/12-safemath-4.md new file mode 100644 index 0000000000..b493e7d73b --- /dev/null +++ b/jp/5/12-safemath-4.md @@ -0,0 +1,381 @@ +--- +title: SafeMathパート4 +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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) { + // ここに1つある! + randNonce++; + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + // さらにここに3つあるぞ! + myZombie.winCount++; + myZombie.level++; + enemyZombie.lossCount++; + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + // ここにも2つある! + myZombie.lossCount++; + enemyZombie.winCount++; + _triggerCooldown(myZombie); + } + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1) + ownerZombieCount[msg.sender] = ownerZombieCountmsg.sender[].sub(1) + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 = zombies[_zombieId].level.add(1); + } + + function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 = randNonce.add(1); + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + myZombie.winCount = myZombie.winCount.add(1); + myZombie.level = myZombie.level.add(1); + enemyZombie.lossCount = enemyZombie.lossCount.add(1); + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + myZombie.lossCount = myZombie.lossCount.add(1); + enemyZombie.winCount = enemyZombie.winCount.add(1); + _triggerCooldown(myZombie); + } + } + } +--- + +よくできた、では今度はこのDAppの全タイプの`uint`にSafeMathを実装しよう! + +`ZombieAttack`にあるこれら問題箇所を修正していこう(`ZombieHelper`に一箇所`zombies[_zombieId].level++;`という修正すべき点があったのだが、お主にのためにやっておいたからな!そのための余計なチャプターはなくなったぞ!😉)。 + +## さあテストだ + +`ZombieAttack`中の全加算`++`において、SafeMathメソッドを実装するのだ。見つけやすいように、コード中にコメントを残しておいたぞ。 diff --git a/jp/5/13-comments.md b/jp/5/13-comments.md new file mode 100644 index 0000000000..1ca1a4bdf4 --- /dev/null +++ b/jp/5/13-comments.md @@ -0,0 +1,465 @@ +--- +title: コメント +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + /// このコメントをnatspecタグでの説明に置きかえよ + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1) + ownerZombieCount[msg.sender] = ownerZombieCountmsg.sender[].sub(1) + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1) + ownerZombieCount[msg.sender] = ownerZombieCountmsg.sender[].sub(1) + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +ゾンビ・ゲームのSolidityコードがいよいよ仕上がった! + +次のレッスンでは、どうやってこのコードをイーサリアム上にデプロイする方法、さらにWeb3.jsで対話する方法を見て行くぞ。 + +だがレッスン5をお終いにする前に、最後に一つやるべきことがある: **コードのコメント** について説明しよう。 + +## コメントのシンタックス + +SolidityにおけるコメントはJavaScriptのものと似ている。お主はクリプトゾンビのレッスンを通して1行のコメントを見てきたはずだ: + +``` +// これは1行コメントだ。自分への(または他社へ向けた)注意書きのようなものだ。 +``` + +ただ`//`をコメントする箇所に加えれば良い。非常に簡単だ。 + +だが、1行では不十分なこともあると思っただろう。お主は生まれながらの作家だな! + +こんな複数行のコメントもできるぞ: + +``` +contract CryptoZombies { + /* This is a multi-lined comment. I'd like to thank all of you + who have taken your time to try this programming course. + I know it's free to all of you, and it will stay free + forever, but we still put our heart and soul into making + this as good as it can be. + + Know that this is still the beginning of Blockchain development. + We've come very far but there are so many ways to make this + community better. If we made a mistake somewhere, you can + help us out and open a pull request here: + https://github.com/loomnetwork/cryptozombie-lessons + + Or if you have some ideas, comments, or just want to say + hi - drop by our Telegram community at https://t.me/loomnetwork + */ +} +``` + +特に、コントラクト中の各関数に期待する働きをコメントして説明するのは良いことだ。こうして他の開発者(またはプロジェクトを6か月休止した後の自分!)が、コード自体を読まずに、コードの働きの概要をさっと理解することができる。 + +Solidityのコミュニティでは、 **_natspec_** というフォーマットを用いることがスタンダードとなっている。こんな感じだ: + +``` +/// @title A contract for basic math operations +/// @author H4XF13LD MORRIS 💯💯😎💯💯 +/// @notice For now, this contract just adds a multiply function +contract Math { + /// @notice Multiplies 2 numbers together + /// @param x the first uint. + /// @param y the second uint. + /// @return z the product of (x * y) + /// @dev This function does not currently check for overflows + function multiply(uint x, uint y) returns (uint z) { + // This is just a normal comment, and won't get picked up by natspec + z = x * y; + } +} +``` + +`@title` と `@author`はそのままの意味だ。 + +`@notice` は **ユーザー** に、コントラクトや関数が何を行うか説明する。`@dev`は開発者向けのさらなる詳細の説明だ。 + +`@param`と`@return`では、関数の各パラメーターが何であり、どんな値を返すのかを記述する。 + +気をつけてほしいが、すべてのタグはオプショナルなので、各関数にこれらすべてのタグを使用すべきというわけではない。だが最低でも`@dev`タグで各関数の働きを説明することはしておこう。 + +# さあテストだ + +今まで気づかなかったかもしれないが、クリプトゾンビの答え合わの際コメントは無視されている。なのでこのチャプターではなTsぺcコードの答え合わせができない ;) + +だがこれまでお主はSolidityの達人だったから、これもできると仮定しよう! + +どちらにせよ挑戦してみるのだ。`ZombieOwnership`にnatspecタグを加えてみよう: + +1. `@title` — 例:ゾンビ所有権の移転を管理するコントラクト + +2. `@author` — Yお主の名前だ! + +3. `@dev` — 例:OppenZeppelinのERC721ドラフト実装に準拠 diff --git a/jp/5/14-wrappingitup.md b/jp/5/14-wrappingitup.md new file mode 100644 index 0000000000..d6eee6ecbf --- /dev/null +++ b/jp/5/14-wrappingitup.md @@ -0,0 +1,38 @@ +--- +title: まとめ +actions: ['答え合わせ', 'ヒント'] +requireLogin: true +skipCheckAnswer: true +material: + saveZombie: false + zombieDeck: + zombie: + lesson: 5 + hideSliders: true + answer: 1 +--- + +よくここまで頑張ったな! これでレッスン5は終わりだ。 + +ご褒美として、お主にレベル10の**H4XF13LD MORRIS 💯💯😎💯💯** ゾンビを与えておいた! + +(何てことだ、あの伝説の **H4XF13LD MORRIS 💯💯😎💯💯** ゾンビだ!!!!) + +お主のゾンビ軍団はゾンビ4体になった。 + +最後にもしゾンビの名前を変えたければ、右側でゾンビをクリックして新しい名前を入力できる。(一体なぜ **H4XF13LD MORRIS💯💯😎💯💯** の名前を変えたがっているのかわからない、最高の名前じゃないか!) + +## まとめ: + +このレッスンで学んだこと: + +- トークン、ERC721規格、トレード可能な資産/ゾンビ +- ライブラリとその使い方 +- SafeMath関数を使ったオーバーフローとアンダーフロー対策 +- コード中のコメントとnatspecスタンダード + +このレッスンで我らがゲームのSOlidityコードは終わりだ!(今のところ、将来さらにレッスンを追加するかもしれない) + +今後2つのレッスンで、コントラクトのデプロイ方法と **_web3.js_** を使った対話の方法をやっていくぞ(DAppのフロントエンドも作れるからな)。 + +もし希望するならゾンビの名前を変えて、次のチャプターに進んでレッスンを終わりにしよう。 diff --git a/jp/5/15-lessoncomplete.md b/jp/5/15-lessoncomplete.md new file mode 100644 index 0000000000..30566287e8 --- /dev/null +++ b/jp/5/15-lessoncomplete.md @@ -0,0 +1,7 @@ +--- +title: レッスン5終了! +actions: ['答え合わせ', 'ヒント'] +material: + lessonComplete: + answer: 1 +--- diff --git a/jp/index.json b/jp/index.json index e4ba47361e..6e88eb011b 100644 --- a/jp/index.json +++ b/jp/index.json @@ -109,12 +109,28 @@ "このURLを送れば友達はいつでもゾンビ軍団を見れるぞ:" ], "footer": [ - "Lesson5は準備中です。", - "準備出来次第、Eメールでお知らせします。", - "それまで{telegramLink}か{twitterLink}に参加してみましょう!" + "CryptoZombiesはいかがでしたか?ご感想をこちらで受け付けています", + "{telegramLink}に参加するか, {twitterLink}をフォロー", + "準備ができたら、下のボタンを押すとレッスン5に進みます:" ], "zombieDesc": "クリプトゾンビ レベル{levelNum} ", "shareLinkText": "#CryptoZombies のLesson4が終わったよ!敵のIOTAゾンビをやっつけたよ!みんなもやってみて! " + }, + "lesson5": { + "achievementsUnlocked": "次の課題が解除されました:", + "newZombieAdded": "{zombieName} のレベルが {levelNum}にアップしました!", + "header": [ + "よくやった! これでCryptoZombiesの{lesson4}は終了だ!", + "作ったゾンビ軍団を友達にみせてやろう!", + "このURLを送れば友達はいつでもゾンビ軍団を見れるぞ:" + ], + "footer": [ + "Lesson6は準備中です。", + "準備出来次第、Eメールでお知らせします。", + "それまで{telegramLink}か{twitterLink}に参加してみましょう!" + ], + "zombieDesc": "A Level {levelNum} CryptoZombie", + "shareLinkText": "I just completed #CryptoZombies Lesson 5 and got a level 10 CryptoZombie! Check out my zombie army:" } }, "zombieBattle": { @@ -233,6 +249,24 @@ "クリプトゾンビ軍団を作ってランキングに参加しますか?", "CryptoZombiesでは、イーサリアムでゲームを開発する方法を無料で公開しています!スタートはこちらから:" ] + }, + "lesson5": { + "zombieDesc": "{name} クリプトゾンビ", + "achievementPreamble": [ + "友達がクリプトゾンビのレッスン5を終了してレベル10のゾンビを獲得しました!", + "左側で、友達のゾンビ軍団をチェックしよう!すっごく怖いよね?", + "レッスン5を終了するために、友達は以下を学びました:" + ], + "achievements": [ + "トークン、ERC721規格、トレード可能な資産/ゾンビ", + "Solidityのライブラリとその使い方", + "SafeMath関数を使ったオーバーフローとアンダーフロー対策", + "Solidityコード中のコメントとnatspecスタンダード" + ], + "callToAction": [ + "クリプトゾンビ軍団を作ってランキングに参加しますか?", + "CryptoZombiesでは、イーサリアムでゲームを開発する方法を無料で公開しています!スタートはこちらから:" + ] } } } diff --git a/jp/index.ts b/jp/index.ts index 239dbf4d57..1dc2dfcd45 100644 --- a/jp/index.ts +++ b/jp/index.ts @@ -70,6 +70,24 @@ import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' +// lesson5 +import l5_overview from './5/00-overview.md' +import l5_ch1 from './5/01-erc721-1.md' +import l5_ch2 from './5/02-erc721-2.md' +import l5_ch3 from './5/03-erc721-3.md' +import l5_ch4 from './5/04-erc721-4.md' +import l5_ch5 from './5/05-erc721-5.md' +import l5_ch6 from './5/06-erc721-6.md' +import l5_ch7 from './5/07-erc721-7.md' +import l5_ch8 from './5/08-erc721-8.md' +import l5_ch9 from './5/09-safemath-1.md' +import l5_ch10 from './5/10-safemath-2.md' +import l5_ch11 from './5/11-safemath-3.md' +import l5_ch12 from './5/12-safemath-4.md' +import l5_ch13 from './5/13-comments.md' +import l5_ch14 from './5/14-wrappingitup.md' +import l5_complete from './5/15-lessoncomplete.md' + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -142,5 +160,23 @@ export default { l4_ch9, l4_ch10, l4_complete - ] -} \ No newline at end of file + ], + 5: [ + l5_overview, + l5_ch1, + l5_ch2, + l5_ch3, + l5_ch4, + l5_ch5, + l5_ch6, + l5_ch7, + l5_ch8, + l5_ch9, + l5_ch10, + l5_ch11, + l5_ch12, + l5_ch13, + l5_ch14, + l5_complete + ], +} diff --git a/jp/share-template-msgs.json b/jp/share-template-msgs.json index a83e403744..792f53f2b5 100644 --- a/jp/share-template-msgs.json +++ b/jp/share-template-msgs.json @@ -13,6 +13,10 @@ }, "4": { "pageTitle": "クリプトゾンビが敵のIOTAゾンビをやっつけたよ!", - "ogDesc": "CryptoZombiesのレッスン3が終わったよ!敵のIOTAゾンビをやっつけたよ!" + "ogDesc": "CryptoZombiesのレッスン4が終わったよ!敵のIOTAゾンビをやっつけたよ!" + }, + "5": { + "pageTitle": "クリプトゾンビ軍団が大きくなってきたよ!", + "ogDesc": "CryptoZombiesのレッスン5が終わったよ!なんとレベル10のH4XF13LD MORRIS💯💯😎💯💯ゾンビをゲットしたよ!" } } diff --git a/ko/4/battle-02.md b/ko/4/battle-02.md index 6d80bdec3d..b77a505986 100644 --- a/ko/4/battle-02.md +++ b/ko/4/battle-02.md @@ -277,4 +277,4 @@ uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100; 3. 해당 함수는 먼저 `randNonce`를 하나 증가시킬 것이네(`randNonce++` 문법을 사용하게). -4. 마지막으로, (한 줄의 코드로)`now`, `msg.sender`, `randNonce`의 `keccak256` 해시 값을 계산하고 `uint`로 변환해야 하네 - 그리고 그 값 `% _moduls`를 한 후 `return`해야 하네(후, 내용이 아주 장황헀군. 잘 이해가 안 된다면, 위에서 우리가 난수를 만들었던 예시를 보게 - 구조가 매우 유사하네). +4. 마지막으로, (한 줄의 코드로)`now`, `msg.sender`, `randNonce`의 `keccak256` 해시 값을 계산하고 `uint`로 변환해야 하네 - 그리고 그 값 `% _modulus`를 한 후 `return`해야 하네(후, 내용이 아주 장황헀군. 잘 이해가 안 된다면, 위에서 우리가 난수를 만들었던 예시를 보게 - 구조가 매우 유사하네). diff --git a/ko/4/battle-03.md b/ko/4/battle-03.md index e91acbd1d0..c14a958440 100644 --- a/ko/4/battle-03.md +++ b/ko/4/battle-03.md @@ -11,7 +11,7 @@ material: contract ZombieBattle is ZombieHelper { uint randNonce = 0; - // 여기에 attackVictoryProbabillity를 만들게 + // 여기에 attackVictoryProbability를 만들게 function randMod(uint _modulus) internal returns(uint) { randNonce++; @@ -242,7 +242,7 @@ material: ## 직접 해보기 -1. 컨트랙트에 `attackVictoryProbabillity`라는 이름의 `uint` 변수를 추가하고, 여기에 `70`을 대입하게. +1. 컨트랙트에 `attackVictoryProbability`라는 이름의 `uint` 변수를 추가하고, 여기에 `70`을 대입하게. 2. `attack`이라는 이름의 함수를 생성하게. 이 함수는 두 개의 매개변수를 받을 것이네: `_zombieId`(`uint`)와 `_targetId`(`uint`)이네. 이 함수는 `external`이어야 하네. diff --git a/ko/4/battle-04.md b/ko/4/battle-04.md index 9a226be2a6..98b4de9a59 100644 --- a/ko/4/battle-04.md +++ b/ko/4/battle-04.md @@ -300,7 +300,7 @@ require(msg.sender == zombieToOwner[_zombieId]); `zombiefeeding.sol`을 다시 보도록 하겠네. 저 내용을 처음으로 썼던 곳이니 말이야. 확인 부분을 그 부분만의 `modifier`로 만들어 구조를 개선하겠네. -1. `modifier`를 `onwerOf`라는 이름으로 만들게. 이 제어자는 `_zombieId`(`uint`)를 1개의 인수로 받을 것이네. +1. `modifier`를 `ownerOf`라는 이름으로 만들게. 이 제어자는 `_zombieId`(`uint`)를 1개의 인수로 받을 것이네. 제어자 내용에서는 `msg.sender`와 `zombieToOwner[_zombieId]`가 같은지 `require`로 확인하고, 함수를 실행해야 하네. 제어자의 문법이 기억이 나지 않는다면 `zombiehelper.sol`을 참고하면 되네. diff --git a/ko/4/battle-08.md b/ko/4/battle-08.md index c63326826e..bf1f675cfe 100644 --- a/ko/4/battle-08.md +++ b/ko/4/battle-08.md @@ -246,7 +246,7 @@ material: ## 직접 해보기 -1. `rand`가 `attackVictoryProbabillity`와 **_같거나 더 작은지_** 확인하는 `if` 문장을 만들게. +1. `rand`가 `attackVictoryProbability`와 **_같거나 더 작은지_** 확인하는 `if` 문장을 만들게. 2. 만약 이 조건이 참이라면, 우리 좀비가 이기게 되네! 그렇다면: @@ -257,4 +257,4 @@ material: c. `enemyZombie`의 `lossCount`를 증가시키게. (이 패배자!!!!!!! 😫 😫 😫) d. `feedAndMultiply` 함수를 실행하게. 실행을 위한 문법을 보려면 `zombiefeeding.sol`을 확인하게. 3번째 인수(`_species`)로는 `"zombie"`라는 문자열을 전달하게(이건 지금 이 순간에는 실제로 아무 것도 하지 않지만, 이후에 우리가 원한다면 좀비 기반의 좀비를 만들어내는 부가적인 기능을 추가할 수도 있을 것이네). - \ No newline at end of file + diff --git a/ko/4/battle-09.md b/ko/4/battle-09.md index c901ea3283..73bb44b470 100644 --- a/ko/4/battle-09.md +++ b/ko/4/battle-09.md @@ -249,7 +249,7 @@ material: } --- -이제 우리는 좀비가 이겼을 떄 어떤 일이 발생할지에 대해 작성했으니, 좀비가 **지면** 어떤 일이 발생할지 생각해보세. +이제 우리는 좀비가 이겼을 때 어떤 일이 발생할지에 대해 작성했으니, 좀비가 **지면** 어떤 일이 발생할지 생각해보세. 우리 게임에서, 좀비가 진다고 좀비의 레벨이 떨어지지는 않네 - 단순히 좀비의 `lossCount`에 그들의 패배를 기록하고, 다시 공격하기 전에 하루를 기다려야만 하도록 그들의 재사용 대기시간이 활성화될 것이네. diff --git a/pt/3/00-overview.md b/pt/3/00-overview.md index 6eaf692c8d..60e4a955d2 100644 --- a/pt/3/00-overview.md +++ b/pt/3/00-overview.md @@ -4,12 +4,12 @@ header: "Lição 3: Conceitos Avançados de Solidity" roadmap: roadmap3.png --- -Grr... Eu não consigo para você, consigo? Suas habilidades em Solidity são formidáveis, humano... +Grr... Eu não consigo parar você, consigo? Suas habilidades em Solidity são formidáveis, humano... Agora que você tem alguma experiência em Solidity, vamos mergulhar em alguns aspectos mais técnicos do desenvolvimento em Ethereum. -Esta lição será um pouco menos chamativa (desculpe, sem reviravoltas!). Mas você irá aprender conceitos realmente importantes que o deixara mais perto de criar DApps reais - coisas como **propriedade do contrato, custo em gas, otimização de código e segurança**. +Esta lição será um pouco menos chamativa (desculpe, sem reviravoltas!). Mas você irá aprender conceitos realmente importantes que o deixará mais perto de criar DApps reais - coisas como **propriedade do contrato, custo em gas, otimização de código e segurança**. -Você foi avisado - sem gatinhos sem arcos-íris na Lição 3! +Você foi avisado - sem gatinhos nem arcos-íris na Lição 3! -Mas um grande conhecimento em Solidity empacotado. Recomendamos fortemente que você complete a Lição 2 antes de começar a próxima. +Mas um grande conhecimento em Solidity densamente agrupado. Recomendamos fortemente que você complete a Lição 2 antes de começar a próxima. diff --git a/pt/3/01-externaldependencies.md b/pt/3/01-externaldependencies.md index 2ffa40b9a3..a961570c7f 100644 --- a/pt/3/01-externaldependencies.md +++ b/pt/3/01-externaldependencies.md @@ -145,7 +145,7 @@ Até agora, Solidity pareceu bastante similar a outras linguagens como Javascrip Para começar, após você implantar um contrato em Ethereum, este é **_imutável_**, quer dizer que ele nunca poder ser modificado ou melhorado novamente. -O código que você implantou para um contrato esta lá permanentemente, para sempre, na blockchain. Esta é uma das razões na qual a segurança em Solidity é uma grande preocupação. Se houver ma falha no código do contrato, não há maneira de remendar depois. Você precisará dizer aos seus usuários para começarem a usar um outro smart contract que tem a correção. +O código que você implantou para um contrato esta lá permanentemente, para sempre, na blockchain. Esta é uma das razões na qual a segurança em Solidity é uma grande preocupação. Se houver uma falha no código do contrato, não há maneira de remendar depois. Você precisará dizer aos seus usuários para começarem a usar um outro smart contract que tem a correção. Mais isto também é uma parte essencial dos smart contracts. O código é a lei. Se você ler o código do smart contract e verificá-lo, você pode ter certeza que toda vez que chamar aquela função sempre irá acontecer exatamente aquilo que o código diz. Ninguém pode mudar essa função depois e retornar resultados inesperados. @@ -161,7 +161,7 @@ Por exemplo, ao invés de ter um código fixo com o endereço do contrato dos Cr ## Vamos testar -Vamos atualizar o nosso código para a Lição 2 para permitir a mudança do endereço do contrato do CryptoKitties. +Vamos atualizar o nosso código da Lição 2 para permitir a mudança do endereço do contrato do CryptoKitties. 1. Remova a linha de código onde esta fixo a variável `ckAddress`. diff --git a/pt/3/02-ownable.md b/pt/3/02-ownable.md index d5d4bd90a5..aa9e8a42d1 100644 --- a/pt/3/02-ownable.md +++ b/pt/3/02-ownable.md @@ -11,7 +11,7 @@ material: // 1. Importe aqui - // 2. Here aqui: + // 2. Herde Aqui: contract ZombieFactory { event NewZombie(uint zombieId, string name, uint dna); @@ -181,7 +181,7 @@ material: Você percebeu a falha de segurança no capítulo anterior? -`setKittyContractAddress` é um `external`, então qualquer um pode chamá-lo! Isso quer dizer que qualquer que chamar a função pode mudar o endereço do contrato do CryptoKitties, e quebrar a nossa aplicação para todos os usuários. +`setKittyContractAddress` é um `external`, então qualquer um pode chamá-lo! Isso quer dizer que qualquer um que chamar a função pode mudar o endereço do contrato do CryptoKitties, e quebrar a nossa aplicação para todos os usuários. Nós queremos uma maneira de atualizar este endereço em nosso contrato, mas nós não queremos que qualquer um passa atualizá-lo. @@ -189,15 +189,15 @@ Para lidar com casos assim, uma prática que se tornou comum é tornar o contrat ## Contratos `Ownable` do OpenZeppelin -Abaixo um contrato `Ownable` pego da biblioteca Solidity do **_OpenZeppelin_**. OpenZeppelin é uma biblioteca de contratos seguros e auditados pela comunidade que você pode usar em suas próprias DApps. Após esta lição, enquanto você estiver ansioso esperando a lição 4, recomendamos fortemente que você visite o site deles para maior aprendizado. +Abaixo um contrato `Ownable` pego da biblioteca Solidity do **_OpenZeppelin_**. OpenZeppelin é uma biblioteca de contratos seguros e auditados pela comunidade que você pode usar em suas próprias DApps. Após esta lição, recomendamos fortemente que você visite o site deles para maior aprendizado. -Leia com atenção o contrato abaixo. Você verá algumas coisas que nós já aprendemos, mas não se preocupe, iremos falar mais sobre em seguida. +Leia com atenção o contrato abaixo. Você verá algumas coisas que nós já aprendemos, mas não se preocupe, iremos falar mais sobre isso em seguida. ``` /** * @title Ownable - * @dev Um contrato Ownable tem um endereço de dono, e fornece uma funções de controle básicos, - * que simplifica a implementação de "permissões de usuário". + * @dev Um contrato Ownable tem um endereço de dono, e fornece funções básicas de autorização, + * que simplificam a implementação de "permissões de usuário". */ contract Ownable { address public owner; @@ -233,15 +233,15 @@ contract Ownable { Um pouco de novas coisas que não vimos antes: -- Construtores: `function Ownable()` is a **_constructor_**, que é um função opcional e especial que tem o mesmo nome do contrato. Esta será executada somente uma vez, quando o contrato é criado a primeira vez. -- Funções Modificadoras: `modifier onlyOwner()`. Modificadores são um tipo de meia-função que são usadas para modificar outras funções, normalmente usadas para checar algo requerido antes da execução. Neste caso, `onlyOwner` pode ser usada para limitar o acesso então **only** (somente) the **owner** (dono) do contrato pode executar esta função. Nós iremos falar mais sobre funções modificadoras no próximo capítulo, e o que esse `_;` faz. -- Palavra-chave `indexed`: não se preocupe com esse, nós ainda não precisamos. +- Construtores: `function Ownable()` é um **_construtor_**, que é uma função opcional e especial que tem o mesmo nome do contrato. Esta será executada somente uma vez, quando o contrato é criado a primeira vez. +- Funções Modificadoras: `modifier onlyOwner()`. Modificadores são um tipo de meia-função que são usadas para modificar outras funções, normalmente usadas para checar algo requerido antes da execução. Neste caso, `onlyOwner` pode ser usada para limitar o acesso então **only** (somente) o **owner** (dono) do contrato pode executar esta função. Nós iremos falar mais sobre funções modificadoras no próximo capítulo, e o que esse `_;` faz. +- Palavra-chave `indexed`: não se preocupe com isso, nós ainda não precisamos. Então o contrato `Ownable` basicamente faz o seguinte: 1. Quando o contrato é criado, este construtor define o `owner` (dono) para `msg.sender` (a pessoa que implantou-o na blockchain) -2. Este adiciona um modificador `onlyOwner`, que restringe o acesso a certas função para somente o `owner` (dono) +2. Este adiciona um modificador `onlyOwner`, que restringe o acesso a certas função somente para o `owner` (dono) 3. Também permite a transferência de um contrato para o novo `owner` (dono) @@ -251,7 +251,6 @@ Já que nós queremos limitar o `setKittyContractAddress` para `onlyOwner`, tere ## Vamos testar -We've gone ahead and copied the code of the `Ownable` contract into a new file, `ownable.sol`. Let's go ahead and make `ZombieFactory` inherit from it. Nós já copiamos o código do contrato `Ownable` em um novo arquivo, `ownable.sol`. Vá em frente e faça o `ZombieFactory` herdá-lo. 1. Modifique nosso código para importar com `import` o conteúdo de `ownable.sol`. Se você não lembra como fazer isso de uma olhada em `zombiefeeding.sol`. diff --git a/pt/5/00-overview.md b/pt/5/00-overview.md new file mode 100644 index 0000000000..cc6cc72d21 --- /dev/null +++ b/pt/5/00-overview.md @@ -0,0 +1,13 @@ +--- +title: ERC721 & Cripto-Colecionáveis +header: "Lesson 5: ERC721 & Cripto-Colecionáveis" +roadmap: roadmap5.jpg +--- + +Ufa! As coisas estão começando a esquentar aqui... + +Nesta lição, Nós vamos ficar um pouco mais avançados. + +Vamos falar sobre **tokens**, e o padrão **ERC721**, e **ativos cripto-colecionáveis**. + +Em outras palavras, vamos fazer isso **para que você possa trocar zumbis com os seus amigos.** diff --git a/pt/5/01-erc721-1.md b/pt/5/01-erc721-1.md new file mode 100644 index 0000000000..e9975c7574 --- /dev/null +++ b/pt/5/01-erc721-1.md @@ -0,0 +1,289 @@ +--- +title: Tokens no Ethereum +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + // Comece aqui + "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 { + + } +--- + +Vamos falar sobre **_tokens_**. + +Se você esteve acompanhando o Ethereum por período de tempo, você provavelmente ouviu pessoas falando sobre tokens - especificamente **_ERC20 tokens_**. + +Um **_token_** no Ethereum é basicamente um smart contract (contrato inteligente) que segue algumas regras comuns — isto é, ele implementa um conjunto padrão de funções que todos os outros contratos de token compartilham, como o `transfer(address _to, uint256 _value)` e `balanceOf(address _owner)`. + +Internamente o smart contract normalmente tem um mapeamento, `mapping(address => uint256) balances`, que mantêm o registro de quanto em saldo cada endereço tem. + +Então basicamente um token é somente contrato que mantem o registro de quem é o dono desse token, e algumas funções, então esses usuários podem transferir seus tokens para outros endereços. + +### Por que isso importa? + +Uma vez que todo token ERC20 compartilha o mesmo conjunto de funções com os mesmos nomes, todos eles podem interagir da mesma maneira. + +Isto significa se você construir uma aplicação que é capaz de interagir com um token ERC20, ela será capaz de interagir com qualquer token ERC20. Deste jeito mais tokens podem facilmente serem adicionados a sua aplicação no futuro sem a necessidade the qualquer código customizado. Você pode simples plugar um novo contrato de token. + +Um exemplo disso poderia ser uma exchange. Quando uma exchange adiciona um novo token ERC20, na verdade ela só precisa adicionar um outro smart contract para funcionar. Usuários podem dizer para este contrato enviar tokens para o endereço da carteira da exchange, e a exchange pode dizer ao contrato para enviar os tokens de volta para os usuários quando eles requisitarem um saque. + +A exchange só precisa implementar esta lógica da transferência uma vez, então quando quiser adicionar um novo token ERC20, é simplesmente um problema de adicionar um novo endereço de contrato no banco de dados. + +### Outros padrões de token + +Tokens ERC20 são realmente legais para tokens que agem como moedas. Mas eles não são particularmente úteis para representar zumbis em nosso jogo de zumbi. + +Primeiro, zumbis não são divisíveis como moedas — Eu posso enviar para você 0.237 ETH, mas transferir pra você 0.237 de um zumbi realmente não faz sentido algum. + +Segundo, todos os zumbis não são criados iguais. Seu zumbi Nível 2 "**Steve**" é totalmente diferente do meu zumbi Nível 732 "**H4XF13LD MORRIS 💯💯😎💯💯**". (Nem mesmo perto, *Steve*). + +Há um outro padrão de token que se encaixa bem melhor para cripto-colecionáveis como CryptoZombies – e eles chamados de **_tokens ERC721._** + +**Tokens ERC721_** **não** são intercambiáveis uma vez que cada um é suposto para ser único, e não divisíveis. Você somente pode trocá-los em unidades inteiras, e cada um tem um ID único. Então esses se encaixam perfeitamente para fazer nossos zumbis trocáveis. + +> Note que usando um padrão como ERC721 tem o benefício que não precisamos ter que implementar uma lógica de leilão ou garantia dentro do nosso contrato que determina como os jogadores devem trocar / vender nossos zumbis. Se obedecer-mos a especificação, qualquer um poderia criar uma plataforma de troca para ativos cripto-colecionáveis ERC721, e nossos zumbis ERC721 seriam utilizáveis nesta plataforma. Então os benefícios são claros de usar um padrão de token ao invés de criar a sua própria lógica de trocas. + +## Vamos testar + +Vamos mergulhar na implementação do ERC721 no próximo capítulo. Mas primeiro, vamos configurar a estrutura de arquivos para esta lição. + +Iremos guardar toda a lógica do ERC721 em um contrato chamado `ZombieOwnership`. + +1. Declare nossa versão `pragma` no topo do arquivo (olhe os arquivos das lições anteriores para a sintaxe). + +2. Este arquivo deve usar o `import` do `zombieattack.sol`. + +3. Declare um novo contrato, `ZombieOwnership`, que herda de `ZombieAttack`. Deixe o corpo do contrato vazio por enquanto. diff --git a/pt/5/02-erc721-2.md b/pt/5/02-erc721-2.md new file mode 100644 index 0000000000..787520e904 --- /dev/null +++ b/pt/5/02-erc721-2.md @@ -0,0 +1,308 @@ +--- +title: Padrão ERC721, Múltipla Herança +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + // Importe o arquivo aqui + + // Declare a herança ERC721 aqui + contract ZombieOwnership is ZombieAttack { + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + } +--- + +Vamos dar uma olhada no padrão ERC721: + +``` +contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; +} +``` + +Esta é a lista de métodos que precisamos implementar, que iremos fazer em partes nos próximos capítulos. + +Isso parece um monte, mas sinta-se sobrecarregado! Estamos aqui para guiar você. + +> Nota: O padrão ERC721 é atualmente um *rascunho*, e ainda não há oficialmente um acordo de implementação. Neste tutorial usamos a versão atual da biblioteca do OpenZeppelin, mas é possível que mude no futuro antes do lançamento oficial. Então considere esta **uma** possível implementação, mas não considere uma versão oficial dos tokens ERC721. + +### Implementando um contrato de token + +Quando implementar um contrato de token, a primeira coisa que fazemos é copiar a interface para o nosso próprio arquivo Solidity e importá-lo. `import ./erc721.sol`. Então nós teremos o nosso contrato herdando-o, e iremos sobrepor cada método com a definição da função. + +Mas espere – `ZombieOwnership` já esta herdando do `ZombieAttack` – como pode também herdar do `ERC721`? + +Para a nossa sorte em Solidity, seu contrato pode herdar de múltiplos contrato conforme segue: + +``` +contract SatoshiNakamoto is NickSzabo, HalFinney { + // Meu deus, os segredos do universo foram revelados! +} +``` + +Como você pode ver, quando usando a múltipla herança, você pode separar os múltiplos contrato que você estar herdando com um vírgula, `,`. Neste caso, nosso contrato esta herdando de `NickSzabo` e `HalFinney`. + +## Vamos testar + +Já criamos `erc721.sol` com as interfaces acima para você. + +1. Importe o `erc721.sol` em `zombieownership.sol` + +2. Declare o `ZombieOwnership` herdando de `ZombieAttack` e `ERC721` diff --git a/pt/5/03-erc721-3.md b/pt/5/03-erc721-3.md new file mode 100644 index 0000000000..271b8e040b --- /dev/null +++ b/pt/5/03-erc721-3.md @@ -0,0 +1,338 @@ +--- +title: balanceOf & ownerOf +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + // 1. Retorne o número de zumbis que o `_owner` tem aqui + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + // 2. Retorne o dono do `_tokenId` aqui + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Ótimo, vamos mergulhar na implementação do ERC721! + +Já saímos na frente e copiamos uma casca vazia de todos as funções que você irá implementar nesta lição. + +Neste capítulos, iremos implementar os dois primeiros métodos: `balanceOf` e `ownerOf`. + +### `balanceOf` + +``` + function balanceOf(address _owner) public view returns (uint256 _balance); +``` + +Esta função simplesmente recebe um `address`, e retorna quantos tokens que o `address` tem. + +Em nosso caso, nossos "tokens" são Zumbis. Você lembra onde em nossa DApp nós guardamos quantos zumbis um dono tem? + +### `ownerOf` + +``` + function ownerOf(uint256 _tokenId) public view returns (address _owner); +``` + +Esta função recebe um token ID (em nosso caso, um ID Zumbi), e retorna o `address` da pessoa que o possui. + +Novamente, esta é muito fácil para implementar-mos, uma vez que já temos um `mapping` (mapeamento) em nossa DApp que guarda esta informação. Podemos implementar esta função em uma linha, só uma declaração de `return`. + +> Nota: Lembre, `uint256` é equivalente à `uint`. Estávamos usando `uint` em nosso código até agora, mas nós usamos `uint256` aqui por que copiamos e colamos da especificação. + +## Vamos testar + +Irei deixar para você descobrir como implementar estas duas funções. + +Cada função, deve simplesmente ser uma linha de código, uma declaração de `return`. De uma olhada em nosso código das lições anteriores para ver onde guardamos este dado. Se você não descobrir, você pode clicar no botão "mostre-me a resposta" para obter ajuda. + +1. Implemente o `balanceOf` para retornar o número de zumbis que um `_owner` tem. + +2. Implemente o `ownerOf` para retornar o endereço de quem é o dono zumbi com ID `_tokenId`. diff --git a/pt/5/04-erc721-4.md b/pt/5/04-erc721-4.md new file mode 100644 index 0000000000..1efe3f9e95 --- /dev/null +++ b/pt/5/04-erc721-4.md @@ -0,0 +1,355 @@ +--- +title: Refatorando +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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; + + // 1. Mude o modificador para `onlyOwnerOf` + 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); + } + + // 2. Mude o nome do modificador aqui também + 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"); + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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; + } + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } +--- + +Opa! Nós introduzimos um erro em nosso código que vai impedir de compilar. Você percebeu? + +No capítulo anterior nós definimos a função chamada de `ownerOf`. Mas se você lembrar da Lição 4, nós também criamos um `modifier` (modificador) com o mesmo nome, `ownerOf`, em `zombiefeeding.sol`. + +Se você tentar compilar este código, o compilador irá retornar um erro dizendo que você não pode ter um modificador e uma função com o mesmo nome. + +Então devemos somente mudar o nome da função em `ZombieOwnership` para qualquer coisa? + +Não, não podemos fazer isso!!! Lembre-se, estamos usando o token padrão ERC721, que significa que outros contratos irão esperar que o nosso contrato tenha as funções com os nomes definidos exatamente. Isto é o que faz estes padrões úteis – se outro contrato sabe que nosso contrato é compatível com ERC721, este pode simplesmente conversar conosco sem a necessidade de saber qualquer coisa sobre as nossas decisões de implementação interna. + +Então significa que iremos ter que refatorar o nosso código da Lição 4 para mudar o nome do `modifier` para outra coisa. + +## Vamos testar + +De volta em `zombiefeeding.sol`. Iremos mudar o nome do nosso `modifier` de `ownerOf` para `onlyOwnerOf`. + +1. Mude o nome da definição do modificador para `onlyOwnerOf` + +2. Role para baixo para a função `feedAndMultiply`, que usar este modificador. Precisamos mudar o nome aqui também. + +> Nota: Nós também usamos este modificar em `zombiehelper.sol` e `zombieattack.sol`, mas não vamos gastar muito tempo nesta lição refatorando, então já saímos na frente e atualiza-mos os modificadores nos arquivos pra você. diff --git a/pt/5/05-erc721-5.md b/pt/5/05-erc721-5.md new file mode 100644 index 0000000000..a65dd23e4a --- /dev/null +++ b/pt/5/05-erc721-5.md @@ -0,0 +1,347 @@ +--- +title: "ERC721: Lógica de Transferência" +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + // Defina a função _transfer() aqui + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Ótimo, consertamos o conflito! + +Agora iremos continuar nossa implementação do ERC721 olhando na transferência de propriedade de uma pessoa para outra. + +Note que a especificação ERC721 tem duas maneiras diferentes de transferir tokens: + +``` +function transfer(address _to, uint256 _tokenId) public; +function approve(address _to, uint256 _tokenId) public; +function takeOwnership(uint256 _tokenId) public; +``` + +1. A primeira forma é o dono do token chamar `transfer` com o `address` que ele quer transferir, e o `_tokenId` do token que ele quer transferir. + +2. A segunda forma é o dono do token primeiro chama `approve`, e envia a mesma informação acima. O contrato então guarda quem esta aprovado para pegar o token, normalmente um `mapping (uint256 => address)`. Então quando alguém chamar `takeOwnership`, o contrato checa se o `msg.sender` esta aprovado pelo dono para pegar o token, se estiver transfere o token para ele. + +Se você notar, ambos `transfer` e `takeOwnership` irão conter a mesma lógica de transferência, em ordem inversa. (Em um caso o remetente do token chama a função; na outra o destinatário do token a chama). + +Então faz sentido abstrairmos esta lógica em uma função privada, `_transfer`, que então será chamada por ambas funções. Desta maneira não precisamos repetir o mesmo código duas vezes. + +## Vamos testar + +Vamos definir a lógica para `_transfer`. + +1. Defina a função chamada `_transfer`. Esta irá receber 3 argumentos, `address _from`, `address _to`, e `uint256 _tokenId`. Esta deverá ser uma função `private` (privada). + +2. Nós temos 2 mapeamos que irão mudar quando a propriedade mudar: `ownerZombieCount` (que guarda o registro de quantos zumbis um dono tem) e `zombieToOwner` (que guarda o registro de quem é dono). + + A primeira coisa que nossa função deveria fazer é incrementar `ownerZombieCount` da pessoa que esta **recebendo** o zumbi (`address _to`). Use o `++` para incrementar. + +3. Em seguida, precisamos **decrementar** o `ownerZombieCount` para a pessoa que esta **enviando** o zumbi (`address _from`). Use o `--` para decrementar. + +4. Por último, precisamos mudar o mapeamento `zombieToOwner` para este `_tokenId` então este agora aponta para `_to`. + +5. Eu menti, este não era o último passo. Ainda há mais uma coisa que devemos fazer. + + A especificação ERC721 incluí um evento `Transfer`. A última linha desta função deverá disparar o `Transfer` com a informação correta – verifique o `erc721.sol` para ver os argumentos esperados para serem chamados com a implementação aqui. diff --git a/pt/5/06-erc721-6.md b/pt/5/06-erc721-6.md new file mode 100644 index 0000000000..a5ff923475 --- /dev/null +++ b/pt/5/06-erc721-6.md @@ -0,0 +1,327 @@ +--- +title: "ERC721: Ainda na Transferência" +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + // 1. Adicione o modificador aqui + function transfer(address _to, uint256 _tokenId) public { + // 2. Defina a função aqui + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Ótimo! Esta foi a parte difícil – agora implementando a função pública `transfer` será fácil, uma vez que nossa função `_transfer` já faze quase toda a parte pesada. + +## Vamos testar + +1. Queremos ter certeza que somente o dono do token/zumbi pode transferi-lo. Lembra como podemos limitar o acesso a função para somente o dono? + + Sim, isso mesmo, nós já temos um modificador que faz isso. Então vamos adicionar o modificador `onlyOwnerOf` para esta função. + +2. Agora o corpo da função realmente somente precisa de uma linha... Só precisa chamar `_transfer`. + + Tenha certeza de passar o `msg.sender` para o argumento `address _from`. diff --git a/pt/5/07-erc721-7.md b/pt/5/07-erc721-7.md new file mode 100644 index 0000000000..cf5a23f53b --- /dev/null +++ b/pt/5/07-erc721-7.md @@ -0,0 +1,342 @@ +--- +title: "ERC721: Approve" +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + // 1. Defina o mapeamento aqui + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + // 2. Adicione a função modificador aqui + function approve(address _to, uint256 _tokenId) public { + // 3. Defina a função aqui + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +Agora, vamos implementar `approve`. + +Lembre-se, com `approve` / `takeOwnership`, a transferência acontece em 2 passos: + +1. Você, o dono, chama `approve` e informa o `address` do novo dono, e o `_tokenId` que você quer ele pegue + +2. O novo dono chama `takeOwnership` com o `_tokenId`, o contrato verifica para certeza que ele já foi aprovado, e então transfer a ele o token. + +Por que isto acontece em 2 chamadas de funções, precisamos de uma estrutura de dados para guardar quem esta sendo aprovado para que entre as chamadas das funções. + +## Vamos testar + +1. Primeiro, vamos definir uma mapeamento `zombieApprovals`. Este deve mapear um `uint` para um `address`. + + Desta maneira, quando alguém chamar `takeOwnership` com um `_tokenId`, podemos usar este mapeamento para rapidamente achar quem é aprovado para pegar o token. + +2. Na função `approve`, queremos ter certeza que somente o dono do token pode dar permissão para alguém obtê-lo. Então precisamos adicionar o modificador `onlyOwnerOf` em `approve` + +3. Para o corpo da função, atribua `zombieApprovals` para o `_tokenId` igual o endereço `_to`. + +4. Finalmente, há um evento `Approval` na especificação ERC721. Então devemos disparar este evento no final da função. Verifique o `erc721.sol` para os argumentos, e tenha certeza de usar `msg.sender` como o `_owner`. diff --git a/pt/5/08-erc721-8.md b/pt/5/08-erc721-8.md new file mode 100644 index 0000000000..ac01bf5ee2 --- /dev/null +++ b/pt/5/08-erc721-8.md @@ -0,0 +1,340 @@ +--- +title: "ERC721: takeOwnership" +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + // Comece aqui + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +Ótimo, agora vamos terminar a nossa implementação do ERC721 com a última função! (Não se preocupe, ainda há a mais para cobrir na Lição 5 após isso 😉) + +A função final, `takeOwnership`, deve simplesmente verificar o `msg.sender` para ter certeza que foi aprovado para pegar este token / zumbi, e chamar `_transfer` se ok. + +## Vamos testar + +1. Primeiro, queremos usar uma declaração de `require` para verificar que `zombieApprovals` para `_tokenId` é igual a `msg.sender`. + + Desta forma se `msg.sender` não for aprovado para pegar este token, este irá lançar um erro. + +2. Para chamar `_transfer`, precisamos saber o endereço da pessoa que é dono do token (isto requer `_from` como um argumento). Para a nossa sorte podemos ver isso em nossa função `ownerOf`. + + Então declare uma variável `address` chamada `owner`, e atribua igual a `ownerOf(_tokenId)`. + +3. Finalmente, chame `_transfer`, e passe-a toda a informação requerida. (Aqui você pode usar `msg.sender` para `_to`, uma vez que a pessoa chamando esta função é a escolhida para quem o token deve ser enviado). + +> Nota: Poderíamos ter feito os passos 2 e 3 em uma linha de código, mas separá-las deixa as coisas mais legíveis. Preferência pessoal. diff --git a/pt/5/09-safemath-1.md b/pt/5/09-safemath-1.md new file mode 100644 index 0000000000..fdbe2dafe6 --- /dev/null +++ b/pt/5/09-safemath-1.md @@ -0,0 +1,444 @@ +--- +title: Prevenindo Overflows +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + // 1. Importe aqui + + contract ZombieFactory is Ownable { + + // 2. Declare o uso de safemath aqui + + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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); + } + + } +--- + +Parabéns, isto conclui a nossa implementação do ERC721! + +Isso não foi tão difícil, foi? Um monte destas coisas em Ethereum soa complicado quando você ouve as pessoas falando, então a melhor maneira de entender é na verdade ir implementar você mesmo. + +Tenha em mente que isto é o mínimo de implementação. Existem recursos extras que queremos adicionar a nossa implementação, como algumas checagem extras para ter certeza que os usuários acidentalmente não transfiram os zumbis para o endereço `0` (que é conhecido como "queimando" um token – basicamente enviado para um endereço que ninguém tem a chave privada, essencialmente tornando-o irrecuperável). Ou colocar uma lógica básica de um leilão na própria DApp (Você consegue pensar em algumas maneiras de implementar-mos isto?) + +Mas queremos manter esta lição manejável, então fomos com a lógica de implementação mais básica. Se você quiser ver um exemplo de uma implementação mais à fundo, você pode dar uma olhada no contrato ERC721 do OpenZeppelin após este tutorial. + +### Melhorias de segurança no contrato: Overflows e Underflows + +Vamos olhar para um dos principais recursos de segurança que você deve estar ciente ao escrever smart contracts: Prevenção de overflows e underflows. + +O que é um **_overflow_** (transbordamento) ? + +Digamos que você tem um `uint8`, que pode ter somente 8 bits. Isso significa que o maior número que podemos guardar é o binário `11111111` (ou um decimal, 2^8 - 1 = 255). + +De uma olhada no seguinte código. Qual é o `number` igual no final? + +``` +uint8 number = 255; +number++; +``` + +Neste caso, nós causamos um "overflow" – então o `number` é contraintuitivamente igual a `0` mesmo após nós aumentarmos. (Se você adicionar 1 para um binário `11111111`, ele restabelece de volta para `00000000`, como um relógio indo de `23:59` para `00:00`). + +Um "underflow" é parecido, onde se você subtrair `1` de um `uint8` que é igual a `0`, este agora é igual à `255` (porque `uint`s são sem sinal, e não podem ser negativos). + +Enquanto não usamos `uint8` aqui, parece improvável que o `uint256` irá transbordar quando incrementarmos em `1` toda vez (2^256 é realmente um número grande), ainda é bom colocar proteções em nossos contratos então nossa DApp nunca terá um comportamento indesejável no futuro. + +### Usando SafeMath + +Para prevenir isto, OpenZeppelin criou uma **_library_** (biblioteca) chamada SafeMath que previne estes erros por padrão. + +Mas antes de disso... O que é uma biblioteca? + +Uma **_biblioteca_** é tipo especial de contrato em Solidity. Uma das coisas que são úteis para anexar funções em tipos de dados nativos. + +Por exemplo, como a biblioteca SafeMath, podemos usar a sintaxe `using SafeMath for uint256`. A biblioteca SafeMath tem 4 funções – `add` (adição), `sub` (subtração), `mul` (multiplicação) e `div` (divisão). E como nós podemos acessar essas funções de `uint256` conforme segue: + +``` +using SafeMath for uint256; + +uint256 a = 5; +uint256 b = a.add(3); // 5 + 3 = 8 +uint256 c = a.mul(2); // 5 * 2 = 10 +``` + +Vamos ver o que estas funções fazem no próximo capítulo, mas por agora vamos adicionar a biblioteca SafeMath em nosso contrato. + +## Vamos testar + +Nós já incluímos a biblioteca `SafeMath` do OpenZeppelin pra você em `safemath.sol`. Você pode dar uma olhada no código agora se quiser, mas nós iremos olhar à fundo no próximo capítulo. + +Primeiro vamos dizer ao nosso contrato para usar SafeMath. Iremos fazer isso em ZombieFactory, nosso contrato base – desta forma podemos usá-lo em qualquer um dos sub-contratos herdados deste. + +1. Importe `safemath.sol` em `zombiefactory.sol`. + +2. Adicione a declaração `using SafeMath para uint256`. diff --git a/pt/5/10-safemath-2.md b/pt/5/10-safemath-2.md new file mode 100644 index 0000000000..3bffdd9e1e --- /dev/null +++ b/pt/5/10-safemath-2.md @@ -0,0 +1,469 @@ +--- +title: SafeMath Parte 2 +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + // 1. Substitua com o `add` da SafeMath + ownerZombieCount[_to]++; + // 2. Substitua com o `sub` da SafeMath + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[_from] = ownerZombieCount[_from].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +Vamos dar uma olhada no código por trás da SafeMath: + +``` +library SafeMath { + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automaticamente lança uma exceção quando dividindo por 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // Não há nenhum caso que isso não contenha + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} +``` + +Primeiro nós temos a palavra reservada `library` (biblioteca) – bibliotecas são similares a `contract`s mas com poucas diferenças. Para nosso propósito, bibliotecas permitem-nos o uso da palavra reservada `using`, que automaticamente acrescenta todos os métodos da biblioteca em outro tipo de dado: + +``` +using SafeMath for uint; +// agora podemos usar estes métodos em qualquer uint +uint test = 2; +test = test.mul(3); // test agora é igual a 6 +test = test.add(5); // test agora é igual a 11 +``` + +Note que as funções `mul` e `add` cada uma requer 2 argumentos, mas quando nós declaramos `using SafeMath for uint`, o `uint` que chamamos a função (`test`) é automaticamente passado como o primeiro argumento. + +Vamos dar uma olhada no código por de trás de `add` para ver o que SafeMath faz: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} +``` + +Basicamente `add` só adiciona 2 `uint`s como `+`, mas este também contem uma declaração `assert` para ter certeza que a soma é maior do que `a`. Isto nos protege contra overflows. + +`assert` é similar o `require`, onde este irá lançar um erro se falso. A diferença entre `assert` e `require` é que `require` irá reembolsar o usuário o resto do seu gás quando a função falhar, enquanto que `assert` não irá. Então a maior parte do tempo você quer usar o `require` em seu código; `assert` é tipicamente usando quando algo de terrível houve com o código (como um `uint` transbordar). + +Então, simplesmente use, As funções `add`, `sub`, `mul` e `div` da SafeMath que fazem as 4 operações matemáticas básicas, mas lançam um erro se um overflow ou underflow ocorrer. + +### Usando SafeMath em nosso código. + +Para prevenir overflow and underflow, podemos procurar por lugares em nosso código onde usamos `+`, `-`, `*`, or `/`, e substituí-los por `add`, `sub`, `mul` e `div`. + +Ex: Ao invés de fazer: + +``` +myUint++; +``` + +Nós deveríamos fazer: + +``` +myUint = myUint.add(1); +``` + +## Vamos testar + +Temos 2 lugares em `ZombieOwnership` onde usamos operações matemáticas. Vamos trocá-las métodos SafeMath. + +1. Substitua `++` com o método SafeMath. + +2. Substitua `--` com o método SafeMath. diff --git a/pt/5/11-safemath-3.md b/pt/5/11-safemath-3.md new file mode 100644 index 0000000000..64512ff8b4 --- /dev/null +++ b/pt/5/11-safemath-3.md @@ -0,0 +1,508 @@ +--- +title: SafeMath Parte 3 +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + // 1. Declare using SafeMath32 for uint32 + // 2. Declare using SafeMath16 for uint16 + + 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 { + // Nota: Nós escolhemos não prevenir o problema ano 2038...Então não precisa + // se preocupar sobre overflow no readyTime. Nosso aplicativo será acabado em 2038 de qualquer forma ;) + uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; + zombieToOwner[id] = msg.sender; + // 3. Vamos usar o `add` do SafeMath aqui: + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath32 + * @dev SafeMath library implemented for uint32 + */ + library SafeMath32 { + + function mul(uint32 a, uint32 b) internal pure returns (uint32) { + if (a == 0) { + return 0; + } + uint32 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint32 a, uint32 b) internal pure returns (uint32) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint32 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint32 a, uint32 b) internal pure returns (uint32) { + assert(b <= a); + return a - b; + } + + function add(uint32 a, uint32 b) internal pure returns (uint32) { + uint32 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath16 + * @dev SafeMath library implemented for uint16 + */ + library SafeMath16 { + + function mul(uint16 a, uint16 b) internal pure returns (uint16) { + if (a == 0) { + return 0; + } + uint16 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint16 a, uint16 b) internal pure returns (uint16) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint16 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint16 a, uint16 b) internal pure returns (uint16) { + assert(b <= a); + return a - b; + } + + function add(uint16 a, uint16 b) internal pure returns (uint16) { + uint16 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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); + } + + } +--- + +Ótimo, agora nossa implementação ERC721 esta segura de overflows & underflows! + +Voltando ao código que escrevemos em lições anteriores, existem uns poucos outros lugares onde o nosso código poderia estar vulnerável a overflows ou underflows. + +Por exemplo, em ZombieAttack nós temos: + +``` +myZombie.winCount++; +myZombie.level++; +enemyZombie.lossCount++; +``` + +Deveríamos prevenir overflows aqui também só para estar seguro. (É uma boa ideia em geral só usar SafeMath ao invés de usar operações matemáticas básicas. Talvez em versões futuras de Solidity estes estejam implementados por padrão, mas por agora nós temos que ter precaução extra com a segurança em nosso código). + +Porém temos um leve problema – `winCount` e `lossCount` são `uint16`, e `level` é um `uint32`. Então se nós usarmos o método `add` da SafeMath com estes argumentos, este não nos protegerão de overflows uma vez que isto irá converter estes tipos para `uint256`: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} + +// Se nós chamarmos `.add` em um `uint8`, este será convertido para um `uint256`. +// Então este não terá um overflow em 2^8, uma vez que um valor 256 é valido para um `uint256`. +``` + +Isto significa que teremos que implementar 2 bibliotecas a mais para prevenir overflows/underflows com os nossos `uint16` e `uint32`. Iremos chamá-las de `SafeMath16` e `SafeMath32`. + +O código será exatamente o mesmo que SafeMath, exceto todas instâncias de `uint256` serão substituídas com `uint32` ou `uint16`. + +Nós saímos na frente e implementamos o código pra você – vá em frente e veja em `safemath.sol` para ver o código. + +Agora precisamos implementar em ZombieFactory. + +## Vamos testar + +Tarefa: + +1. Declare que estamos usando `SafeMath32` para `uint32`. + +2. Declare que estamos usando `SafeMath16` para `uint16`. + +3. Existe mais uma linha de código em ZombieFactory onde nós devemos usar o método SafeMath. Deixamos um comentário para indicar onde. diff --git a/pt/5/12-safemath-4.md b/pt/5/12-safemath-4.md new file mode 100644 index 0000000000..efe8b5e05a --- /dev/null +++ b/pt/5/12-safemath-4.md @@ -0,0 +1,381 @@ +--- +title: SafeMath Parte 4 +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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) { + // Aqui um! + randNonce++; + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + // Aqui mais 3! + myZombie.winCount++; + myZombie.level++; + enemyZombie.lossCount++; + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + // ...e mais 2! + myZombie.lossCount++; + enemyZombie.winCount++; + _triggerCooldown(myZombie); + } + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 = zombies[_zombieId].level.add(1); + } + + function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 = randNonce.add(1); + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + myZombie.winCount = myZombie.winCount.add(1); + myZombie.level = myZombie.level.add(1); + enemyZombie.lossCount = enemyZombie.lossCount.add(1); + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + myZombie.lossCount = myZombie.lossCount.add(1); + enemyZombie.winCount = enemyZombie.winCount.add(1); + _triggerCooldown(myZombie); + } + } + } +--- + +Ótimo, agora podemos implementar SafeMath em todos os tipos de `uint` que usamos em nossa DApp! + +Vamos consertar todos esse potências problemas em `ZombieAttack`. (Ainda há um `zombies[_zombieId].level++;` que precisa ser consertado em `ZombieHelper`, mas nós cuidamos deste pra você então nós não precisamos de um capítulo extra 😉). + +## Vamos testar + +Vá em frente e implemente os métodos SafeMath em todos os incrementos `++` em `ZombieAttack`. Deixamos comentários no código para deixá-los fáceis de achar. diff --git a/pt/5/13-comments.md b/pt/5/13-comments.md new file mode 100644 index 0000000000..47447ebc0e --- /dev/null +++ b/pt/5/13-comments.md @@ -0,0 +1,465 @@ +--- +title: Comentários +actions: ['verificarResposta', 'dicas'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + /// TODO: Substitua isto com um descrição natspec + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +O código Solidity para o nosso jogo zumbi finalmente terminou! + +Nas próximas lições, iremos olhar em como implantar o código no Ethereum, e como interagir com ele usando a Web3.js. + +Mas uma coisa final antes de deixá-lo ir na Lição 5: Vamos falar sobre **comentando o seu código**. + +## Sintaxe para comentários + +Comentando um código em Solidity e como em JavaScript. Você já viu alguns exemplos de uma simples linha de comentário nas lições do CryptoZombies. + +``` +// Este é um comentário de uma linha. É tipo uma nota própria (ou para outros) +``` + +Somente adicione duas `//` em qualquer lugar e você esta comentando. É tão fácil que você deveria fazer toda hora. + +Mas eu entendo você – algumas vezes uma linha não é o suficiente. Você nasceu um escritor, afinal! + +Assim, nós também temos comentários multi-linhas: + +``` +contract CryptoZombies { + /* Este é um comentário multi-linha. Eu gostaria de agradecer à todos vocês + que usaram o seu tempo para tentar este curso de programação. + Eu sei que é grátis para todos vocês, e continuará grátis + para sempre, mas ainda colocamos nossos corações e almas para fazer + isto tão bom quanto o possível. + + Saiba que este ainda é o início do desenvolvimento do Blockchain. + Chegamos muito longe, mas ainda existem várias maneiras de fazer esta + comunidade melhor. Se cometemos um erro em algum lugar, você pode + nos ajudar e abrir um pull request aqui: + https://github.com/loomnetwork/cryptozombie-lessons + + Ou se você tiver algumas ideias, comentários, ou só dizer um Olá – + envie pela nossa comunidade Telegram em https://t.me/loomnetwork + */ +} +``` + +Em particular, é uma boa prática comentar o seu código para explicar o comportamento esperado de cada função em seu contrato. Desta maneira outro desenvolvedor (ou você, após seis meses de hiato de um projeto!) pode rapidamente ler e entender em alto nível o que seu código faz sem ter que ler o próprio código. + +O padrão na comunidade Solidity é usar o formato chamado **_natspec_**, que se parece com isso: + +``` +/// @title Um contrato para operações básicas de matemática +/// @author H4XF13LD MORRIS 💯💯😎💯💯 +/// @notice No momento, este contrato somente adiciona uma função de multiplicação +contract Math { + /// @notice Multiplica dois números juntos + /// @param x o primeiro uint. + /// @param y o segundo uint. + /// @return z o produto de (x * y) + /// @dev Esta função não atualmente não checa por overflows + function multiply(uint x, uint y) returns (uint z) { + // Este é somente um comentário normal, e não sera lido pelo natspec + z = x * y; + } +} +``` + +`@title` e `@author` são simples. + +`@notice` explica para o **usuário** o que o contrato / função faz. `@dev` é para explicar detalhes extras para os desenvolvedores. + +`@param` e `@return` são para descrever o que cada parâmetro e valor de retorno da função fazem. + +Note que você não precisa ter que usar todas essas tags para cada função - todas as tags são opcionais. Mas pelo menos, deixa uma nota com `@dev` explicando o que cada função faz. + +# Vamos testar + +Se você ainda não notou, o verificador de respostas do CryptoZombies ignora comentários quando verifica suas respostas. Então não precisamos verificar seu código natspec para este capítulo ;) + +No entanto, aqui você já é um bruxo do Solidity – vamos assumir que você entendeu isto! + +De uma chance, e tente adicionar alguma tag natspec no `ZombieOwnership`: + +1. `@title` — Ex: Um contrato que gerencia a transferência de propriedade do zumbi + +2. `@author` — Seu nome! + +3. `@dev` — Ex: Em conformidade com a implementação do rascunho de especificação ERC721 do OpenZeppelin diff --git a/pt/5/14-wrappingitup.md b/pt/5/14-wrappingitup.md new file mode 100644 index 0000000000..1b9f61e4ba --- /dev/null +++ b/pt/5/14-wrappingitup.md @@ -0,0 +1,38 @@ +--- +title: Juntando Tudo +actions: ['verificarResposta', 'dicas'] +requireLogin: true +skipCheckAnswer: true +material: + saveZombie: false + zombieDeck: + zombie: + lesson: 5 + hideSliders: true + answer: 1 +--- + +Parabéns! Isso concluí a Lição 5. + +Como uma recompensa, nós transferimos pra você o seu próprio zumbi de Nível 10 **H4XF13LD MORRIS 💯💯😎💯💯** + +(Meu deus, o lendário zumbi **H4XF13LD MORRIS 💯💯😎💯💯** !!!!111) + +Agora você tem 4 zumbis em seu exército. + +Antes de você ir, você tem a opção de renomear qualquer qualquer um, se você clicar neles a direita e entrar um novo nome. (Mas eu não sei por que você iria querer renomear **H4XF13LD MORRIS 💯💯😎💯💯**, sem dúvida o melhor nome já visto). + +## Recapitulando: + +Nesta lição aprendemos sobre: + +- Tokens, o padrão ERC721, e ativos/zumbis cambiáveis +- Bibliotecas e como usá-las +- Como prevenir overflows e underflows usando a biblioteca SafeMath +- Comentar o seu código e padrão natspec + +Esta lição conclui o código do nosso jogo em Solidity! (No momento – podemos adicionar ainda mais lições no futuro). + +Nas próximas duas lições, iremos ver como em como implantar os seus contratos e interagir com eles usando **_web3.j_** (então você pode criar um front-end para a sua DApp). + +Vá em frente e renomeie qualquer um dos seus zumbis se você quiser, então siga para o próximo capítulo para completar a lição. diff --git a/pt/5/15-lessoncomplete.md b/pt/5/15-lessoncomplete.md new file mode 100644 index 0000000000..4f6718382a --- /dev/null +++ b/pt/5/15-lessoncomplete.md @@ -0,0 +1,7 @@ +--- +title: Lesson 5 Completa! +actions: ['verificarResposta', 'dicas'] +material: + lessonComplete: + answer: 1 +--- diff --git a/pt/index.json b/pt/index.json index b3032b097c..4cac6faf44 100644 --- a/pt/index.json +++ b/pt/index.json @@ -115,6 +115,22 @@ ], "zombieDesc": "Um CryptoZombie de Nível {levelNum} ", "shareLinkText": "Eu acabei de completar a Lição 4 do #CryptoZombies! e derrotei o maligno zumbi IOTA! tente você também:" + }, + "lesson5": { + "achievementsUnlocked": "Conquistas Desbloqueadas:", + "newZombieAdded": "Você recebeu {zombieName}, um CryptoZombie de nível {levelNum}!", + "header": [ + "Parabéns! Você completou a {lesson5} do CryptoZombies!", + "Mostre o seu exército de zumbis para os seus amigos!", + "Compartilhe esta URL para que os seus amigos vejam todo o seu exército zumbi:" + ], + "footer": [ + "Lição 6 dentro de poucas semanas!", + "Você receberá um email nosso asim que estiver pronta.", + "Enquanto isso, junte-se a nós no {telegramLink}, ou siga-nos em nosso {twitterLink} para juntar-se a conversa!" + ], + "zombieDesc": "Um CryptoZombie de Nível {levelNum}", + "shareLinkText": "I just completed #CryptoZombies Lesson 5 and got a level 10 CryptoZombie! Olha só o meu exército de zumbi:" } }, "zombieBattle": { @@ -224,8 +240,26 @@ "E mais!" ], "callToAction": [ - "Want to build your own CryptoZombie army and join the ranks?", - "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" + "Quer criar o seu próprio CryptoZombie e juntar-se a classificação?", + "Aprenda a criar os seus próprios jogos em Ethereum DE GRAÇA com CryptoZombies! Comece agora:" + ] + }, + "lesson5": { + "zombieDesc": "{name} CryptoZombie", + "achievementPreamble": [ + "O seu amigo completou a Lição 5 do CryptoZombies, e recebeu um CryptoZombie de Nível 10!", + "Olhá só o exército zumbi do seu amigo. Muito bom, né?", + "Para completar a Lição 5 do CryptoZombies, seu amigo aprendeu sobre:" + ], + "achievements": [ + "Tokens, o padrão ERC721, e a criação de ativos/zumbis cambiáveis", + "Bibliotecas e como usá-las", + "A biblioteca SafeMath e prevenção de overflows e underflows em uint", + "Comentar o seu código Solidity e o padrão natspec" + ], + "callToAction": [ + "Quer criar o seu próprio CryptoZombie e juntar-se a classificação?", + "Aprenda a criar os seus próprios jogos em Ethereum DE GRAÇA com CryptoZombies! Comece agora:" ] } } diff --git a/pt/index.ts b/pt/index.ts index 9d22c5346b..1dc2dfcd45 100644 --- a/pt/index.ts +++ b/pt/index.ts @@ -70,6 +70,24 @@ import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' +// lesson5 +import l5_overview from './5/00-overview.md' +import l5_ch1 from './5/01-erc721-1.md' +import l5_ch2 from './5/02-erc721-2.md' +import l5_ch3 from './5/03-erc721-3.md' +import l5_ch4 from './5/04-erc721-4.md' +import l5_ch5 from './5/05-erc721-5.md' +import l5_ch6 from './5/06-erc721-6.md' +import l5_ch7 from './5/07-erc721-7.md' +import l5_ch8 from './5/08-erc721-8.md' +import l5_ch9 from './5/09-safemath-1.md' +import l5_ch10 from './5/10-safemath-2.md' +import l5_ch11 from './5/11-safemath-3.md' +import l5_ch12 from './5/12-safemath-4.md' +import l5_ch13 from './5/13-comments.md' +import l5_ch14 from './5/14-wrappingitup.md' +import l5_complete from './5/15-lessoncomplete.md' + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -142,5 +160,23 @@ export default { l4_ch9, l4_ch10, l4_complete - ] + ], + 5: [ + l5_overview, + l5_ch1, + l5_ch2, + l5_ch3, + l5_ch4, + l5_ch5, + l5_ch6, + l5_ch7, + l5_ch8, + l5_ch9, + l5_ch10, + l5_ch11, + l5_ch12, + l5_ch13, + l5_ch14, + l5_complete + ], } diff --git a/pt/share-template-msgs.json b/pt/share-template-msgs.json index 65f1cdcdab..463453cee4 100644 --- a/pt/share-template-msgs.json +++ b/pt/share-template-msgs.json @@ -14,5 +14,9 @@ "4": { "pageTitle": "Meu CryptoZombie derrotou o maligno zumbi IOTA!", "ogDesc": "Eu acabei de completar a Lição 4 do CryptoZombies! e derrotei o maligno zumbi IOTA! Venha reviver a batalha..." + }, + "5": { + "pageTitle": "Meu Exército CryptoZombie Cresceu!", + "ogDesc": "Eu acabei de completar a Lição 5 do CryptoZombies! e consegui um zumbi de nível 10 H4XF13LD MORRIS 💯💯😎💯💯 demais!" } } diff --git a/ru/1/functions.md b/ru/1/functions.md index e4dd203184..5bb216f19c 100644 --- a/ru/1/functions.md +++ b/ru/1/functions.md @@ -67,6 +67,6 @@ eatHamburgers("vitalik", 100); Чтобы начать производить зомби, зададим функцию. -1. Создай функцию под названием `createZombie` (создать зомби), которая берет 2 параметра: **__имя_** (строку `string`) и **__ДНК_** (`uint`). +1. Создай функцию под названием `createZombie` (создать зомби), которая берет 2 параметра: **__name_** (имя, строка `string`) и **__dna_** (ДНК, тип `uint`). Пока оставь тело функции пустым, мы заполним его позже. diff --git a/ru/1/keccak256.md b/ru/1/keccak256.md index 42960a1b76..96e42957ea 100644 --- a/ru/1/keccak256.md +++ b/ru/1/keccak256.md @@ -71,7 +71,7 @@ keccak256("aaaab"); keccak256("aaaac"); ``` -Как видишь, функция возвращает абсолютно другое значение, хотя мы изменили всего одну входную цифру. +Как видишь, функция возвращает абсолютно другое значение, хотя мы изменили всего одну входную букву. > Примечание: в блокчейне остро стоит проблема генерации **безопасных** случайных чисел. Приведенный нами метод небезопасен, но для текущей задачи годится, поскольку безопасность не входит в приоритетные задачи ДНК зомби. diff --git a/ru/4/00-overview.md b/ru/4/00-overview.md index f903acab67..86ca3cfe63 100644 --- a/ru/4/00-overview.md +++ b/ru/4/00-overview.md @@ -10,4 +10,4 @@ roadmap: roadmap3.png Битвы зомби — развлечение не для слабонервных... -В этом уроке мы объединим все изученные в предыдущих главах концепции и создадим функцию зомби-битв. А еще разберемся с функциями `payable` и узнаем, как наделить DApps способностью получать деньги от игроков. +В этом уроке мы объединим все изученные в предыдущих главах концепции и создадим функцию зомби-битв. А еще разберемся с функциями **payable** и узнаем, как наделить DApps способностью получать деньги от игроков. diff --git a/ru/4/battle-02.md b/ru/4/battle-02.md index f3f922256a..f168fc371b 100644 --- a/ru/4/battle-02.md +++ b/ru/4/battle-02.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -203,6 +205,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/ru/4/battle-03.md b/ru/4/battle-03.md index 2981ff6b8f..f025608c04 100644 --- a/ru/4/battle-03.md +++ b/ru/4/battle-03.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -211,6 +213,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/ru/4/battle-06.md b/ru/4/battle-06.md index 13d5d99150..cac1b62777 100644 --- a/ru/4/battle-06.md +++ b/ru/4/battle-06.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -211,6 +213,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/ru/4/battle-08.md b/ru/4/battle-08.md index 9a1d4d8379..aeb980f637 100644 --- a/ru/4/battle-08.md +++ b/ru/4/battle-08.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -213,6 +215,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/ru/4/battle-09.md b/ru/4/battle-09.md index 17b567aab0..805957f80d 100644 --- a/ru/4/battle-09.md +++ b/ru/4/battle-09.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -218,6 +220,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/ru/index.ts b/ru/index.ts index 177f4cf28d..eed3f8111d 100644 --- a/ru/index.ts +++ b/ru/index.ts @@ -54,6 +54,22 @@ import l3_ch12 from './3/12-forloops.md' import l3_ch13 from './3/13-wrappingitup.md' import l3_complete from './3/14-lessoncomplete.md' +// lesson4 +import l4_overview from './4/00-overview.md' +import l4_payable from './4/payable.md' +import l4_withdraw from './4/withdraw.md' +import l4_ch1 from './4/battle-01.md' +import l4_ch2 from './4/battle-02.md' +import l4_ch3 from './4/battle-03.md' +import l4_ch4 from './4/battle-04.md' +import l4_ch5 from './4/battle-05.md' +import l4_ch6 from './4/battle-06.md' +import l4_ch7 from './4/battle-07.md' +import l4_ch8 from './4/battle-08.md' +import l4_ch9 from './4/battle-09.md' +import l4_ch10 from './4/wrappingitup.md' +import l4_complete from './4/lessoncomplete.md' + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -110,5 +126,21 @@ export default { l3_ch12, l3_ch13, l3_complete - ] + ]/*, + 4: [ + l4_overview, + l4_payable, + l4_withdraw, + l4_ch1, + l4_ch2, + l4_ch3, + l4_ch4, + l4_ch5, + l4_ch6, + l4_ch7, + l4_ch8, + l4_ch9, + l4_ch10, + l4_complete + ]*/ } diff --git a/th/index.ts b/th/index.ts index 0708ec76ed..255ac56cb8 100644 --- a/th/index.ts +++ b/th/index.ts @@ -36,7 +36,7 @@ import multiplereturns from './2/12-multiplereturns.md' import kittygenes from './2/13-kittygenes.md' import wrappingitup from './2/14-wrappingitup.md' import lesson2complete from './2/15-lessoncomplete.md' -/* + // lesson3 import l3_overview from './3/00-overview.md' import l3_ch1 from './3/01-externaldependencies.md' @@ -53,7 +53,7 @@ import l3_ch11 from './3/11-savinggasstorage.md' import l3_ch12 from './3/12-forloops.md' import l3_ch13 from './3/13-wrappingitup.md' import l3_complete from './3/14-lessoncomplete.md' -*/ + // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -93,7 +93,7 @@ export default { kittygenes, wrappingitup, lesson2complete - ]/*, + ], 3: [ l3_overview, l3_ch1, @@ -110,5 +110,5 @@ export default { l3_ch12, l3_ch13, l3_complete - ]*/ + ] } diff --git a/tr/2/00-overview.md b/tr/2/00-overview.md new file mode 100644 index 0000000000..c629bcdcca --- /dev/null +++ b/tr/2/00-overview.md @@ -0,0 +1,12 @@ +--- +title: Zombiler Kurbanlarına Saldırır +header: Yani, bunu Ders 2'de yaptın! +roadmap: roadmap2.jpg +--- + +Etkileyici, insan! Düşündüğümden daha iyi bir kodlayıcısın. + +Ders 2 size **zombi ordunuzun diğer yaşam formlarında beslenerek nasıl çoğalacağını** öğretecek. + +Bu derste daha gelişmiş Solidity kavramlarını ele alacağız, bu nedenle başlamadan +önce Ders 1'i tamamlamanız kesinlikle önerilir. diff --git a/tr/2/1-overview.md b/tr/2/1-overview.md new file mode 100644 index 0000000000..a97c38e8ca --- /dev/null +++ b/tr/2/1-overview.md @@ -0,0 +1,34 @@ +--- +title: Ders 2 Genel Bakış +actions: ['cevapKontrol', 'ipuçları'] +material: + saveZombie: false + zombieBattle: + zombie: + lesson: 1 + humanBattle: true + ignoreZombieCache: true + answer: 1 +--- + +Ders 1'de, bir isim alan, rastgele bir zombi türetmek için onu kullanan ve bu zombiyi blok zincirindeki uygulamamızın zombi veritabanına ekleyen bir fonksiyon oluşturduk. + +Ders 2'de, uygulamamızı biraz daha oyun gibi yapacağız: Onu çok oyunculu yapacağız ve sadece onları rastgele türetmek yerine zombileri oluşturmak için daha eğlenceli bir yol da ekliyor olacağız. + +Nasıl yeni zombiler oluşturacağız? Zombilerimizi diğer yaşam formlarında "besleyerek"! + +## Zombi Beslemek + +Bir zombi beslendiğinde, bir virüs ile host bulaştırır. Virüs daha sonra hostu, ordunuza katılan yeni bir zombi haline getirir. Yeni zombinin DNA'sı önceki zombinin DNA'sı ve hostun DNA'sından hesaplanır. + +Ve zombilerimiz en fazla neyle beslenmeyi sever? + +Bunu bulmak içib... Ders 2'yi tamamlamanız gerekecek! + +# Teste koy + +Doğru beslemek için basit bir demo var. Zombiniz beslendiğinde ne olduğunu görmek için bir insana tıkla! + +Yeni zombi DNA'sının hostun DNA'sından olduğu gibi orijinal zombinizin DNA'sından da belirlendiğini görebilirsiniz. + +Hazır olduğunuzda, ilerlemek için "Sonraki bölüm"e tıklayın ve oyunumuzu çoklu oyunculu yaparak başlatalım. diff --git a/tr/2/10-interactingcontracts.md b/tr/2/10-interactingcontracts.md new file mode 100644 index 0000000000..5b228c5a88 --- /dev/null +++ b/tr/2/10-interactingcontracts.md @@ -0,0 +1,193 @@ +--- +title: Zombiler Ne Yer? +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + // Burada KittyInterface oluştur + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) internal { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +Zombilerimizi besleme zamanı! Ve zombilerimiz en fazla ne yemeyi sever? + +Peki bu sadece CryptoZombie'lerin yemeyi sevmesi olur... + +**CryptoKitties!** 😱😱😱 + +(Evet, Ciddiyim 😆 ) + +Bunu yapmak için CryptoKitties akıllı kontratından kittyDna'yı okumamız gerekecek. CryptoKitties verileri blok zincirinde açık olarak depolandığından bunu yapabiliriz. Blok zinciri çok iyi değil mi?! + +Endişelenme — oyunumuz aslında herhangi birinin CryptoKitty'sine zarar vermeyecek. Biz sadece CryptoKitties vereilerini *okuyoruz*, aslında onu silemeyiz 😉 + +## Diğer kontratlarla etkileşime girmek + +Blok zincirinde sahip olmadığımız başka bir kontratla kontratımızın görüşmesi için, ilk olarak bir **_arayüz_** belirlememiz gerek. + +Bir basit örneği inceleyelim. Blok zincirinde bunun gibi bir kontratın olduğunu farz edelim: + +``` +contract LuckyNumber { + mapping(address => uint) numbers; + + function setNum(uint _num) public { + numbers[msg.sender] = _num; + } + + function getNum(address _myAddress) public view returns (uint) { + return numbers[_myAddress]; + } +} +``` + +Bu, herhangi birinin şanslı numarasını depolayabileceği basit bir kontrat olurdu ve onların Ethereum adresleriyle ilişkili olacaktı. Daha sonra başka biri onların adresini kullanarak kişinin şanslı numarasını arayabilirdi. + +Şimdi `getNum` fonksiyonunu kullanarak bu kontratta verileri okumak istediğimiz harici bir kontrat yaptığımızı farz edelim. + +İlk olarak `LuckyNumber` kontratın bir **_arayüzünü_** tanımlamamız gerekirdi: + +``` +contract NumberInterface { + function getNum(address _myAddress) public view returns (uint); +} +``` + +Bunun birkaç değişiklik ile bir kontrat tanımlıyor gibi göründüğüne dikkat edin. For one, Bilhassa, sadece etkileşime sokacağımız fonksiyonları bildiriyoruz — bu durumda `getNum` — ve diğer fonksyonların herhangi birinden ve durum değişkenlerinden söz etmiyoruz. + +İkinci olarak, fonksiyon gövdesini tanımlamıyoruz. Kıvırcık parantez yerine (`{` ve `}`), fonksiyon tanımını basitçe bir noktalı virgül (`;`) ile bitiriyoruz. + +Yani bir tür kontrat iskeleti gibi görünüyor. Bu, derleyicinin onun bir arayüz olduğunu nasıl tanıyacağıdır. + +Bu arayüzü dapp's kodumuza ekleyerek our kontratımız diğer kontratın fonksiyonunun ne gibi gözüktüğünü, onları nasıl çağıracağını, beklemek için yanıt türünü bilir. + +Aslında diğer kontratın fonksiyonlarını çağırmayı diğer derste öğreneceğiz fakat şimdilik CryptoKitties kontratı için arayüzümüzü bildirelim. + +# Teste koy + +CryptoKitties kaynak kodunu sizin için araştırdık ve "genler" (zombi oyunumuzun yeni bir zombi oluşturmak için ihtiyacı olan şey!) dahil tüm kitty'nin verilerini getiren `getKitty` denilen bir fonksiyon bulduk. + +Fonksiyon şunun gibi gözüküyor: + +``` +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 +) { + Kitty storage kit = kitties[_id]; + + // if this variable is 0 then it's not gestating + isGestating = (kit.siringWithId != 0); + isReady = (kit.cooldownEndBlock <= block.number); + cooldownIndex = uint256(kit.cooldownIndex); + nextActionAt = uint256(kit.cooldownEndBlock); + siringWithId = uint256(kit.siringWithId); + birthTime = uint256(kit.birthTime); + matronId = uint256(kit.matronId); + sireId = uint256(kit.sireId); + generation = uint256(kit.generation); + genes = kit.genes; +} +``` + +Fonksiyon alışık olduğumuzdan biraz farklı gözüküyor. Bir grup farklı değer getirdiğini... görebilirsiniz. Javascript gibi bir programlama dilinden geliyorsanız, bu farklıdır — Solidity'de bir fonksiyondan birden fazla değer getirebilirsiniz. + +Şimdi bu foksiyonun nasıl göründüğünü biliyoruz, onu bir arayüz oluşturmak için kullanabiliriz: + +1. `KittyInterface` denilen bir arayüz belirleyin. Hatırla, bu yeni bir kontrat oluşturmak gibidir — `contract` anahtar kelimesini kullanırız. + +2. Arayüz içinde, `getKitty` fonksiyonu tanımlayın (kıvırcık parantezler içindeki herşeyin yerine, yukardaki fonksiyonun bir kopyala/yapıştırı olan fakat `returns` ifadesinden sonra bir noktalı virgül ile. diff --git a/tr/2/11-interactingcontracts2.md b/tr/2/11-interactingcontracts2.md new file mode 100644 index 0000000000..c6dac31744 --- /dev/null +++ b/tr/2/11-interactingcontracts2.md @@ -0,0 +1,148 @@ +--- +title: Bir Arayüz Kullanımı +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + // Initialize kittyContract here using `ckAddress` from above + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) internal { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +Önceki örneğimiz `NumberInterface` ile devam ediyor, arayüzü aşu şekilde tanımladığımızda: + +``` +contract NumberInterface { + function getNum(address _myAddress) public view returns (uint); +} +``` + +Aşağıdaki gibi onu bir kontratta kullanabiliriz: + +``` +contract MyContract { + address NumberInterfaceAddress = 0xab38... + // ^ The address of the FavoriteNumber contract on Ethereum + NumberInterface numberContract = NumberInterface(NumberInterfaceAddress) + // Now `numberContract` is pointing to the other contract + + function someFunction() public { + // Now we can call `getNum` from that contract: + uint num = numberContract.getNum(msg.sender); + // ...and do something with `num` here + } +} +``` + +Böylece, bu fonksiyonu `public` veya `external` olarak ortaya koydukları sürece kontratınız Ethereum blok zincirinde herhangi bir kontratile etkileşime girebilir. + +# Teste koy + +Kontratımızı CryptoKitties akıllı kontrattan okumak için ayarlayalım! + +1. `ckAddress` isimli bir değişken altında kod içinde CryptoKitties kontratın adresini sizin için kaydettim. Sonraki satırda, `kittyContract` isimli bir `KittyInterface` oluşturun ve onu `ckAddress` ile başlatın — yukarıda `numberContract` ile yaptığımız gibi. diff --git a/tr/2/12-multiplereturns.md b/tr/2/12-multiplereturns.md new file mode 100644 index 0000000000..fb7338de4e --- /dev/null +++ b/tr/2/12-multiplereturns.md @@ -0,0 +1,162 @@ +--- +title: Çoklu Değişken Getirme Kullanımı +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + // define function here + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) internal { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + 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 { + + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + function feedOnKitty(uint _zombieId, uint _kittyId) public { + uint kittyDna; + (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); + feedAndMultiply(_zombieId, kittyDna); + } + + } +--- + +Bu `getKitty` fonksiyonu çoklu değişkenlerin getirilmesini gördüğümüz ilk örnek. Nasıl kullanıldığına bir bakalım: + +``` +function multipleReturns() internal returns(uint a, uint b, uint c) { + return (1, 2, 3); +} + +function processMultipleReturns() external { + uint a; + uint b; + uint c; + // This is how you do multiple assignment: + (a, b, c) = multipleReturns(); +} + +// Or if we only cared about one of the values: +function getLastReturnValue() external { + uint c; + // We can just leave the other fields blank: + (,,c) = multipleReturns(); +} +``` + +# Teste koy + +CryptoKitties kontrat ile etkileşim zamanı! + +Kontrattan kitty genlerini alan bir fonksiyon yapalım: + +1. `feedOnKitty` denilen bir fonksiyon yapın. 2 `uint` parametresi alacak, `_zombieId` ve `_kittyId` ve bir `public` fonksiyon olması lazım. + +2. Fonksiyon ilk olarak `kittyDna` isimli bir `uint` ifade etmeli. + + > Dikkat: `KittyInterface`'mizde, `genes` bir `uint256`'dir — fakat ders 1'de hatırlarsanız, `uint` `uint256` için bir takma isimdir — onlar aynı şeydir. + +3. Fonksiyon daha sonra `_kittyId` ile `kittyContract.getKitty` fonksiyonunu çağırmalı ve `genes`i `kittyDna`'da depolamalı. Hatırlayın — `getKitty` defalarca değişken getirir. (Tam olarak 10 — İyiyim, senin için saydım!). Fakat önemsediğimiz sonuncusu, `genes`. Virgüllerini dikkatli say! + +4. Son olarak, fonksiyon `feedAndMultiply`'i çağırmalı ve `_zombieId` ve `kittyDna`nın ikisine de geçmeli. diff --git a/en/5/comments.md b/tr/2/13-kittygenes.md similarity index 58% rename from en/5/comments.md rename to tr/2/13-kittygenes.md index 393b55ebb6..bd46ea304d 100644 --- a/en/5/comments.md +++ b/tr/2/13-kittygenes.md @@ -1,7 +1,6 @@ --- -title: Comments +title: "Bonus: Kitty Genes" actions: ['checkAnswer', 'hints'] -requireLogin: true material: editor: language: sol @@ -28,28 +27,24 @@ material: contract ZombieFeeding is ZombieFactory { - // 1. Remove this: address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; - // 2. Change this to just a declaration: KittyInterface kittyContract = KittyInterface(ckAddress); - // 3. Add setKittyContractAddress method here - - function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public { + // Modify function definition here: + function feedAndMultiply(uint _zombieId, uint _targetDna) public { require(msg.sender == zombieToOwner[_zombieId]); Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; - if (keccak256(_species) == keccak256("kitty")) { - newDna = newDna - newDna % 100 + 99; - } + // Add an if statement here _createZombie("NoName", newDna); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); - feedAndMultiply(_zombieId, kittyDna, "kitty"); + // And modify function call here: + feedAndMultiply(_zombieId, kittyDna); } } @@ -115,11 +110,8 @@ material: contract ZombieFeeding is ZombieFactory { - KittyInterface kittyContract; - - function setKittyContractAddress(address _address) external { - kittyContract = KittyInterface(_address); - } + address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; + KittyInterface kittyContract = KittyInterface(ckAddress); function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public { require(msg.sender == zombieToOwner[_zombieId]); @@ -141,46 +133,40 @@ material: } --- -Solidity, or BlockChain programming in general, has alot in common with the kind of programming done for the aerospace industry. That is, if you make a mistake, the plane falls from the sky, the rockets can't reach the moon, and [all your money gets stuck forever](https://medium.com/chain-cloud-company-blog/parity-multisig-hack-again-b46771eaa838). +Fonksiyon mantığımız şimdi tamam... fakat bir bonus özellik daha ekleyelim. + +Onu, kittie'lerden yapılan zombilerin cat-zombieler olduğunu gösteren bir benzersiz özelliğe sahip olacak şekilde yapalım. + +Bunu yapmak için, zombinin DNA'sına özel bir kitty kodu ekleyebiliriz. + +Ders 1'den hatırlarsanız, şu anda zombi görünümünü belirlemek için 16 haneli DNA'mızın ilk 12 basamağını kullanıyoruz. O halde "özel" karakterleri kullanmak için kullanılmayan son 2 basamağı kullanalım. -So it becomes really really important that you double, triple check everything and make every effort to clarify the meaning and the intention behind each line of code. +Cat-zombielerin DNA'sının son iki basamağının `99` olduğunu farz edeceğiz. (kedilerin 9 canı olduğundan). Yani kodumuzda, bir zombi bir kediden geliyorsa `if`, o zaman DNA'nın son iki basamağını `99` diyeceğiz. -This is why we are taking time to go over `comments` and their importance in Solidity programming. +## If ifadeleri -## Commenting with Style: +Solidity'de if ifadeleri javascriptteki gibidir: ``` -// This is a single-line comment. It's kind of like a note to self (or to others) +function eatBLT(string sandwich) public { + // Remember with strings, we have to compare their keccak256 hashes + // to check equality + if (keccak256(sandwich) == keccak256("BLT")) { + eat(); + } +} ``` -Just add double `//` anywhere and you're commenting. It's so easy that you should do it all the time. But I hear you, sometimes a single line is not enough, you are born a writer: +# Teste koy -``` -contract CryptoZombies { - /* - This is a multi-lined comment. I'd like to thank all of you who have taken your time to try this programming course. I know it's free to all of you, and it will stay free forever, but we still put our heart and soul into making this as good as it can be. +Hadi zombi kodumuzdaki kedi genlerini uygulayalım. - Know that this is still the beginning of Blockchain development. We've come very far but there are so many ways to make this community better. If we made a mistake somewhere, you can make a pull request here: https://github.com/loomnetwork/cryptozombie-lessons +1. Öncelikle, `feedAndMultiply` için fonksiyon tanımını değiştirelim böylece o 3. bir argüman alır: `_species` isimli bir `string` - Or if you have some ideas, comments, or just want to say hi - drop by our Telegram. - */ -} -``` +2. Daha sonra, yeni zombinin DNA'sını hesapladıktan sonra, `_species`'in `keccak256` hashlerini ve `"kitty"` dizisini karşılaştıran bir `if` ifadesi ekleyelim -But sometimes the written language is not precise enough, or you want to make it really easy for others to read, then it's time to add real documentation. +3. `if` ifadesinin içinde, DNA'nın son iki basamağını `99` ile değiştirmek istiyoruz. Bunu yapmanın bir yolu mantık kullanmak: `newDna = newDna - newDna % 100 + 99;`. -In this community, the general trend is to use `Doxygen` style tags. These tags are just a special way of writting comments that are clearer and machine readable. It makes it easier to generate documentation for others. + > Açıklama: Varsayalım `newDna` `334455`'tir. O zaman `newDna % 100` `55`'tir, yani `newDna - newDna % 100` `334400`'dır. Son olarak `334499` almak için `99` ekleyin. -``` -/** @title A basic math operation. */ -contract multiply { - /** - * @param x the first variable. - * @param y the second variable. - * @return z The answer. - */ - function multiply(uint x, uint y) returns (uint z) { - z = x * y; - } -} -``` \ No newline at end of file +4. Son olarak, `feedOnKitty` içinde fonksiyon çağrımını değiştirmemiz gerekiyor. `feedAndMultiply`'i çağırdığında, sonlandırmak için `"kitty"` parametresi ekler. diff --git a/tr/2/14-wrappingitup.md b/tr/2/14-wrappingitup.md new file mode 100644 index 0000000000..fdb2b802a9 --- /dev/null +++ b/tr/2/14-wrappingitup.md @@ -0,0 +1,65 @@ +--- +title: Kısa Kesmek +actions: ['cevapKontrol', 'ipuçları'] +material: + saveZombie: true + zombieBattle: + zombie: + lesson: 1 + humanBattle: false + hideSliders: true + answer: 1 +--- + +İşte bu kadar, ders 2'yi tamamladınız! + +Çalışmasını görmek için demoyu kontrol edebilirsiniz. Devam edin, Biliyorum bu sayfanın aşağısına kadar bekleyemiyorsunuz 😉. Saldırı için bir kitty'e tıklayın ve edindiğiniz yeni kitty zombieyi görün! + +## Javascript uygulaması + +Bu kontratı Ethereum'a açmaya hazır olduğumuzda `ZombieFeeding`'i derleyip yayacağız — bu kontrat `ZombieFactory`'den kalan son kontratımız olduğundan ve her iki kontratta da tüm genel yöntemlere erişimi olduğundan. + +Javascript ve web3.js kullanarak yayılan kontratımızın bir etkileşim örneğine bakalım: + +``` +var abi = /* abi derleyici tarafından oluşturuldu */ +var ZombieFeedingContract = web3.eth.contract(abi) +var contractAddress = /* dağıtımdan sonraki Ethereum'da kontrat adresimiz */ +var ZombieFeeding = ZombieFeedingContract.at(contractAddress) + +// Saldırmak istediğimiz zombi kimliğimiz ve kitty kimliğimiz olduğunu göz önünde tutuyoruz +let zombieId = 1; +let kittyId = 1; + +// CryptoKitty'nin görüntüsünü almak için, onların web API'sini sorgulamamız gerek. +// Bu bilgi blok zincirinde depılanmaz, sadece web sunucularında. +// Herşey bir blok zincirinde depolanmışsa, zombi oyunumuzu beğenmezlerse +// sunucunun çöküşünden, API'lerinin değiştirilmesinden yada varlıklarını +// yüklemekten dolayı şirketin bizi engellemesinden endişelenmemeliyiz ;) +let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId +$.get(apiUrl, function(data) { + let imgUrl = data.image_url + // resmi görüntülemek için birşey yap +}) + +// Kullanıcı bir kitty'e tıkladığında: +$(".kittyImage").click(function(e) { + // Kontratımızın `feedOnKitty` yöntemini çağır + ZombieFeeding.feedOnKitty(zombieId, kittyId) +}) + +// Kontratımızdan bir NewZombie etkinliği için dinle yani onu görüntüleyebiliriz: +ZombieFactory.NewZombie(function(error, result) { + if (error) return + // Bu fonksiyon ders 1'deki gibi zombiyi görüntüleyecek: + generateZombie(result.zombieId, result.name, result.dna) +}) +``` + +# Deneyin! + +Beslemek istediğiniz kitty'i seçin. Zombinin DNA'sı ve kittynin DNA'sı birleşecek ve ordunuzda yeni bir zombiniz olacak! + +Dikkat, bu sevimli kedi bacakları yeni zombizin mi? Bu çalışmada DNA'nın son `99` basamağımız 😉 + +İsterseniz başa dönebilir ve tekrar deneyebilirsiniz. Yeni bir kitty zombi edindiğinizde onunla mutlusunuzdur (sadece tutulan birini alırsınız), devam edin ve ders 2'yi tamamlamak için sonraki bölüme ilerleyin! diff --git a/tr/2/15-lessoncomplete.md b/tr/2/15-lessoncomplete.md new file mode 100644 index 0000000000..5c0d3bb0ed --- /dev/null +++ b/tr/2/15-lessoncomplete.md @@ -0,0 +1,8 @@ +--- +title: Ders 2 Tamamlandı! +actions: ['cevapKontrol', 'ipuçları'] +material: + lessonComplete: + answer: 1 +--- + diff --git a/tr/2/2-mappings.md b/tr/2/2-mappings.md new file mode 100644 index 0000000000..e356f3e24e --- /dev/null +++ b/tr/2/2-mappings.md @@ -0,0 +1,120 @@ +--- +title: Haritalandırma ve Adresler +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + 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; + + // declare mappings here + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } +--- + +Veritabanımızda zombiler vererek oyunumuzu çok oyunculu yapalım. + +To do this, Bunu yapmak için, 2 yeni veri türüne ihtiyacımı olacak: `mapping` ve `address`. + +## Addresler + +Ethereum blok zinciri banka hesapları gibi düşünebileceğiniz **_hesaplardan_**. Bir hesabın **_Ether_** (Ethereum blok zincirinde kullanılan para birimi) bakiyesi vardır ve banka hesabınızın diğer banka hespalarına elektronik para transferi yapabildiği gibi diğer hesaplara Ether ödemeleri gönderebilir ve alabilirsiniz. + +Her hesap bir banka hesap numarası gibi düşünebileceğiniz bir `address'`'e sahiptir. O, hesabı işaret eden özel bir tanımlayıcıdır ve bunun gibi görünür: + +`0x0cE446255506E92DF41614C46F1d6df9Cc969183` + +(Bu adres CryptoZombies takımına aittir. CryptoZombies ile eğleniyorsanız, bize biraz Ether gönderebilirsiniz! 😉 ) + +Sonraki bir derste adreslerin asıl meselesine geleceğiz fakat şimdilik sadece **bir adresin belirli birkullanıcıya ait olduğunu** anlamanız gerekiyor (veya bir akıllı kontrata). + +Yani, zombilerimizin sahipliği için onu bir özel kimlik olarak kullanabiliriz. Bir kullanıcı uygulamamızla etkileşerek yeni zombiler oluşturduğunda, fonksiyon olarak çağrulan Ethereum adresine bu zombilerin sahipliğini ayarlayacağız. + +## Haritalandırmalar + +Ders 1'de **_yapılar_** ve **_sıralamalar_**'a baktık. **_Haritalandırmalar_** Solidiy'de organize edilen verilerin depolanmasının başka bir yoludur. + +Bunun gibi bir `mapping` tanımlamak: + +``` +// Bir finansal uygulama için, kullanıcının hesap bakiyesini tutan bir uint depolamak: +mapping (address => uint) public accountBalance; +// userId'ye bağlı kullanıcı adlarını depolamak/ aramak için kullanılabilir +mapping (uint => string) userIdToName; +``` + +Bir haritalandırma özellikle veri depolamak ve aramak için bir anahtar değerdir. İlk örnekte, anahtar bir `address`'tir ve değer bir `uint`'tir ve ikinci örnekte anahtar bir `uint` ve değer bir `string`'tir. + +# Teste koy + +Zombi sahipliği depolamak için, iki haritalandırma kullanacağız: biri sahibi onunan zombinin adresinin izini sürdürür ve diğeri sahibin kaç zombisinin olduğunun izini takip eder. + +1. `zombieToOwner` denilen bir haritalandırma oluşturun. Anahtar bir `uint` (kendi kimliğine bağlı zombiyi depolayacağız ve araştıracağız) ve bir `address` değeri olacak. Bu haritalandırmayı `public` yapalım. + +2. Anahtarın bir `address` ve bir `uint` değeri olduğu `ownerZombieCount` denilen bir haritalandırma oluşturun. diff --git a/tr/2/3-msgsender.md b/tr/2/3-msgsender.md new file mode 100644 index 0000000000..4cb685bd55 --- /dev/null +++ b/tr/2/3-msgsender.md @@ -0,0 +1,133 @@ +--- +title: Msg.sender +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 1; + // start here + 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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 { + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } +--- + +Artık kimin bir zombi sahibi olduğunun izlenmesini sürdürmek için haritalandırmamız var, onları kullanmak için `_createZombie` yöntemini güncellemek isteyeceğiz. + +Bunu yapmak için, `msg.sender` denilen bir şeyi kullanmamız gerek. + +## msg.sender + +Solidity'de, tüm fonksiyonlarda mevcut olan belli global değişkenler vardır. Bunlardan biri geçerli fonksiyonu çağıran kişinin (veya akıllı kontratın) `address`'ine başvuran `msg.sender`'dir. + +> Dikkat: Solidity'de, fonksiyon uygulaması her zaman harici bir çağırıcı ile başlatmaya ihtiyaç duyar. Bir kontrat birisi onun fonksiyonlarından birini çağırana kadar hiç birşey yapmadan blok zincirinde bulunacaktır. Yani her zaman bi `msg.sender` olacaktır. + +`msg.sender` kullanımının ve bir `mapping` güncellemenin bir örneği buradadır: + +``` +mapping (address => uint) favoriteNumber; + +function setMyNumber(uint _myNumber) public { + // Update our `favoriteNumber` mapping to store `_myNumber` under `msg.sender` + favoriteNumber[msg.sender] = _myNumber; + // ^ The syntax for storing data in a mapping is just like with arrays +} + +function whatIsMyNumber() public view returns (uint) { + // Retrieve the value stored in the sender's address + // Will be `0` if the sender hasn't called `setMyNumber` yet + return favoriteNumber[msg.sender]; +} +``` + +Bu saçma örnekte, her hangi biri kontratımıza kendi adreslerine bağlanabilecek `setMyNumber` çağırabilir ve bir `uint` depolayabilirdi. Sonra `whatIsMyNumber`'i çağırdıklarında depoladıkları `uint`'i çağırırlardı. + +`msg.sender` kullanmak size Ethereum blok zinciri güvenliğini verir — birinin başka birisinin verilerinin değiştirmesinin tek yolu onların Ethereum adresi ile ilişkili özel anahtarını çalmak olurdu. + +# Teste koy + +Fonksiyonu çağırana zombi sahipliği atamak için ders 1'den `_createZombie` yöntemimizi güncelleyelim. + +1. Önce, yeni zombinin `id`'sini geri aldıktan sonra, `msg.sender`i bu `id` altında depolamak için `zombieToOwner` haritalandırmamızı güncelleyelim. + +2. İkinci olarak, bu `msg.sender` için `ownerZombieCount`'i arttıralım. + +Solidity'de, javascriptteki gibi bir `uint`'i `++` ile arttırabilirsiniz: + +``` +uint number = 0; +number++; +// `number` şimdi `1` +``` + +Bu bölüm için son cevabınız iki satır kod olmalı. diff --git a/tr/2/4-require.md b/tr/2/4-require.md new file mode 100644 index 0000000000..f23a4b9d31 --- /dev/null +++ b/tr/2/4-require.md @@ -0,0 +1,118 @@ +--- +title: İstek +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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 { + // start here + uint randDna = _generateRandomDna(_name); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } +--- + +Ders 1'de, onu kullanıcıların `createRandomZombie` çağırarak ve bir isim girerek oluşturabileceği şekilde yaptık. Ancak, kullanıcılar ordularında sınırsız zombi oluşturmak için bu fonksiyonu çağırmayı sürdürebilseydi, oyun çok eğlenceli olmazdı. + +Onu, her oyuncunun bu fonksiyonu bir kez çağırabileceği şekilde yapalım. Bu yolla yeni oyuncular ordularındaki ilk zombiyi oluşturmak için oyunu ilk başlattıklarında onu çağıracaklar. + +Bu fonksiyonu oyuncu başına sadece bir kez çağrılabileceği şekilde nasıl yaparız? + +Bunun için `require` kullanırız. `require` onu bir şart doğru değilse fonksiyonun hata fırlatacağı ve uygulamayı durduracağı şekilde yapar: +``` +function sayHiToVitalik(string _name) public returns (string) { + // Compares if _name equals "Vitalik". Throws an error and exits if not true. + // (Side note: Solidity doesn't have native string comparison, so we + // compare their keccak256 hashes to see if the strings are equal) + require(keccak256(_name) == keccak256("Vitalik")); + // If it's true, proceed with the function: + return "Hi!"; +} +``` + +Bu fonksiyonu `sayHiToVitalik("Vitalik")` ile çağırırsanız, "Merhaba!" getirecektir. Onu herhangi bir diğer giriş ile çağırırsanız, bir hata çıkaracak ve uygulamayacaktır. + +Böylelikle `require`, bir fonksiyonun çalışmasından önce doğru olması gereken belirli koşulları doğrulamak için oldukça kullanışlıdır. + +# Teste koy + +Zombi oyunumuzda, bir kullanıcının aralıksız olarak `createRandomZombie` çağırmasıyla zombi ordularında sınırsız zombi oluşturabilmelerini istemiyoruz — bu oyunu daha eğlenceli yapmaz. + +İlk zombilerini oluşturduklarında, bu fonksiyonun kullanıcı başına bir kere uygulandığından emin olmak için `require` kullanalım. + +1. `createRandomZombie` başlangıcında bir `require` koşulu koyun. Fonksiyon `ownerZombieCount[msg.sender]`'in `0`'a eşit olduğundan emin olmak için denetlemeli ve aksi takdirde hata çıkarır. + +> Not: Solidity'de, ilk hangi terimi koyduğunuzun önemi yoktur — her iki sıra da eşdeğerdir. Ancak, cevap tarayıcımız gayet temel olduğundan, sadece bir cevabı doğru olarak kabul edecektir — `ownerZombieCount[msg.sender]`'in ilk gelmesi için bekliyor. diff --git a/tr/2/5-inheritance.md b/tr/2/5-inheritance.md new file mode 100644 index 0000000000..7302f17247 --- /dev/null +++ b/tr/2/5-inheritance.md @@ -0,0 +1,122 @@ +--- +title: Miras +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + + // Buradan başlayın + + answer: > + pragma solidity ^0.4.19; + + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + + contract ZombieFeeding is ZombieFactory { + + } + +--- + +Oyun kodumuz oldukça uzun. Son derece uzun bir kontrat yapmaktansa, bazen kodu organize etmek için kod mantığınızı çoğlu kontratlara bölmek onu anlamlı yapar. + +Bunu daha yönetilebilir yapan Solidity'nin bir özelliği de kontrat **_mirası_**: + +``` +contract Doge { + function catchphrase() public returns (string) { + return "So Wow CryptoDoge"; + } +} + +contract BabyDoge is Doge { + function anotherCatchphrase() public returns (string) { + return "Such Moon BabyDoge"; + } +} +``` + +`Doge`'den `BabyDoge` **_mirasları_**. Bu, `BabyDoge`'u derleyip açarsanız hem `catchphrase()` hem de `anotherCatchphrase()` erişimine sahip olacağı anlamına gelir (ve `Doge`'da belirtebileceğimiz her bir diğer genel fonksiyonlar). + +Bu mantıklı miras için kullanılılabilir (bir alt sınıfla olduğu gibi, bir `Cat` bir `Animal`'dır). Fakat ayrıca farklı sınıflar içine benzer mantığı birlikte gruplayarak kodunuzu organize etmek için basitçe kullanılabilir. + +# Teste koy + +Sonraki bölümlerde, zombilerimizi besleyip çoğaltmak için işlevselliği uyguluyor olacağız. Hadi `ZombieFactory`'den tüm yöntemleri miras alan kendi sahip olduğu sınıf içine bu mantığı koyalım. + +1. `ZombieFactory` altında `ZombieFeeding` denilen bir kontrat yapın. Bu kontrat `ZombieFactory` kontratımızdan miras alıyor olmalı. diff --git a/tr/2/6-importfiles.md b/tr/2/6-importfiles.md new file mode 100644 index 0000000000..8be24ae039 --- /dev/null +++ b/tr/2/6-importfiles.md @@ -0,0 +1,86 @@ +--- +title: Import +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + // put import statement here + + contract ZombieFeeding is ZombieFactory { + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + } + +--- + +Whoa! Doğrulamak için kodu bitirdiğimi fark edeceksin ve şimdi düzenleyicinizin üstünde sekmeleriniz var. Devam edin, denemek için sekmelerin arasına tıklayın. + +Kodumuz oldukça uzun sürdü, bu nedenle onu daha yönetilebilir yapmak için onu çoklu dosyalara ayırdık. Bu normalde Solidity projelerinizde uzun kod temellileri nasıl uygulayacağınızdır. + +Çoklu dosyalarınız olduğunda ve birini diğeri içine import etmek istediğinizde, Solidity `import` anahtar kelimesini kullanır: + +``` +import "./someothercontract.sol"; + +contract newContract is SomeOtherContract { + +} +``` + +Yani bu kontrat gibi aynı dizin içinde `someothercontract.sol` isimli bir dosyamız olsaydı (bu `./` anlamının ne olduğudur), derleyici tarafından importlanırdı. + +# Teste koy + +Şimdi bir çoklu dosya yapısı kurduk, diğer dosyaların içeriklerini okumak için `import` kullanmamız gerekiyor: + +1. `zombiefactory.sol`'i yeni dosyanmız `zombiefeeding.sol`'a aktar. diff --git a/tr/2/7-storage.md b/tr/2/7-storage.md new file mode 100644 index 0000000000..f1ce4290f6 --- /dev/null +++ b/tr/2/7-storage.md @@ -0,0 +1,135 @@ +--- +title: Depolama vs Hafıza +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + // Buradan başla + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + } + + } +--- + +Solidity'de, değişkenleri depolayacağınız iki yer vardır — `storage` ve `memory`. + +**_Depolama_** değişkenlerin kalıcı olarak blok zincirinde depolanmasını işaret eder. **_Hafıza_** değişkenleri geçicidir ve kontratınızı çağıran harici fonksiyon arasında silinir. Bilgisayarınızın hard diski ve RAM'i gibi düşünün. + +Çoğu zaman Solidity onları varsayılan olarak işlediğinden bu anahtar kelimeleri kullanmanız gerekmez. Durum değişkenleri (fonksiyonların dışında tanımlanmış) fonksiyonların içinde belirlenen değişkenler `memory` iken ve fonksiyon bitir çağırdığında gözden kaybolacakken varsayılan `storage` ve kalıcı olarak blok zincirine yazılır. + +Ancak, **_yapılar_** ve **_sıralar_** ile fonksiyon içine yaklaşım zamanı olarak adlandırılan bu anahtarlara ihtiyacınız olmadığında süreler vardır.: + +``` +contract SandwichFactory { + struct Sandwich { + string name; + string status; + } + + Sandwich[] sandwiches; + + function eatSandwich(uint _index) public { + // Sandwich mySandwich = sandwiches[_index]; + + // ^ Oldukça anlaşılır görünüyor, fakat solidity `storage` veya `memory`'i + // burada açıkça belirlemeniz gerektiği söyleyen bir uyarı verecek. + + // Yani bunun yerine, `storage` anahtar kelimesi ile belirlemelisiniz, şöyle: + Sandwich storage mySandwich = sandwiches[_index]; + // ...hangi durumlarda `mySandwich` bir `sandwiches[_index]` işaretçisidir + // depolamada, ve... + mySandwich.status = "Eaten!"; + // ...bu kalıcı olarak blok zincirinde `sandwiches[_index]`'i değiştirecek. + + // Sadece bir kopya istiyorsanız, `memory` kullanabilirsiniz: + Sandwich memory anotherSandwich = sandwiches[_index + 1]; + // ...hangi durumlarda `anotherSandwich` basitçe hafızadaki verinin bir + // kopyası olacak, ve... + anotherSandwich.status = "Eaten!"; + // ...geçici değişkeni değiştirecek ve `sandwiches[_index + 1]`'i + // etkilemeyecek. Fakat bunu yapabilirsiniz: + sandwiches[_index + 1] = anotherSandwich; + // ...değişiklikleri blok zinciri deposuna geri kopyalamak isterseniz. + } +} +``` + +Hangisini ne zaman kullanacağınızı henüz tam anlamadıysanız endişelenmeyin — bu eğitim boyunca `storage` ve `memory`'nin ne zaman kullanılacağını size anlatacağız, ve Solidity derleyicisi ayrıca sizi bilgilendirmek için bu anahtar kelimelerden birini kullanmanız gerektiğinde size uyarılar verecek. + +Şimdilik, `storage` veya `memory`'de açıkla belirlemeniz gerekecek durumlar olduğunu anlamanız yeterli ! + +# Teste koy + +Zombilerimize beslenme ve çoğalma yeteneğiverme zamanı! + +Bir zom bir başka bir yaşam formunda beslendiğinde, DNA'sı yeni bir zombi oluşturmak için diğer yaşam formunun DNA'sı ile birleşecek. + +1. `feedAndMultiply`. denilen bir fonksiyon oluşturun. İki parametre alacaktır: `_zombieId` (bir `uint`) ve `_targetDna` (ayrı bir `uint`). Bu fonksiyon `public` olmalıdır. + +2. Başka birinin zombimizi kullanıp beslemesine izin vermek istemiyoruz! O zaman ilk olarak, bu zombiye sahip olduğumuzdan emin olalım. `msg.sender`'in bu zombinin sahibine eşit olduğundan emin olmak için bir `require` durumu ekleyin (`createRandomZombie` fonksiyonunda yaptığımızla aynı). + + > Not: Tekrar, cevap denetleyicimiz primitif olduğundan, ilk gelmesi için `msg.sender` çıkarıyor ve sırayı değiştirirseniz onu yanlış işaretleyecek. Ama normalde kodlarken, istediğiniz bir tercih sırasını kullanabilirsiniz — ikisi de doğru. + +3. Bu zombinin DNA'sını almamız gerekecek. Yani fonksiyonumuzun yapması gerek sonki şey `myZombie` isimli yerel bir `Zombie` belirlemek (bir `storage` işaretçisi olacak). Bu değişkeni `zombies` sıralamamızda indeks `_zombieId`'ye eşit olarak ayarlayın. + +Kapatma satırları dahil, şimdiye kadar 4 satır kodunuz olmalı `}`. + +Sonraki bölümde bu fonksiyonu ayrıntılarıyla anlatmaya devam edeceğiz! diff --git a/tr/2/8-feedandmultiply2.md b/tr/2/8-feedandmultiply2.md new file mode 100644 index 0000000000..0c481d1ee6 --- /dev/null +++ b/tr/2/8-feedandmultiply2.md @@ -0,0 +1,106 @@ +--- +title: Zombie DNA'sı +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + // buradan başla + } + + } + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } +--- + +Hadi `feedAndMultiply` fonksiyonunu yazmayı bitirelim. + +Yeni bir zombi DNA'sı hesaplamak için formül basittir: Beslenen zombinin DNA'sı ve hedefin DNA'sı arasında ortalama almak basittir. + +Örneğin: + +``` +function testDnaSplicing() public { + uint zombieDna = 2222222222222222; + uint targetDna = 4444444444444444; + uint newZombieDna = (zombieDna + targetDna) / 2; + // ^ 3333333333333333'e eşit olacak +} +``` + +Daha sonra istersek formülümüzü yeni zombinin DNA'sına bazı rastgelelikler eklemek gibi daha karışık yapabiliriz. Fakat şimdilik bunu basit tutacağız — ona her zaman geri dönebiliriz. + +# Teste koy + +1. İlk olarak `_targetDna`'nın 16 basamaktan uzun olmadığına emin olmamız gerekiyor. Bunu yapmak için, sadece 16 basamak alması için `_targetDna`yı `_targetDna % dnaModulus`a eşitleyebiliriz. + +2. Sonraki fonksiyonumuz `newDna` isimli bir `uint` belirlemeli ve `myZombie`'nin DNA'sının ve `_targetDna`'in ortalamasına eşit olarak ayarlamalıdır (yukarıdaki örnekte olduğu gibi). + + > Not: `myZombie.name` ve `myZombie.dna` kullanarak `myZombie`'nin özelliklerine erişebilirsiniz + +3. Yeni DNA'mız olduğunda, `_createZombie`'yi çağıralım. Bu fonksinun çağırması için hangi parametreler olduğunu unuttuysan `zombiefactory.sol` sekmesine bakabilirsin. Bir isim gerektirdiğine dikkat edin, şimdilik zombimizin ismini `"NoName"` olarak ayarlayalım — daha sonra zombinin ismini değiştirmek için bir fonksiyon yazabiliriz. + +> Not: Sizin için Solidity vızıldar, burdaki kodumuzda bir problem farkedebilirsiniz! Endişelenmeyin, bunu sonraki bölümde düzelteceğiz ;) diff --git a/tr/2/9-internalfunctions.md b/tr/2/9-internalfunctions.md new file mode 100644 index 0000000000..33b43ec2e0 --- /dev/null +++ b/tr/2/9-internalfunctions.md @@ -0,0 +1,145 @@ +--- +title: Fonksiyon Görünürlüğünde Dahası +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + // edit function definition below + function _createZombie(string _name, uint _dna) private { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } + "zombiefeeding.sol": | + pragma solidity ^0.4.19; + + import "./zombiefactory.sol"; + + contract ZombieFeeding is ZombieFactory { + + function feedAndMultiply(uint _zombieId, uint _targetDna) public { + require(msg.sender == zombieToOwner[_zombieId]); + Zombie storage myZombie = zombies[_zombieId]; + _targetDna = _targetDna % dnaModulus; + uint newDna = (myZombie.dna + _targetDna) / 2; + _createZombie("NoName", newDna); + } + + } + answer: > + pragma solidity ^0.4.19; + + 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; + + mapping (uint => address) public zombieToOwner; + mapping (address => uint) ownerZombieCount; + + function _createZombie(string _name, uint _dna) internal { + uint id = zombies.push(Zombie(_name, _dna)) - 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); + _createZombie(_name, randDna); + } + + } +--- + +**Önceki dersimizdeki kodda bir yanlışlık var!** + +Onu derlemeyi denerseniz, derleyici bir hata verecektir. + +Bu sorun `ZombieFeeding` içinden `_createZombie` fonksiyonunu çağırmayı denediğimiz fakat `_createZombie`'nin `ZombieFactory` içinde bir `private` fonksiyon olmasıdır. Bu, `ZombieFactory`'den miras kalan kontratların hiç biri ona erişemeyeceği anlamına gelir. + +## Dahili ve Harici + +`public` ve `private`'e ek olarak, Solidity'nin fonksiyonlar için iki görünürlük türü daha vardır: `internal` ve `external`. + +Bu kontrattan kalan kontratlara da erişebilir olması dışında, `internal` `private`'e benzerdir. **(Hey, bu bizim istediğimiz şey gibi!)**. + +Bu fonksiyonların SADECE kontrat dışında çağrılabilir olması dışında, `external` `public`'e benzerdir — bu kontratın içine başka fonksiyonlarla çağrılamazlar. Niçin `external` veya `public` kullanmak isteyebileceğinizden daha sonra bahsedeceğiz. + +`internal` veya `external` fonksiyonlarını belirlemek için, sözdizimi `private` ve `public` ile aynıdır : + +``` +contract Sandwich { + uint private sandwichesEaten = 0; + + function eat() internal { + sandwichesEaten++; + } +} + +contract BLT is Sandwich { + uint private baconSandwichesEaten = 0; + + function eatWithBacon() public returns (string) { + baconSandwichesEaten++; + // We can call this here because it's internal + eat(); + } +} +``` + +# Teste koy + +1. `_createZombie()`'yi `private`'den `internal`'a değiştirin böylece diğer kontratlarımız ona ulaşabilecek. + + Zaten sizi doğru sekmeye, `zombiefactory.sol`'e dönmeye odakladık. diff --git a/tr/2/template.md b/tr/2/template.md new file mode 100644 index 0000000000..baa9a1983a --- /dev/null +++ b/tr/2/template.md @@ -0,0 +1,19 @@ +--- +title: Veri Türleri +actions: ['cevapKontrol', 'ipuçları'] +material: + editor: + language: sol + startingCode: | + answer: > +--- + +Solidity'nin kodu kontratlarda kapsüllenmiştir. Bir kontrat temel olarak... + +``` +contract HelloWorld + +``` + +# Teste koy + diff --git a/tr/index.ts b/tr/index.ts index 239dbf4d57..15f44d9b9a 100644 --- a/tr/index.ts +++ b/tr/index.ts @@ -18,7 +18,7 @@ import puttingittogether from './1/puttingittogether.md' import events from './1/events.md' import web3js from './1/web3js.md' import lessoncomplete from './1/lessoncomplete.md' - +/* // lesson2 import l2_overview from './2/00-overview.md' import overview from './2/1-overview.md' @@ -69,7 +69,7 @@ import l4_ch8 from './4/battle-08.md' import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' - +*/ // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -91,7 +91,7 @@ export default { events, web3js, lessoncomplete - ], + ]/*, 2: [ l2_overview, overview, @@ -142,5 +142,5 @@ export default { l4_ch9, l4_ch10, l4_complete - ] + ]*/ } \ No newline at end of file diff --git a/zh/2/13-kittygenes.md b/zh/2/13-kittygenes.md index 1bff9126c9..3c38f07d02 100644 --- a/zh/2/13-kittygenes.md +++ b/zh/2/13-kittygenes.md @@ -118,7 +118,7 @@ material: Zombie storage myZombie = zombies[_zombieId]; _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; - if (keccak256(species) == keccak256("kitty")) { + if (keccak256(_species) == keccak256("kitty")) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); diff --git a/zh/3/02-ownable.md b/zh/3/02-ownable.md index 3124ae9128..e2062c16b3 100644 --- a/zh/3/02-ownable.md +++ b/zh/3/02-ownable.md @@ -242,7 +242,7 @@ contract Ownable { 1. 合约创建,构造函数先行,将其 `owner` 设置为`msg.sender`(其部署者) -2. 为它加上一个修饰符 `onlyOwner`,它会限制陌生人的人访问,将访问某些函数的权限锁定在 `owner` 上。 +2. 为它加上一个修饰符 `onlyOwner`,它会限制陌生人的访问,将访问某些函数的权限锁定在 `owner` 上。 3. 允许将合约所有权转让给他人。 diff --git a/zh/3/04-gas.md b/zh/3/04-gas.md index e8fd7259f8..cfe74712e4 100644 --- a/zh/3/04-gas.md +++ b/zh/3/04-gas.md @@ -21,7 +21,7 @@ material: struct Zombie { string name; uint dna; - // Add new data here + //在这里添加数据 } Zombie[] public zombies; @@ -186,13 +186,13 @@ material: ## Gas - 驱动以太坊DApps的能源 -在 Solidit y中,你的用户想要每次执行你的 DApp 都需要支付一定的 **_gas_**,gas 可以用以太币购买,因此,用户每次跑 DApp 都得花费以太币。 +在 Solidity 中,你的用户想要每次执行你的 DApp 都需要支付一定的 **_gas_**,gas 可以用以太币购买,因此,用户每次跑 DApp 都得花费以太币。 -一个 DApp 收取多少 gas 取决于功能逻辑的复杂程度。每个操作背后,都在计算完成这个操作所需要的计算资源,(比如,存储数据就比做个加法运算贵得多), 一次操作所需要花费的**_gas_**等于这个操作背后的所有运算花销的总和。 +一个 DApp 收取多少 gas 取决于功能逻辑的复杂程度。每个操作背后,都在计算完成这个操作所需要的计算资源,(比如,存储数据就比做个加法运算贵得多), 一次操作所需要花费的 **_gas_** 等于这个操作背后的所有运算花销的总和。 由于运行你的程序需要花费用户的真金白银,在以太坊中代码的编程语言,比其他任何编程语言都更强调优化。同样的功能,使用笨拙的代码开发的程序,比起经过精巧优化的代码来,运行花费更高,这显然会给成千上万的用户带来大量不必要的开销。 -## 为什么要用**_gas_**来驱动? +## 为什么要用 **_gas_** 来驱动? 以太坊就像一个巨大、缓慢、但非常安全的电脑。当你运行一个程序的时候,网络上的每一个节点都在进行相同的运算,以验证它的输出 —— 这就是所谓的”去中心化“ 由于数以千计的节点同时在验证着每个功能的运行,这可以确保它的数据不会被被监控,或者被刻意修改。 @@ -201,11 +201,11 @@ material: >注意:如果你使用侧链,倒是不一定需要付费,比如咱们在 Loom Network 上构建的 CryptoZombies 就免费。你不会想要在以太坊主网上玩儿“魔兽世界”吧? - 所需要的 gas 可能会买到你破产。但是你可以找个算法理念不同的侧链来玩它。我们将在以后的课程中咱们会讨论到,什么样的 DApp 应该部署在太坊主链上,什么又最好放在侧链。 -## 省”gas“的招数:结构封装 (Struct packing) +## 省 gas 的招数:结构封装 (Struct packing) 在第1课中,我们提到除了基本版的 `uint` 外,还有其他变种 `uint`:`uint8`,`uint16`,`uint32`等。 -通常情况下我们不会考虑使用 `unit` 变种,因为无论如何定义 `uint`的大小,Solidity 为它保留256位的存储空间。例如,使用 `uint8` 而不是`uint`(`uint256`)不会为你节省任何gas。 +通常情况下我们不会考虑使用 `unit` 变种,因为无论如何定义 `uint`的大小,Solidity 为它保留256位的存储空间。例如,使用 `uint8` 而不是`uint`(`uint256`)不会为你节省任何 gas。 除非,把 `unit` 绑定到 `struct` 里面。 @@ -230,7 +230,7 @@ MiniMe mini = MiniMe(10, 20, 30); ``` 所以,当 `uint` 定义在一个 `struct` 中的时候,尽量使用最小的整数子类型以节约空间。 -并且把同样类型的变量放一起(即在struct中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 `struct`: +并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 `struct`: `uint c; uint32 a; uint32 b;` 和 @@ -240,7 +240,7 @@ MiniMe mini = MiniMe(10, 20, 30); ## 实战演习 -在本课中,咱们给僵尸添2个新功能:`le​​vel` 和 `readyTime` - 后者是用来实现一个”冷却定时器“,以限制僵尸猎食的频率。 +在本课中,咱们给僵尸添2个新功能:`le​​vel` 和 `readyTime` - 后者是用来实现一个“冷却定时器”,以限制僵尸猎食的频率。 让我们回到 `zombiefactory.sol`。 diff --git a/zh/3/08-functionmodifiers.md b/zh/3/08-functionmodifiers.md index 6b260b9d6f..63eb05c7b4 100644 --- a/zh/3/08-functionmodifiers.md +++ b/zh/3/08-functionmodifiers.md @@ -189,7 +189,7 @@ mapping (uint => uint) public age; // 限定用户年龄的修饰符 modifier olderThan(uint _age, uint _userId) { - require (age[_userId] >= _age); + require(age[_userId] >= _age); _; } diff --git a/zh/4/battle-02.md b/zh/4/battle-02.md index 056ea863f6..cbc4e959cd 100644 --- a/zh/4/battle-02.md +++ b/zh/4/battle-02.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -163,6 +165,7 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -203,6 +206,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/zh/4/battle-03.md b/zh/4/battle-03.md index 4f87a0ced4..62e4906180 100644 --- a/zh/4/battle-03.md +++ b/zh/4/battle-03.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -171,6 +173,8 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; + /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -211,6 +215,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/zh/4/battle-04.md b/zh/4/battle-04.md index 2bf0371cf9..9aee9f2626 100644 --- a/zh/4/battle-04.md +++ b/zh/4/battle-04.md @@ -66,6 +66,7 @@ material: } } "zombieattack.sol": | + pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -176,6 +177,8 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; + /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control diff --git a/zh/4/battle-05.md b/zh/4/battle-05.md index b1ae4093c7..93ac6c3da7 100644 --- a/zh/4/battle-05.md +++ b/zh/4/battle-05.md @@ -173,6 +173,7 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -234,6 +235,11 @@ material: 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; } diff --git a/zh/4/battle-06.md b/zh/4/battle-06.md index 0ecded457c..9a540cdd55 100644 --- a/zh/4/battle-06.md +++ b/zh/4/battle-06.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -171,6 +173,8 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; + /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -211,6 +215,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/zh/4/battle-07.md b/zh/4/battle-07.md index 9035b7750c..5c48a2869f 100644 --- a/zh/4/battle-07.md +++ b/zh/4/battle-07.md @@ -54,6 +54,8 @@ material: } "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -174,6 +176,8 @@ material: } } "ownable.sol": | + pragma solidity ^0.4.19; + /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control diff --git a/zh/4/battle-08.md b/zh/4/battle-08.md index eddf9c3a9b..d50a2bb059 100644 --- a/zh/4/battle-08.md +++ b/zh/4/battle-08.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -173,6 +175,7 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -213,6 +216,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/zh/4/battle-09.md b/zh/4/battle-09.md index 743ea5a0e1..8142ab5b1f 100644 --- a/zh/4/battle-09.md +++ b/zh/4/battle-09.md @@ -7,6 +7,8 @@ material: language: sol startingCode: "zombieattack.sol": | + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { @@ -178,6 +180,8 @@ material: } "ownable.sol": | + pragma solidity ^0.4.19; + /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control @@ -218,6 +222,8 @@ material: } answer: > + pragma solidity ^0.4.19; + import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { diff --git a/zh/5/00-overview.md b/zh/5/00-overview.md new file mode 100644 index 0000000000..e05609d267 --- /dev/null +++ b/zh/5/00-overview.md @@ -0,0 +1,13 @@ +--- +title: ERC721 标准和电子收集物 +header: "Lesson 5: ERC721 标准和电子收集物" +roadmap: roadmap5.jpg +--- + +呼,游戏变得越来越刺激啦... + +在这一课,我们将接触到一些更高级的东西。 + +我们将讨论 **代币**, **ERC721** 标准, 以及 **加密收集资产**. + +换句话说,我们将 **让你可以和你的朋友交易僵尸!** diff --git a/zh/5/01-erc721-1.md b/zh/5/01-erc721-1.md new file mode 100644 index 0000000000..e04eb8e4f9 --- /dev/null +++ b/zh/5/01-erc721-1.md @@ -0,0 +1,291 @@ +--- +title: 以太坊上的代币 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + // 从这里开始 + "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": | + pragma solidity ^0.4.19; + + /** + * @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 { + + } +--- + +让我们来聊聊 **_代币_**. + +如果你对以太坊的世界有一些了解,你很可能听过人们聊到代币——尤其是 **_ERC20 代币_**. + +一个 **_代币_** 在以太坊基本上就是一个遵循一些共同规则的智能合约——即它实现了所有其他代币合约共享的一组标准函数,例如 `transfer(address _to, uint256 _value)` 和 `balanceOf(address _owner)`. + +在智能合约内部,通常有一个映射, `mapping(address => uint256) balances`,那用于追踪每个地址还有多少余额。 + +所以基本上一个代币只是一个追踪谁拥有多少该代币的合约,和一些可以让那些用户将他们的代币转移到其他地址的函数。 + +### 它为什么重要呢? + +由于所有 ERC20 代币共享具有相同名称的同一组函数,它们都可以以相同的方式进行交互。 + +这意味着如果你构建的应用程序能够与一个 ERC20 代币进行交互,那么它就也能够与任何 ERC20 代币进行交互。 这样一来,将来你就可以轻松地将更多的代币添加到你的应用中,而无需进行自定义编码。 你可以简单地插入新的代币合约地址,然后哗啦,你的应用程序有另一个它可以使用的代币了。 + +其中一个例子就是交易所。 当交易所添加一个新的 ERC20 代币时,实际上它只需要添加与之对话的另一个智能合约。 用户可以让那个合约将代币发送到交易所的钱包地址,然后交易所可以让合约在用户要求取款时将代币发送回给他们。 + +交易所只需要实现这种转移逻辑一次,然后当它想要添加一个新的 ERC20 代币时,只需将新的合约地址添加到它的数据库即可。 + +### 其他代币标准 + +对于像货币一样的代币来说,ERC20 代币非常酷。 但是要在我们僵尸游戏中代表僵尸就并不是特别有用。 + +首先,僵尸不像货币可以分割 —— 我可以发给你 0.237 以太,但是转移给你 0.237 的僵尸听起来就有些搞笑。 + +其次,并不是所有僵尸都是平等的。 你的2级僵尸"**Steve**"完全不能等同于我732级的僵尸"**H4XF13LD MORRIS 💯💯😎💯💯**"。(你差得远呢,*Steve*)。 + +有另一个代币标准更适合如 CryptoZombies 这样的加密收藏品——它们被称为**_ERC721 代币._** + +**_ERC721 代币_**是**不**能互换的,因为每个代币都被认为是唯一且不可分割的。 你只能以整个单位交易它们,并且每个单位都有唯一的 ID。 这些特性正好让我们的僵尸可以用来交易。 + +> 请注意,使用像 ERC721 这样的标准的优势就是,我们不必在我们的合约中实现拍卖或托管逻辑,这决定了玩家能够如何交易/出售我们的僵尸。 如果我们符合规范,其他人可以为加密可交易的 ERC721 资产搭建一个交易所平台,我们的 ERC721 僵尸将可以在该平台上使用。 所以使用代币标准相较于使用你自己的交易逻辑有明显的好处。 + +## 实战演习 + +我们将在下一章深入讨论ERC721的实现。 但首先,让我们为本课设置我们的文件结构。 + +我们将把所有ERC721逻辑存储在一个叫`ZombieOwnership`的合约中。 + +1. 在文件顶部声明我们`pragma`的版本(格式参考之前的课程)。 + +2. 将 `zombieattack.sol` `import` 进来。 + +3. 声明一个继承 `ZombieAttack` 的新合约, 命名为`ZombieOwnership`。合约的其他部分先留空。 diff --git a/zh/5/02-erc721-2.md b/zh/5/02-erc721-2.md new file mode 100644 index 0000000000..52f09e0782 --- /dev/null +++ b/zh/5/02-erc721-2.md @@ -0,0 +1,312 @@ +--- +title: ERC721 标准, 多重继承 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + // 在这里引入文件 + + // 在这里声明 ERC721 的继承 + contract ZombieOwnership is ZombieAttack { + + } + "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": | + pragma solidity ^0.4.19; + + /** + * @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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: > + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + } +--- + +让我们来看一看 ERC721 标准: + +``` +contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; +} +``` + +这是我们需要实现的方法列表,我们将在接下来的章节中逐个学习。 + +虽然看起来很多,但不要被吓到了!我们在这里就是准备带着你一步一步了解它们的。 + +> 注意: ERC721目前是一个 *草稿*,还没有正式商定的实现。在本教程中,我们使用的是 OpenZeppelin 库中的当前版本,但在未来正式发布之前它可能会有更改。 所以把这 **一个** 可能的实现当作考虑,但不要把它作为 ERC721 代币的官方标准。 + +### 实现一个代币合约 + +在实现一个代币合约的时候,我们首先要做的是将接口复制到它自己的 Solidity 文件并导入它,`import ./erc721.sol`。 接着,让我们的合约继承它,然后我们用一个函数定义来重写每个方法。 + +但等一下—— `ZombieOwnership`已经继承自 `ZombieAttack`了 —— 它如何能够也继承于 `ERC721`呢? + +幸运的是在Solidity,你的合约可以继承自多个合约,参考如下: + +``` +contract SatoshiNakamoto is NickSzabo, HalFinney { + // 啧啧啧,宇宙的奥秘泄露了 +} +``` + +正如你所见,当使用多重继承的时候,你只需要用逗号 `,` 来隔开几个你想要继承的合约。在上面的例子中,我们的合约继承自 `NickSzabo` 和 `HalFinney`。 + +来试试吧。 + +## 实战演习 + +我们已经在上面为你创建了带着接口的 `erc721.sol` 。 + +1. 将 `erc721.sol` 导入到 `zombieownership.sol` + +2. 声明 `ZombieOwnership` 继承自 `ZombieAttack` 和 `ERC721` \ No newline at end of file diff --git a/zh/5/03-erc721-3.md b/zh/5/03-erc721-3.md new file mode 100644 index 0000000000..6a69885ad5 --- /dev/null +++ b/zh/5/03-erc721-3.md @@ -0,0 +1,338 @@ +--- +title: balanceOf 和 ownerOf +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + // 1. 在这里返回 `_owner` 拥有的僵尸数 + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + // 2. 在这里返回 `_tokenId` 的所有者 + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +太棒了,我们来深入讨论一下 ERC721 的实现。 + +我们已经把所有你需要在本课中实现的函数的空壳复制好了。 + +在本章节,我们将实现头两个方法: `balanceOf` 和 `ownerOf`。 + +### `balanceOf` + +``` + function balanceOf(address _owner) public view returns (uint256 _balance); +``` + +这个函数只需要一个传入 `address` 参数,然后返回这个 `address` 拥有多少代币。 + +在我们的例子中,我们的“代币”是僵尸。你还记得在我们 DApp 的哪里存储了一个主人拥有多少只僵尸吗? + +### `ownerOf` + +``` + function ownerOf(uint256 _tokenId) public view returns (address _owner); +``` + +这个函数需要传入一个代币 ID 作为参数 (我们的情况就是一个僵尸 ID),然后返回该代币拥有者的 `address`。 + +同样的,因为在我们的 DApp 里已经有一个 `mapping` (映射) 存储了这个信息,所以对我们来说这个实现非常直接清晰。我们可以只用一行 `return` 语句来实现这个函数。 + +> 注意:要记得, `uint256` 等同于`uint`。我们从课程的开始一直在代码中使用 `uint`,但从现在开始我们将在这里用 `uint256`,因为我们直接从规范中复制粘贴。 + +## 实战演习 + +我将让你来决定如何实现这两个函数。 + +每个函数的代码都应该只有1行 `return` 语句。看看我们在之前课程中写的代码,想想我们都把这个数据存储在哪。如果你觉得有困难,你可以点“我要看答案”的按钮来获得帮助。 + +1. 实现 `balanceOf` 来返回 `_owner` 拥有的僵尸数量。 + +2. 实现 `ownerOf` 来返回拥有 ID 为 `_tokenId` 僵尸的所有者的地址。 \ No newline at end of file diff --git a/zh/5/04-erc721-4.md b/zh/5/04-erc721-4.md new file mode 100644 index 0000000000..546604d893 --- /dev/null +++ b/zh/5/04-erc721-4.md @@ -0,0 +1,355 @@ +--- +title: 重构 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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; + + // 1. 把修饰符名称改成 `onlyOwnerOf` + 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); + } + + // 2. 这里也要修改修饰符的名称 + 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"); + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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; + } + + } + "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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } +--- + +嘿嘿!我们刚刚的代码中其实有个错误,以至于其根本无法通过编译,你发现了没? + +在前一个章节我们定义了一个叫 `ownerOf` 的函数。但如果你还记得第4课的内容,我们同样在`zombiefeeding.sol` 里以 `ownerOf` 命名创建了一个 `modifier`(修饰符)。 + +如果你尝试编译这段代码,编译器会给你一个错误说你不能有相同名称的修饰符和函数。 + +所以我们应该把在 `ZombieOwnership` 里的函数名称改成别的吗? + +不,我们不能那样做!!!要记得,我们正在用 ERC721 代币标准,意味着其他合约将期望我们的合约以这些确切的名称来定义函数。这就是这些标准实用的原因——如果另一个合约知道我们的合约符合 ERC721 标准,它可以直接与我们交互,而无需了解任何关于我们内部如何实现的细节。 + +所以,那意味着我们将必须重构我们第4课中的代码,将 `modifier` 的名称换成别的。 + +## 实战演习 + +我们回到了 `zombiefeeding.sol` 。我们将把 `modifier` 的名称从 `ownerOf` 改成 `onlyOwnerOf`。 + +1. 把修饰符定义中的名称改成 `onlyOwnerOf` + +2. 往下滑到使用此修饰符的函数 `feedAndMultiply` 。我们也需要改这里的名称。 + +> 注意:我们在 `zombiehelper.sol` 和 `zombieattack.sol` 里也使用了这个修饰符,但为了不在这节课的重构里花太多时间,我们已经将那些文件里的修饰符名称为你改好了。 \ No newline at end of file diff --git a/zh/5/05-erc721-5.md b/zh/5/05-erc721-5.md new file mode 100644 index 0000000000..27c75c0a54 --- /dev/null +++ b/zh/5/05-erc721-5.md @@ -0,0 +1,348 @@ +--- +title: "ERC721: 转移标准" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + // 在这里定义 _transfer() + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public { + + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +好了,我们将冲突修复了! + +现在我们将通过学习把所有权从一个人转移给另一个人来继续我们的 ERC721 规范的实现。 + +注意 ERC721 规范有两种不同的方法来转移代币: + +``` +function transfer(address _to, uint256 _tokenId) public; + +function approve(address _to, uint256 _tokenId) public; +function takeOwnership(uint256 _tokenId) public; +``` + +1. 第一种方法是代币的拥有者调用`transfer` 方法,传入他想转移到的 `address` 和他想转移的代币的 `_tokenId`。 + +2. 第二种方法是代币拥有者首先调用 `approve`,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个 `mapping (uint256 => address)` 里。然后,当有人调用 `takeOwnership` 时,合约会检查 `msg.sender` 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。 + +你注意到了吗,`transfer` 和 `takeOwnership` 都将包含相同的转移逻辑,只是以相反的顺序。 (一种情况是代币的发送者调用函数;另一种情况是代币的接收者调用它)。 + +所以我们把这个逻辑抽象成它自己的私有函数 `_transfer`,然后由这两个函数来调用它。 这样我们就不用写重复的代码了。 + +## 实战演习 + +让我们来定义 `_transfer` 的逻辑。 + +1. 定义一个名为 `_transfer `的函数。它会需要3个参数:`address _from`、`address _to`和`uint256 _tokenId`。它应该是一个 `私有` 函数。 + +2. 我们有2个映射会在所有权改变的时候改变: `ownerZombieCount` (记录一个所有者有多少只僵尸)和 `zombieToOwner` (记录什么人拥有什么)。 + + 我们的函数需要做的第一件事是为 **接收** 僵尸的人(`address _to`)增 加`ownerZombieCount`。使用 `++` 来增加。 + +3. 接下来,我们将需要为 **发送** 僵尸的人(`address _from`)**减少**`ownerZombieCount`。使用 `--` 来扣减。 + +4. 最后,我们将改变这个 `_tokenId` 的 `zombieToOwner` 映射,这样它现在就会指向 `_to`。 + +5. 骗你的,那不是最后一步。我们还需要再做一件事情。 + + ERC721规范包含了一个 `Transfer` 事件。这个函数的最后一行应该用正确的参数触发`Transfer` ——查看 `erc721.sol` 看它期望传入的参数并在这里实现。 diff --git a/zh/5/06-erc721-6.md b/zh/5/06-erc721-6.md new file mode 100644 index 0000000000..81a984203c --- /dev/null +++ b/zh/5/06-erc721-6.md @@ -0,0 +1,327 @@ +--- +title: "ERC721: 转移-续" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + // 1. 在这里添加修饰符 + function transfer(address _to, uint256 _tokenId) public { + // 2. 在这里定义方法 + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public { + + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +太好了!刚才那是最难的部分——现在实现公共的 `transfer` 函数应该十分容易,因为我们的 `_transfer` 函数几乎已经把所有的重活都干完了。 + +## 实战演习 + +1. 我们想确保只有代币或僵尸的所有者可以转移它。还记得我们如何限制只有所有者才能访问某个功能吗? + + 没错,我们已经有一个修饰符能够完成这个任务了。所以将修饰符 `onlyOwnerOf` 添加到这个函数中。 + +2. 现在该函数的正文只需要一行代码。它只需要调用 `_transfer`。 + + 记得把 `msg.sender` 作为参数传递进 `address _from`。 diff --git a/zh/5/07-erc721-7.md b/zh/5/07-erc721-7.md new file mode 100644 index 0000000000..472e77c833 --- /dev/null +++ b/zh/5/07-erc721-7.md @@ -0,0 +1,343 @@ +--- +title: "ERC721: 批准" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + // 1. 在这里定义映射 + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + // 2. 在这里添加方法修饰符 + function approve(address _to, uint256 _tokenId) public { + // 3. 在这里定义方法 + } + + function takeOwnership(uint256 _tokenId) public { + + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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": | + pragma solidity ^0.4.19; + /** + * @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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + + } + } +--- + +现在,让我们来实现 `approve`。 + +记住,使用 `approve` 或者 `takeOwnership` 的时候,转移有2个步骤: + +1. 你,作为所有者,用新主人的 `address` 和你希望他获取的 `_tokenId` 来调用 `approve` + +2. 新主人用 `_tokenId` 来调用 `takeOwnership`,合约会检查确保他获得了批准,然后把代币转移给他。 + +因为这发生在2个函数的调用中,所以在函数调用之间,我们需要一个数据结构来存储什么人被批准获取什么。 + +## 实战演习 + +1. 首先,让我们来定义一个映射 `zombieApprovals`。它应该将一个 `uint` 映射到一个 `address`。 + + 这样一来,当有人用一个 `_tokenId` 调用 `takeOwnership` 时,我们可以用这个映射来快速查找谁被批准获取那个代币。 + +2. 在函数 `approve` 上, 我们想要确保只有代币所有者可以批准某人来获取代币。所以我们需要添加修饰符 `onlyOwnerOf` 到 `approve`。 + +3. 函数的正文部分,将 `_tokenId` 的 `zombieApprovals` 设置为和 `_to` 相等。 + +4. 最后,在 ERC721 规范里有一个 `Approval` 事件。所以我们应该在这个函数的最后触发这个事件。(参考 `erc721.sol` 来确认传入的参数,并确保 `_owner` 是 `msg.sender`) diff --git a/zh/5/08-erc721-8.md b/zh/5/08-erc721-8.md new file mode 100644 index 0000000000..629e70173e --- /dev/null +++ b/zh/5/08-erc721-8.md @@ -0,0 +1,340 @@ +--- +title: "ERC721: takeOwnership" +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + // 从这里开始 + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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; + } + + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +太棒了,现在让我们完成最后一个函数来结束 ERC721 的实现。(别担心,这后面我们还会讲更多内容😉) + +最后一个函数 `takeOwnership`, 应该只是简单地检查以确保 `msg.sender` 已经被批准来提取这个代币或者僵尸。若确认,就调用 `_transfer`; + +## 实战演习 + +1. 首先,我们要用一个 `require` 句式来检查 `_tokenId` 的 `zombieApprovals` 和 `msg.sender` 相等。 + + 这样如果 `msg.sender` 未被授权来提取这个代币,将抛出一个错误。 + +2. 为了调用 `_transfer`,我们需要知道代币所有者的地址(它需要一个 `_from` 来作为参数)。幸运的是我们可以在我们的 `ownerOf` 函数中来找到这个参数。 + + 所以,定义一个名为 `owner` 的 `address` 变量,并使其等于 `ownerOf(_tokenId)`。 + +3. 最后,调用 `_transfer`, 并传入所有必须的参数。(在这里你可以用 `msg.sender` 作为 `_to`, 因为代币正是要发送给调用这个函数的人)。 + + > 注意: 我们完全可以用一行代码来实现第2、3两步。但是分开写会让代码更易读。一点个人建议 :) \ No newline at end of file diff --git a/zh/5/09-safemath-1.md b/zh/5/09-safemath-1.md new file mode 100644 index 0000000000..e776d826fa --- /dev/null +++ b/zh/5/09-safemath-1.md @@ -0,0 +1,446 @@ +--- +title: 预防溢出 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + // 1. 在这里引入 + + contract ZombieFactory is Ownable { + + // 2. 在这里定义 using safemath + + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to]++; + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "ownable.sol": | + pragma solidity ^0.4.19; + /** + * @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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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); + } + + } +--- + +恭喜你,我们完成了 ERC721 的实现。 + +并不是很复杂,对吧?很多类似的以太坊概念,当你只听人们谈论它们的时候,会觉得很复杂。所以最简单的理解方式就是你自己来实现它。 + +不过要记住那只是最简单的实现。还有很多的特性我们也许想加入到我们的实现中来,比如一些额外的检查,来确保用户不会不小心把他们的僵尸转移给`0` 地址(这被称作 “烧币”, 基本上就是把代币转移到一个谁也没有私钥的地址,让这个代币永远也无法恢复)。 +或者在 DApp 中加入一些基本的拍卖逻辑。(你能想出一些实现的方法么?) + +但是为了让我们的课程不至于离题太远,所以我们只专注于一些基础实现。如果你想学习一些更深层次的实现,可以在这个教程结束后,去看看 OpenZeppelin 的 ERC721 合约。 + +### 合约安全增强: 溢出和下溢 + +我们将来学习你在编写智能合约的时候需要注意的一个主要的安全特性:防止溢出和下溢。 + +什么是 **_溢出_** (**_overflow_**)? + +假设我们有一个 `uint8`, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制 `11111111` (或者说十进制的 2^8 - 1 = 255). + +来看看下面的代码。最后 `number` 将会是什么值? + +``` +uint8 number = 255; +number++; +``` + +在这个例子中,我们导致了溢出 — 虽然我们加了1, 但是 `number` 出乎意料地等于 `0`了。 (如果你给二进制 `11111111` 加1, 它将被重置为 `00000000`,就像钟表从 `23:59` 走向 `00:00`)。 + +下溢(`underflow`)也类似,如果你从一个等于 `0` 的 `uint8` 减去 `1`, 它将变成 `255` (因为 `uint` 是无符号的,其不能等于负数)。 + +虽然我们在这里不使用 `uint8`,而且每次给一个 `uint256` 加 `1` 也不太可能溢出 (2^256 真的是一个很大的数了),在我们的合约中添加一些保护机制依然是非常有必要的,以防我们的 DApp 以后出现什么异常情况。 + +### 使用 SafeMath + +为了防止这些情况,OpenZeppelin 建立了一个叫做 SafeMath 的 **_库_**(**_library_**),默认情况下可以防止这些问题。 + +不过在我们使用之前…… 什么叫做库? + +一个**_库_** 是 Solidity 中一种特殊的合约。其中一个有用的功能是给原始数据类型增加一些方法。 + +比如,使用 SafeMath 库的时候,我们将使用 `using SafeMath for uint256` 这样的语法。 SafeMath 库有四个方法 — `add`, `sub`, `mul`, 以及 `div`。现在我们可以这样来让 `uint256` 调用这些方法: + +``` +using SafeMath for uint256; + +uint256 a = 5; +uint256 b = a.add(3); // 5 + 3 = 8 +uint256 c = a.mul(2); // 5 * 2 = 10 +``` + +我们将在下一章来学习这些方法,不过现在我们先将 SafeMath 库添加进我们的合约。 + +## 实战演习 + +我们已经帮你把 OpenZeppelin 的 `SafeMath` 库包含进 `safemath.sol`了,如果你想看一下代码的话,现在可以看看,不过我们下一章将深入进去。 + +首先我们来告诉我们的合约要使用 SafeMath。我们将在我们的 `ZombieFactory` 里调用,这是我们的基础合约 — 这样其他所有继承出去的子合约都可以使用这个库了。 + +1. 将 `safemath.sol` 引入到 `zombiefactory.sol`. + +2. 添加定义: `using SafeMath for uint256;`. diff --git a/zh/5/10-safemath-2.md b/zh/5/10-safemath-2.md new file mode 100644 index 0000000000..691dc646a4 --- /dev/null +++ b/zh/5/10-safemath-2.md @@ -0,0 +1,470 @@ +--- +title: SafeMath 第二部分 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + // 1. Replace with SafeMath's `add` + ownerZombieCount[_to]++; + // 2. Replace with SafeMath's `sub` + ownerZombieCount[_from]--; + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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": | + pragma solidity ^0.4.19; + /** + * @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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[_from] = ownerZombieCount[_from].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +来看看 SafeMath 的部分代码: + +``` +library SafeMath { + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} +``` + +首先我们有了 `library` 关键字 — 库和 `合约`很相似,但是又有一些不同。 就我们的目的而言,库允许我们使用 `using` 关键字,它可以自动把库的所有方法添加给一个数据类型: + +``` +using SafeMath for uint; +// 这下我们可以为任何 uint 调用这些方法了 +uint test = 2; +test = test.mul(3); // test 等于 6 了 +test = test.add(5); // test 等于 11 了 +``` + +注意 `mul` 和 `add` 其实都需要两个参数。 在我们声明了 `using SafeMath for uint` 后,我们用来调用这些方法的 `uint` 就自动被作为第一个参数传递进去了(在此例中就是 `test`) + +我们来看看 `add` 的源代码看 SafeMath 做了什么: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} +``` + +基本上 `add` 只是像 `+` 一样对两个 `uint` 相加, 但是它用一个 `assert` 语句来确保结果大于 `a`。这样就防止了溢出。 + +`assert` 和 `require` 相似,若结果为否它就会抛出错误。 `assert` 和 `require` 区别在于,`require` 若失败则会返还给用户剩下的 gas, `assert` 则不会。所以大部分情况下,你写代码的时候会比较喜欢 `require`,`assert` 只在代码可能出现严重错误的时候使用,比如 `uint` 溢出。 + +所以简而言之, SafeMath 的 `add`, `sub`, `mul`, 和 `div` 方法只做简单的四则运算,然后在发生溢出或下溢的时候抛出错误。 + +### 在我们的代码里使用 SafeMath。 + +为了防止溢出和下溢,我们可以在我们的代码里找 `+`, `-`, `*`, 或 `/`,然后替换为 `add`, `sub`, `mul`, `div`. + +比如,与其这样做: + +``` +myUint++; +``` + +我们这样做: + +``` +myUint = myUint.add(1); +``` + +## 实战演习 + +在 `ZombieOwnership` 中有两个地方用到了数学运算,来替换成 SafeMath 方法把。 + +1. 将 `++` 替换成 SafeMath 方法。 + +2. 将 `--` 替换成 SafeMath 方法。 diff --git a/zh/5/11-safemath-3.md b/zh/5/11-safemath-3.md new file mode 100644 index 0000000000..051ee0206f --- /dev/null +++ b/zh/5/11-safemath-3.md @@ -0,0 +1,509 @@ +--- +title: SafeMath 第三部分 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombiefactory.sol": | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + // 1. 为 uint32 声明 使用 SafeMath32 + // 2. 为 uint16 声明 使用 SafeMath16 + + 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 { + // 注意: 我们选择不处理2038年问题,所以不用担心 readyTime 的溢出 + // 反正在2038年我们的APP早完蛋了 + uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1; + zombieToOwner[id] = msg.sender; + // 3. 在这里使用 SafeMath 的 `add` 方法: + 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); + } + + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"); + } + } + "ownable.sol": | + pragma solidity ^0.4.19; + /** + * @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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath32 + * @dev SafeMath library implemented for uint32 + */ + library SafeMath32 { + + function mul(uint32 a, uint32 b) internal pure returns (uint32) { + if (a == 0) { + return 0; + } + uint32 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint32 a, uint32 b) internal pure returns (uint32) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint32 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint32 a, uint32 b) internal pure returns (uint32) { + assert(b <= a); + return a - b; + } + + function add(uint32 a, uint32 b) internal pure returns (uint32) { + uint32 c = a + b; + assert(c >= a); + return c; + } + } + + /** + * @title SafeMath16 + * @dev SafeMath library implemented for uint16 + */ + library SafeMath16 { + + function mul(uint16 a, uint16 b) internal pure returns (uint16) { + if (a == 0) { + return 0; + } + uint16 c = a * b; + assert(c / a == b); + return c; + } + + function div(uint16 a, uint16 b) internal pure returns (uint16) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint16 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + function sub(uint16 a, uint16 b) internal pure returns (uint16) { + assert(b <= a); + return a - b; + } + + function add(uint16 a, uint16 b) internal pure returns (uint16) { + uint16 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./ownable.sol"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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); + } + + } +--- + +太好了,这下我们的 ERC721 实现不会有溢出或者下溢了。 + +回头看看我们在之前课程写的代码,还有其他几个地方也有可能导致溢出或下溢。 + +比如, 在 ZombieAttack 里面我们有: + +``` +myZombie.winCount++; +myZombie.level++; +enemyZombie.lossCount++; +``` + +我们同样应该在这些地方防止溢出。(通常情况下,总是使用 SafeMath 而不是普通数学运算是个好主意,也许在以后 Solidity 的新版本里这点会被默认实现,但是现在我们得自己在代码里实现这些额外的安全措施)。 + +不过我们遇到个小问题 — `winCount` 和 `lossCount` 是 `uint16`, 而 `level` 是 `uint32`。 所以如果我们用这些作为参数传入 SafeMath 的 `add` 方法。 它实际上并不会防止溢出,因为它会把这些变量都转换成 `uint256`: + +``` +function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; +} + +// 如果我们在`uint8` 上调用 `.add`。它将会被转换成 `uint256`. +// 所以它不会在 2^8 时溢出,因为 256 是一个有效的 `uint256`. +``` + +这就意味着,我们需要再实现两个库来防止 `uint16` 和 `uint32` 溢出或下溢。我们可以将其命名为 `SafeMath16` 和 `SafeMath32`。 + +代码将和 SafeMath 完全相同,除了所有的 `uint256` 实例都将被替换成 `uint32` 或 `uint16`。 + +我们已经将这些代码帮你写好了,打开 `safemath.sol` 合约看看代码吧。 + +现在我们需要在 ZombieFactory 里使用它们。 + +## Putting it to the Test + +分配: + +1. 声明我们将为 `uint32` 使用`SafeMath32`。 + +2. 声明我们将为 `uint16` 使用`SafeMath16`。 + +3. 在 ZombieFactory 里还有一处我们也应该使用 SafeMath 的方法, 我们已经在那里留了注释提醒你。 diff --git a/zh/5/12-safemath-4.md b/zh/5/12-safemath-4.md new file mode 100644 index 0000000000..788d601e0f --- /dev/null +++ b/zh/5/12-safemath-4.md @@ -0,0 +1,382 @@ +--- +title: SafeMath 第4部分 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "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 onlyOwnerOf(_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); + } + } + } + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 = zombies[_zombieId].level.add(1); + } + + function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + using SafeMath32 for uint32; + using SafeMath16 for uint16; + + 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] = ownerZombieCount[msg.sender].add(1); + 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": | + pragma solidity ^0.4.19; + /** + * @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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + 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 = randNonce.add(1); + return uint(keccak256(now, msg.sender, randNonce)) % _modulus; + } + + function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) { + Zombie storage myZombie = zombies[_zombieId]; + Zombie storage enemyZombie = zombies[_targetId]; + uint rand = randMod(100); + if (rand <= attackVictoryProbability) { + myZombie.winCount = myZombie.winCount.add(1); + myZombie.level = myZombie.level.add(1); + enemyZombie.lossCount = enemyZombie.lossCount.add(1); + feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); + } else { + myZombie.lossCount = myZombie.lossCount.add(1); + enemyZombie.winCount = enemyZombie.winCount.add(1); + _triggerCooldown(myZombie); + } + } + } +--- + +真棒,现在我们已经为我们的DApp 里面用到的 `uint` 数据类型都实现了 SafeMath 了。 + +让我们把 `ZombieAttack` 里所有潜在的问题都修复了吧。 (其实在 `ZombieHelper` 里也有一处 `zombies[_zombieId].level++;` 需要修复,不过我们已经帮你做好了,这样我们就不用再来一章了 😉)。 + +## 实战演习 + +放心大胆去对 `ZombieAttack` 里所有的 `++` 操作都使用 SafeMath 方法吧。为了方便你找,我们已经在相应的地方留了注释给你。 diff --git a/zh/5/13-comments.md b/zh/5/13-comments.md new file mode 100644 index 0000000000..ef8fd2c198 --- /dev/null +++ b/zh/5/13-comments.md @@ -0,0 +1,461 @@ +--- +title: 注释 +actions: ['checkAnswer', 'hints'] +requireLogin: true +material: + editor: + language: sol + startingCode: + "zombieownership.sol": | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + /// TODO: 把这里变成 natspec 标准的注释把 + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCountmsg.sender[].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } + "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 onlyOwnerOf(_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) onlyOwnerOf(_zombieId) { + zombies[_zombieId].name = _newName; + } + + function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_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 onlyOwnerOf(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 onlyOwnerOf(_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"; + import "./safemath.sol"; + + contract ZombieFactory is Ownable { + + using SafeMath for uint256; + + 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; + } + + } + "safemath.sol": | + pragma solidity ^0.4.18; + + /** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ + library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } + } + "erc721.sol": | + contract ERC721 { + event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function transfer(address _to, uint256 _tokenId) public; + function approve(address _to, uint256 _tokenId) public; + function takeOwnership(uint256 _tokenId) public; + } + answer: | + pragma solidity ^0.4.19; + + import "./zombieattack.sol"; + import "./erc721.sol"; + import "./safemath.sol"; + + contract ZombieOwnership is ZombieAttack, ERC721 { + + using SafeMath for uint256; + + mapping (uint => address) zombieApprovals; + + function balanceOf(address _owner) public view returns (uint256 _balance) { + return ownerZombieCount[_owner]; + } + + function ownerOf(uint256 _tokenId) public view returns (address _owner) { + return zombieToOwner[_tokenId]; + } + + function _transfer(address _from, address _to, uint256 _tokenId) private { + ownerZombieCount[_to] = ownerZombieCount[_to].add(1); + ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1); + zombieToOwner[_tokenId] = _to; + Transfer(_from, _to, _tokenId); + } + + function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + _transfer(msg.sender, _to, _tokenId); + } + + function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) { + zombieApprovals[_tokenId] = _to; + Approval(msg.sender, _to, _tokenId); + } + + function takeOwnership(uint256 _tokenId) public { + require(zombieApprovals[_tokenId] == msg.sender); + address owner = ownerOf(_tokenId); + _transfer(owner, msg.sender, _tokenId); + } + } +--- + +僵尸游戏的 Solidity 代码终于完成啦。 + +在以后的课程中,我们将学习如何将游戏部署到以太坊,以及如何和 Web3.js 交互。 + +不过在你离开第五课之前,我们来谈谈如何 **给你的代码添加注释**. + +## 注释语法 + +Solidity 里的注释和 JavaScript 相同。在我们的课程中你已经看到了不少单行注释了: + +``` +// 这是一个单行注释,可以理解为给自己或者别人看的笔记 +``` + +只要在任何地方添加一个 `//` 就意味着你在注释。如此简单所以你应该经常这么做。 + +不过我们也知道你的想法:有时候单行注释是不够的。毕竟你生来话痨。 + +所以我们有了多行注释: + +``` +contract CryptoZombies { + /* 这是一个多行注释。我想对所有花时间来尝试这个编程课程的人说声谢谢。 + 它是免费的,并将永远免费。但是我们依然倾注了我们的心血来让它变得更好。 + + 要知道这依然只是区块链开发的开始而已,虽然我们已经走了很远, + 仍然有很多种方式来让我们的社区变得更好。 + 如果我们在哪个地方出了错,欢迎在我们的 github 提交 PR 或者 issue 来帮助我们改进: + https://github.com/loomnetwork/cryptozombie-lessons + + 或者,如果你有任何的想法、建议甚至仅仅想和我们打声招呼,欢迎来我们的电报群: + https://t.me/loomnetworkcn + */ +} +``` + +特别是,最好为你合约中每个方法添加注释来解释它的预期行为。这样其他开发者(或者你自己,在6个月以后再回到这个项目中)可以很快地理解你的代码而不需要逐行阅读所有代码。 + +Solidity 社区所使用的一个标准是使用一种被称作 **_natspec_** 的格式,看起来像这样: + +``` +/// @title 一个简单的基础运算合约 +/// @author H4XF13LD MORRIS 💯💯😎💯💯 +/// @notice 现在,这个合约只添加一个乘法 +contract Math { + /// @notice 两个数相乘 + /// @param x 第一个 unit + /// @param y 第二个 uint + /// @return z (x * y) 的结果 + /// @dev 现在这个方法不检查溢出 + function multiply(uint x, uint y) returns (uint z) { + // 这只是个普通的注释,不会被 natspec 解释 + z = x * y; + } +} +``` + +`@title`(标题) 和 `@author` (作者)很直接了. + +`@notice` (须知)向 **用户** 解释这个方法或者合约是做什么的。 `@dev` (开发者) 是向开发者解释更多的细节。 + +`@param` (参数)和 `@return` (返回) 用来描述这个方法需要传入什么参数以及返回什么值。 + +注意你并不需要每次都用上所有的标签,它们都是可选的。不过最少,写下一个 `@dev` 注释来解释每个方法是做什么的。 + +## 实战演习 + +如果你还没注意到:CryptoZombies 的答案检查器在工作的时候将忽略所有的注释。所以这一章我们其实无法检查你的 natspec 注释了。全靠你自己咯。 + +话说回来,到现在你应该已经是一个 Solidity 小能手了。我们就假定你已经学会这些了。 + +大胆去做些尝试把,给 `ZombieOwnership` 加上一些 natspec 标签: + +1. `@title` — 例如:一个管理转移僵尸所有权的合约 + +2. `@author` — 你的名字 + +3. `@dev` — 例如:符合 OpenZeppelin 对 ERC721 标准草案的实现 diff --git a/zh/5/14-wrappingitup.md b/zh/5/14-wrappingitup.md new file mode 100644 index 0000000000..0013447b2a --- /dev/null +++ b/zh/5/14-wrappingitup.md @@ -0,0 +1,38 @@ +--- +title: 放在一起 +actions: ['checkAnswer', 'hints'] +requireLogin: true +skipCheckAnswer: true +material: + saveZombie: false + zombieDeck: + zombie: + lesson: 5 + hideSliders: true + answer: 1 +--- + +恭喜你!这些就是第五课的全部啦。 + +作为奖赏,我们送给你了一个10级僵尸:**H4XF13LD MORRIS 💯💯😎💯💯** ! + +(天啊,传奇的**H4XF13LD MORRIS 💯💯😎💯💯** 僵尸!) + +这下你的僵尸大军有4个僵尸啦。 + +在你继续前,你可以点击每个僵尸来给它们起一个新名字, (注: **H4XF13LD MORRIS 💯💯😎💯💯** 这个梗来自于一个在2000年左右流行的古老游戏,我们的开发者觉得它很酷,你也可以给它起一个你觉得很酷的名字,比如“隔壁老王”或者“绿帽僵尸”😏)。 + +## 总结一下 + +这节课里面我们学到了 + +- 代币, ERC721 标准,以及可交易的物件/僵尸 +- 库以及如何使用库 +- 如何利用 SafeMath 来防止溢出和下溢 +- 代码注释和 natspec 标准 + +这节教程完成了我们游戏的 Solidity 代码(仅针对当下来说,未来的课程我们也许会加入更多进去)。 + +在接下来的两节课中,我们将学习如何将游戏部署到以太坊以及和 **_web3.js_** 交互 (这样你就能为你的 DApp 打造一个界面了 )。 + +继续玩儿或者重命名你的僵尸,然后就可以点击下一章来结束本节教程了。 diff --git a/zh/5/15-lessoncomplete.md b/zh/5/15-lessoncomplete.md new file mode 100644 index 0000000000..33b1d2f679 --- /dev/null +++ b/zh/5/15-lessoncomplete.md @@ -0,0 +1,7 @@ +--- +title: 第五课结束! +actions: ['checkAnswer', 'hints'] +material: + lessonComplete: + answer: 1 +--- diff --git a/zh/index.json b/zh/index.json index e35c96eaea..ff86e74fd8 100644 --- a/zh/index.json +++ b/zh/index.json @@ -7,7 +7,7 @@ "syncing": "正在同步...", "telegram": { "name": "Telegram", - "openChat": "Open Chat", + "openChat": "加入群聊", "action": "加入我们的Telegram", "link": "https://t.me/loomnetworkCN" }, @@ -22,19 +22,19 @@ "lessonMenu": { "pageTitle": "@:base.name | @:base.tagLine", "itemLabel": "教程 {lessonNum}: {title}", - "signIn": "登陆后同步进度", + "signIn": "登录后同步进度", "progressLoaded": "进度已同步" }, "overview": { "pageTitle": "@:base.name {lesson} | @:base.tagLine", "presents": "隆重宣布", "title": "教程 {lessonNum}: {title}", - "signInPrompt": "从这章开始需要同步以前建立的僵尸,请注册或者登陆:", - "signInButton": "登陆", - "levelUpText": "这段课程我们会用到之前建立僵尸", - "missingZombieText": "亲,没有查到课程{lessonNum}的僵尸。如果没有完成的话请现在去完成:", + "signInPrompt": "从这章开始需要同步以前生成的僵尸,请注册或者登录:", + "signInButton": "登录", + "levelUpText": "这段课程我们会用到之前生成的僵尸", + "missingZombieText": "亲,没有检测到课程{lessonNum}的僵尸。如果没有完成的话请现在去完成:", "fixMissingZombieText": "如果已经完成了第{lessonNum}课程,这个提醒可能是错误的,你可以跳回到之前的课程{generateNewZombie},进度会保留。", - "generateNewZombieLink": "从新同步僵尸", + "generateNewZombieLink": "重新同步僵尸", "areYouReady": "少年,准备开始了吗?", "resumeLesson": "跳到云尖, 第{lessonNum}课" }, @@ -53,68 +53,84 @@ }, "lessonComplete": { "shareLinksAction": "点击一下按钮后分享:", - "signIn": "登陆后同步进度", + "signIn": "登录后同步进度", "goToNextLesson": "去下一个课程", "lesson1": { "header": [ - "太棒啦!你完成了CryptoZombies的第一个课程!", + "太棒啦!你完成了 CryptoZombies 的第一个课程!", "你离自己编译区块链游戏跨出了很大的一步。", "跟你的朋友们晒一晒你新的僵尸吧!", "这是你的僵尸的永久地址:" ], "footer": "去下一课程之前,请保存进度。", - "zombieDesc": "CryptoZombie一级", - "shareLinkText": "我刚完成了#CryptoZombies的第一课!学会了如何在#Ethereum上搭建游戏。看我的部队里的第一个僵尸!" + "zombieDesc": "CryptoZombie 一级", + "shareLinkText": "我刚完成了#CryptoZombies 的第一课!学会了如何在#Ethereum 上搭建游戏。看我的部队里的第一个僵尸!" }, "lesson2": { "header": [ - "你好棒!太棒啦!你完成了CryptoZombies的{lesson2}!", + "你好棒!太棒啦!你完成了 CryptoZombies 的{lesson2}!", "成功解锁了:", "{zombieName}被升级到二级!", "无名~ 僵尸已加入你的部队! (别担心,第三节课里你会有机会改它的名字)", - "晒一晒你的CryptoKitty-猎杀者!和你的朋友们分享!", - "这是你的僵尸的永久地址,让你的朋友跟你一起猎杀CryptoKitties:" + "晒一晒你的 CryptoKitty-猎杀者!和你的朋友们分享!", + "这是你的僵尸的永久地址,让你的朋友跟你一起猎杀 CryptoKitties:" ], "footer": [ - "喜欢CryptoZombies? 跟我们打声招呼!", + "喜欢 CryptoZombies? 跟我们打声招呼!", "我们大家都在{telegramLink}, 或者可以查看{twitterLink}", "少年!第三节课等待你:" ], - "zombieDesc": "CryptoZombie二级", - "shareLinkText": "我刚完成了#CryptoZombies的第二课!我的僵尸猎杀了一个在以太坊上的CryptoKitty。看他们对打吧!" + "zombieDesc": "CryptoZombie 二级", + "shareLinkText": "我刚完成了#CryptoZombies 的第二课!我的僵尸猎杀了一个在以太坊上的 CryptoKitty。看他们对打吧!" }, "lesson3": { - "achievementsUnlocked": "Achievements Unlocked:", - "zombieUpgraded": "{zombieName} has been upgraded to Level {levelNum}!", + "achievementsUnlocked": "成就解锁了:", + "zombieUpgraded": "{zombieName} 升级到了第 {levelNum} 级!", "header": [ - "Congratulations! You have completed {lesson3} of CryptoZombies!", - "Show off your zombie army to your friends!", - "Share this URL so your friends can check out your army:" + "恭喜! 你完成了 CryptoZombies 的第 {levelNum}! 课", + "向你的朋友们炫耀你的僵尸吧", + "把这个网址发送给你的朋友,他们就能围观你的僵尸大军了:" ], "footer": [ - "喜欢CryptoZombies? 跟我们打声招呼!", + "喜欢 CryptoZombies? 跟我们打声招呼!", "我们大家都在{telegramLink}, 或者可以查看{twitterLink}", "少年!踏上你的第四课之路吧:" ], - "zombieDesc": "A Level 3 CryptoZombie", - "shareLinkText": "I just completed #CryptoZombies Lesson 3! Check out my zombie army:" + "zombieDesc": "一个 3 级 CryptoZombie", + "shareLinkText": "我刚完成了 #CryptoZombies 第三课! 快来围观我的僵尸大军:" }, "lesson4": { - "achievementsUnlocked": "Achievements Unlocked:", - "zombieUpgraded": "{zombieName} has been upgraded to Level {levelNum}!", - "newZombieAdded": "You've added a new Level 1 zombie to your army!", + "achievementsUnlocked": "成就解锁了:", + "zombieUpgraded": "{zombieName} 升级到了第 {levelNum} 级!", + "newZombieAdded": "你又给你的僵尸大军征集到了一个 1 级僵尸!", "header": [ - "Congratulations! You have completed {lesson4} of CryptoZombies!", - "Show off your zombie army to your friends!", - "Share this URL so your friends can check out your army:" + "恭喜! 你完成了 CryptoZombies 的第 {lesson4}! 课", + "向你的朋友们炫耀你的僵尸吧", + "把这个网址发送给你的朋友,他们就能围观你的僵尸大军了:" ], "footer": [ - "Lesson 5 coming in 1-2 weeks", - "You'll get an email from us as soon as it's ready.", - "In the meantime, join us on {telegramLink}, or follow our {twitterLink} to join the conversation!" + "喜欢我们的课程么? 来跟我们打声招呼鼓励一下我们吧", + "加入我们的电报群: {telegramLink}, 或者成为我们的 {twitterLink} 粉丝", + "准备好了的话,点击下面的按钮进入第五课:" + ], + "zombieDesc": "一个 {levelNum} 级CryptoZombie", + "shareLinkText": "我刚完成了 #CryptoZombies 的第四课, 并打败了邪恶的 IOTA 僵尸,来试试你的本领吧:" + }, + "lesson5": { + "achievementsUnlocked": "成就解锁了:", + "newZombieAdded": "你得到了 {zombieName}, 一个 {levelNum}级别的 CryptoZombie!", + "header": [ + "恭喜! 你完成了 CryptoZombies 的第 {lesson5}! 课", + "向你的朋友们炫耀你的僵尸吧", + "把这个网址发送给你的朋友,他们就能围观你的僵尸大军了:" ], - "zombieDesc": "A Level {levelNum} CryptoZombie", - "shareLinkText": "I just completed #CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Try it out yourself:" + "footer": [ + "第六课将在不久后发布", + "一旦发布你就会收到我们的邮件通知", + "现在,请加入我们的 {telegramLink},或者成为我们的{twitterLink} 粉丝吧" + ], + "zombieDesc": "一个 {levelNum} 级别 CryptoZombie", + "shareLinkText": "我刚完成了 #CryptoZombies 的第五课还得到了一个10级的 CryptoZombie! 来看看我的僵尸大军:" } }, "zombieBattle": { @@ -148,10 +164,10 @@ "defaultZombieDesc": "CryptoZombie一级" }, "battleArena": { - "IOTAslain": "IOTA has been slain in battle!", - "winCount": "Your Zombie's Win Count:", - "iotaLose": "IOTA's Loss Count", - "newZombie": "New Zombie Created" + "IOTAslain": "IOTA 在战斗中被打败了!", + "winCount": "你的僵尸的战胜次数:", + "iotaLose": "IOTA 的战败次数:", + "newZombie": "新僵尸生成了" }, "sharePage": { "pageTitle": "看我的CryptoZombie {zombieName}!", @@ -180,58 +196,76 @@ ] }, "lesson2": { - "zombieCardBattle": "Zombie Card Battle", + "zombieCardBattle": "僵尸卡牌大战", "love": "love", "achievementPreamble": [ - "Your friend has completed CryptoZombies Lesson 2, and upgraded {zombieName} to a CryptoKitty slayer!", - "A CryptoKitty Slayer?!", - "It turns out CryptoZombies {love} to feed on CryptoKitties.", - "When a CryptoZombie feeds on a CryptoKitty, their DNA mixes together and the CryptoKitty becomes reanimated as a CryptoZombie.", - "Go ahead, choose a CryptoKitty to attack, and see what kind of CatZombie you get!", - "Solidity Concepts Mastered", - "In order to complete Lesson 2 of CryptoZombies Code School, your friend built a {zombieCardBattle} and learned about:" + "你朋友刚完成了 CryptoZombies 的第二课, 并将 {zombieName} 升级成了一个 CryptoKitty 杀手!", + "一个 CryptoKitty 杀手?!", + "看起来我们的 CryptoZombies {love} 猎食 CryptoKitties.", + "当一个 CryptoZombie 猎食 CryptoKitty 后, 它们的 DNA 就会混合在一起,然后 CryptoKitty 就变成了变种 CryptoZombie.", + "继续玩儿,点击一个 CryptoKitty 去攻击, 然后看看你得到了什么猫僵尸!", + "Solidity 概念掌握啦", + "为了完成 CryptoZombies 的第二课, 你的朋友创建了一个 {zombieCardBattle} 并学会了:" ], "achievements": [ - "The basics of making a multi-player game", - "Interacting with other contracts on the Ethereum network", - "Organizing larger Solidity projects into multiple contracts" + "编写多人以太坊游戏的基础", + "与以太坊上的其他合约交互", + "将代码分成多个合约来管理较大的 Solidity 工程" ], "callToAction": [ - "Want to build your own CryptoZombies and join the ranks?", - "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" + "想建立你自己的 CryptoZombies 并加入排行么?", + "在 CryptoZombies 中免费学习编写你自己的以太坊游戏! 快来吧:" ] }, "lesson3": { "achievementPreamble": [ - "Your friend has completed CryptoZombies Lesson 3, and upgraded their zombie army!", - "In order to complete Lesson 3 of CryptoZombies, your friend learned about:" + "你朋友完成了 CryptoZombies 第三课, 还把僵尸大军升级了!", + "为了完成 CryptoZombies 第三课, 你朋友学到了:" ], "achievements": [ - "How to build updatable smart contracts", - "Securing contracts with contract ownership", - "Gas and gas optimization on Ethereum", - "Function modifiers and security checks" + "如何编写可更新的智能合约", + "用合约拥有权来保护合约", + "Gas 和 如何在以太坊上节约 gas 花费", + "方法修饰器和安全检查" ], "callToAction": [ - "Want to build your own CryptoZombie army and join the ranks?", - "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" + "想建立你自己的 CryptoZombies 并加入排行么?", + "在 CryptoZombies 中免费学习编写你自己的以太坊游戏! 快来吧:" ] }, "lesson4": { "achievementPreamble": [ - "Your friend has completed CryptoZombies Lesson 4, and defeated the evil IOTA zombie!", - "Go ahead and try it for yourself to the left. Use your friend's army to engage in battle!", - "In order to complete Lesson 4 of CryptoZombies, your friend learned about:" + "你朋友完成了 CryptoZombies 第四课, 还打败了 IOTA 僵尸!", + "放心大胆在左边用你朋友的僵尸来一场大战吧", + "为了完成 CryptoZombies 第四课, 你朋友学到了:" + ], + "achievements": [ + "可支付方法,以及如何利用以太坊游戏盈利", + "从智能合约中提取一台", + "随机数生成器 以及 以太坊上的安全知识", + "此外还有更多!" + ], + "callToAction": [ + "想建立你自己的 CryptoZombies 并加入排行么?", + "在 CryptoZombies 中免费学习编写你自己的以太坊游戏! 快来吧:" + ] + }, + "lesson5": { + "zombieDesc": "{name} CryptoZombie", + "achievementPreamble": [ + "你朋友完成了 CryptoZombies 第五课, 还得到了一个10级的僵尸", + "围观一下左边你朋友的僵尸大军,很震撼吧?", + "为了完成 CryptoZombies 第五课, 你朋友学到了:" ], "achievements": [ - "Payable functions, and how to earn money from your Ethereum-based games", - "Withdrawing ETH from smart contracts", - "Random number generation & security on Ethereum", - "And more!" + "代币, ERC721 标准,以及创建可交易的代币或者物件", + "Solidity 库以及如何使用", + "SafeMath 库以及如何防止溢出和下溢", + "natspec 标准以及如何给 Solidity 添加注释" ], "callToAction": [ - "Want to build your own CryptoZombie army and join the ranks?", - "Learn to build your own games on Ethereum for FREE with CryptoZombies! Get started now:" + "想建立你自己的 CryptoZombies 并加入排行么?", + "在 CryptoZombies 中免费学习编写你自己的以太坊游戏! 快来吧:" ] } } diff --git a/zh/index.ts b/zh/index.ts index 9d22c5346b..3e9e22e7ee 100644 --- a/zh/index.ts +++ b/zh/index.ts @@ -70,6 +70,23 @@ import l4_ch9 from './4/battle-09.md' import l4_ch10 from './4/wrappingitup.md' import l4_complete from './4/lessoncomplete.md' +// lesson5 +import l5_overview from './5/00-overview.md' +import l5_ch1 from './5/01-erc721-1.md' +import l5_ch2 from './5/02-erc721-2.md' +import l5_ch3 from './5/03-erc721-3.md' +import l5_ch4 from './5/04-erc721-4.md' +import l5_ch5 from './5/05-erc721-5.md' +import l5_ch6 from './5/06-erc721-6.md' +import l5_ch7 from './5/07-erc721-7.md' +import l5_ch8 from './5/08-erc721-8.md' +import l5_ch9 from './5/09-safemath-1.md' +import l5_ch10 from './5/10-safemath-2.md' +import l5_ch11 from './5/11-safemath-3.md' +import l5_ch12 from './5/12-safemath-4.md' +import l5_ch13 from './5/13-comments.md' +import l5_ch14 from './5/14-wrappingitup.md' +import l5_complete from './5/15-lessoncomplete.md' // chapterList is an ordered array of chapters. The order represents the order of the chapters. // chapter index will be 1-based and not zero-based. First chapter is 1 @@ -142,5 +159,24 @@ export default { l4_ch9, l4_ch10, l4_complete - ] + ], + + 5: [ + l5_overview, + l5_ch1, + l5_ch2, + l5_ch3, + l5_ch4, + l5_ch5, + l5_ch6, + l5_ch7, + l5_ch8, + l5_ch9, + l5_ch10, + l5_ch11, + l5_ch12, + l5_ch13, + l5_ch14, + l5_complete + ], } diff --git a/zh/lander.yml b/zh/lander.yml index baf263c97d..f67207c1fa 100644 --- a/zh/lander.yml +++ b/zh/lander.yml @@ -1,6 +1,6 @@ --- zh: - ogdescription: CryptoZombies是个在编游戏的过程中学习Solidity智能协议语言的互动教程。 - ogtitle: CryptoZombies - 编游戏的同时学习以太坊的智能协议. Loom Network产品。 + ogdescription: CryptoZombies 是个在编游戏的过程中学习 Solidity 智能协议语言的互动教程。 + ogtitle: CryptoZombies - 编游戏的同时学习以太坊的智能协议. Loom Network 产品。 telegram_open_chat: Open Chat telegram_link: https://t.me/loomnetworkCN \ No newline at end of file