Skip to content

Latest commit

 

History

History
486 lines (449 loc) · 21.1 KB

02.md

File metadata and controls

486 lines (449 loc) · 21.1 KB
title actions requireLogin material
Web3 `Providers`
checkAnswer
hints
true
editor
language startingCode answer
html
index.html zombieownership.sol zombieattack.sol zombiehelper.sol zombiefeeding.sol zombiefactory.sol ownable.sol safemath.sol erc721.sol
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CryptoZombies front-end</title> <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script language="javascript" type="text/javascript" src="web3.min.js"></script> </head> <body> <script> // Empieza aquí </script> </body> </html>
pragma solidity ^0.4.25; 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) external view returns (uint256) { return ownerZombieCount[_owner]; } function ownerOf(uint256 _tokenId) external view returns (address) { 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 transferFrom(address _from, address _to, uint256 _tokenId) external payable { require (zombieToOwner[_tokenId] == msg.sender || zombieApprovals[_tokenId] == msg.sender); _transfer(_from, _to, _tokenId); } function approve(address _approved, uint256 _tokenId) external payable onlyOwnerOf(_tokenId) { zombieApprovals[_tokenId] = _approved; emit Approval(msg.sender, _approved, _tokenId); } }
pragma solidity ^0.4.25; import "./zombiehelper.sol"; contract ZombieAttack is ZombieHelper { uint randNonce = 0; uint attackVictoryProbability = 70; function randMod(uint _modulus) internal returns(uint) { randNonce = randNonce.add(1); return uint(keccak256(ab.encodePacked(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); } } }
pragma solidity ^0.4.25; 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 { address _owner = owner(); owner.transfer(address(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; } }
pragma solidity ^0.4.25; 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(abi.encodePacked(_species)) == keccak256(abi.encodePacked("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"); } }
pragma solidity ^0.4.25; 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]++; emit NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(abi.encodePacked(_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); } }
pragma solidity ^0.4.25; /** * @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 private _owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns(address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns(bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @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 { _transferOwnership(newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0)); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } }
pragma solidity ^0.4.25; /** * @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; } }
pragma solidity ^0.4.25; contract ERC721 { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CryptoZombies front-end</title> <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script language="javascript" type="text/javascript" src="web3.min.js"></script> </head> <body> <script> window.addEventListener('load', function() { // Checking if Web3 has been injected by the browser (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Use Mist/MetaMask's provider web3js = new Web3(web3.currentProvider); } else { // Handle the case where the user doesn't have Metamask installed // Probably show them a message prompting them to install Metamask } // Now you can start your app & access web3 freely: startApp() }) </script> </body> </html>

Estupendo! Ahora que ya tenemos Web3.js en nuestro proyecto, vamos a inicializarlo y hablar de la blockchain.

Lo primero que necesitamos, es un Web3 Provider.

Recuerda, que Ethereum está compuesto por nodos que a la vez todos estos comparten una copia de los mismos datos. Al configurar un proveedor Web3 en Web3.js le decimos qué nodo deberíamos de estar hablando para manejar nuestras lecturas y escrituras. Es como configurar la URL de un servidor remoto para tus llamadas API en una aplicación web tradicional.

Podrías "hostear" tu propio nodo Ethereum como proveedor. Sin embargo, hay un servicio de terceros que te hace la vida más fácil así que no necesitas mantener tu propio nodo Ethereum para ofrecer tu DApp a tus usuarios — Infura.

Infura

Infura es un servicio que mantiene un conjunto de nodos Ethereum con una capa de almacenamiento en caché para lecturas rápidas, el cual puedes acceder de forma gratuita a través de su API. Usando Infura comno proveedor, puedes enviar y recibir mensajes de manera confiable desde/hacia la blockchain de Ethereum sin necesidad de mantener y configurar tu propio nodo. En el siguiente ejemplo, se muestra como se puede configurar Web3 para usar Infura como proveedor Web3:

var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));

Sin embargo, dado que muchas personas serán usuarios de nuestra DApp — y estos usuarios no solo van a leer la blockchain sino también escribir en ella — vamos a necesitar una forma para que estos usuarios puedan firmar diversas transacciones con su llave privada.

Nota: Ethereum (y las Blockchains en general) usan una llave pública/privada para firmar de forma digital las transacciones. De esta manera si cambio algunos datos de la Blockchain, puedo demostrar a través de mi llave pública que fui yo quien lo firmó — pero como nadie sabe mi llave privada, nadie puede falsificar una transacción en mi nombre. La criptografía es complicada, así que a menos que seas un experto en seguridad y realmente sepas lo que estás haciendo, probablemente no sea una buena idea tratar de administrar manuealmente las claves privadas de los usuarios en el front-end de nuestra aplicación. Per afortunadamente no necesitamos hacerlo — hoy en día ya hay servicios que manejan estas llaves por nosotros. El más popular de estos es Metamask.

Metamask

Metamask en una extensión de navegador para Chrome y Firefox que permite a los usuarios administrar de forma segura sus cuentas Ethereum y claves privadas, y usar estas cuentas para interactuar con sitios web que usan Web3.js. (Si aún no lo tienes instalado, defenitivamente es MUY recomendable que vayas y te lo instales — entonces tu navegador ya podrá interactuar con Web3, ¡además de poder interactuar con cualquier sitio web que utilice la Blockchain de Ethereum!). Y como desarrollador, si deseas que tus usuarios puedan interactyar con tu propia DApp a través de una web en su navegador (como lo estamos haciendo con nuestro juego CryptoZombies), Definitivamente querrás que sea compatible con Metamask.

Nota: Metamask usa los servidores de Infura como un proveedor de web3, como hicimos arriba — pero también le da al usuario la opción de elegir su propio proveedor web3. Así que usando Metamask como proveedor web3, le estás dando al usuario una opción, y es una cosa menos de la que debes preocuparte en tu aplicación.

Usando el proveedor web3 de Metamask

Metamask inyecto su proveedor web3 en el navegador en el objeto JavaScript global web3. Entonces, tu applicación puede verificar si web3 existe, y si usa web3.currentProvider como proveedor. Aquí abajo te hemos dejado una plantilla de código proporcionado por Metamask para saber cómo detectar si nuestro usuario tiene Metamask instalado, y si no lo tiene el navegador le dará un aviso diciendo que necesitará instalar Metamask para usar nuestra aplicación:

window.addEventListener('load', function() {
  // Aquí se comprueba si Web3 ha sido inyectado por el navegador (Mist/Metamask)
  if (typeof web3 !== 'undefined') {
    // Usar el proveedor Mist/MetaMask
    web3js = new Web3(web3.currentProvider);
  } else {
    // Esto se activará si el usuario no tiene instalado Mist/Metamask. Sería 
    // recomendable avisar al usuario de que debe instalarse Misk/Metamask
    // para poder usar nuestra DApp.
  }
  // Ahora ya puedes iniciar tu DApp y acceder a Web3.js libremente:
  startApp()
})

Puedes usar este código en todas tus aplicaciones que exijan tener instado Metamask.

Nota: Hay otros programas de administración de claves privadas que sus usuarios podrían estar usando además de MetaMask, como por ejemplo el navegador web Mist. No obstante, todos implementan un patrón de inyección de la variable web3, por lo que el método que describimos aquí para detectar el proveedor web3 del usuario también funcionará para estos.

Póngalo a prueba

Hemos creado algunas etiquetas de script antes del cierre </body> en tu archivo HTML. Podemos escribir tu código de JavaScript para esta lección aquí.

  1. Dirígete arriba y copia/pega la plantilla de código para detectar si nuestros usuarios tienen instalado Metamask. Es el bloque que comienza con window.addEventListener.