Skip to content

Latest commit

 

History

History
778 lines (653 loc) · 30.1 KB

09.md

File metadata and controls

778 lines (653 loc) · 30.1 KB
title actions requireLogin material
Iscrizione (Subscribing) agli eventi
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> <script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script> </head> <body> <div id="txStatus"></div> <div id="zombies"></div> <script> var cryptoZombies; var userAccount; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); var accountInterval = setInterval(function() { // Controlla se l'account è stato modificato if (web3.eth.accounts[0] !== userAccount) { userAccount = web3.eth.accounts[0]; // Chiama alcune funzioni per aggiornare l'interfaccia utente (UI) con il nuovo account getZombiesByOwner(userAccount) .then(displayZombies); } }, 100); // Inizia qui } function displayZombies(ids) { $("#zombies").empty(); for (const id of ids) { // Cerca i dettagli degli zombi dal nostro contratto. Restituisce un oggetto `zombie` getZombieDetails(id) .then(function(zombie) { // Utilizzo dei "template literals" di ES6 per iniettare variabili nel codice HTML. // Aggiungi ognuno al nostro div #zombies $("#zombies").append(`<div class="zombie"> <ul> <li>Name: ${zombie.name}&lt;/li&gt; &lt;li&gt;DNA: ${zombie.dna}</li> <li>Level: ${zombie.level}&lt;/li&gt; &lt;li&gt;Wins: ${zombie.winCount}</li> <li>Losses: ${zombie.lossCount}&lt;/li&gt; &lt;li&gt;Ready Time: ${zombie.readyTime}</li> </ul> </div>`); }); } } function createRandomZombie(name) { // Ci vorrà del tempo, quindi aggiorna l'interfaccia utente per informare l'utente che // la transazione è stata inviata $("#txStatus").text("Creating new zombie on the blockchain. This may take a while..."); // Invia la tx (Id della transazione) al nostro contratto: return cryptoZombies.methods.createRandomZombie(name) .send({ from: userAccount }) .on("receipt", function(receipt) { $("#txStatus").text("Successfully created " + name + "!"); // La transazione è stata accettata nella blockchain, ridisegniamo l'interfaccia utente (UI) getZombiesByOwner(userAccount).then(displayZombies); }) .on("error", function(error) { // Fai qualcosa per avvisare l'utente che la sua transazione è fallita $("#txStatus").text(error); }); } function feedOnKitty(zombieId, kittyId) { $("#txStatus").text("Eating a kitty. This may take a while..."); return cryptoZombies.methods.feedOnKitty(zombieId, kittyId) .send({ from: userAccount }) .on("receipt", function(receipt) { $("#txStatus").text("Ate a kitty and spawned a new Zombie!"); getZombiesByOwner(userAccount).then(displayZombies); }) .on("error", function(error) { $("#txStatus").text(error); }); } function levelUp(zombieId) { $("#txStatus").text("Leveling up your zombie..."); return cryptoZombies.methods.levelUp(zombieId) .send({ from: userAccount, value: web3.utils.toWei("0.001", "ether") }) .on("receipt", function(receipt) { $("#txStatus").text("Power overwhelming! Zombie successfully leveled up"); }) .on("error", function(error) { $("#txStatus").text(error); }); } function getZombieDetails(id) { return cryptoZombies.methods.zombies(id).call() } function zombieToOwner(id) { return cryptoZombies.methods.zombieToOwner(id).call() } function getZombiesByOwner(owner) { return cryptoZombies.methods.getZombiesByOwner(owner).call() } window.addEventListener('load', function() { // Verifica se Web3 è stato iniettato dal browser (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Usa il provider Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Gestire il caso in cui l'utente non ha installato Metamask // Probabilmente mostrare loro un messaggio che richiede di installare Metamask } // Ora puoi avviare la tua app e accedere a web3 liberamente: startApp() }) </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; emit 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(abi.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; 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); 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 Il contratto di proprietà ha un indirizzo del proprietario e fornisce funzioni di controllo * delle autorizzazioni di base, ciò semplifica l'implementazione delle "autorizzazioni dell'utente". */ contract Ownable { address private _owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev Il costruttore di proprietà imposta il `proprietario` originale del contratto sull'account del mittente. */ 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 lo lancia automaticamente quando si divide per 0 uint256 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga 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 lo lancia automaticamente quando si divide per 0 uint32 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga 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 lo lancia automaticamente quando si divide per 0 uint16 c = a / b; // assert(a == b * c + a % b); // Non esiste un caso in cui ciò non valga 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> <script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script> </head> <body> <div id="txStatus"></div> <div id="zombies"></div> <script> var cryptoZombies; var userAccount; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); var accountInterval = setInterval(function() { // Controlla se l'account è stato modificato if (web3.eth.accounts[0] !== userAccount) { userAccount = web3.eth.accounts[0]; // Chiama alcune funzioni per aggiornare l'interfaccia utente (UI) con il nuovo account getZombiesByOwner(userAccount) .then(displayZombies); } }, 100); cryptoZombies.events.Transfer({ filter: { _to: userAccount } }) .on("data", function(event) { let data = event.returnValues; getZombiesByOwner(userAccount).then(displayZombies); }).on("error", console.error); } function displayZombies(ids) { $("#zombies").empty(); for (const id of ids) { // Cerca i dettagli degli zombi dal nostro contratto. Restituisce un oggetto `zombie` getZombieDetails(id) .then(function(zombie) { // Utilizzo dei "template literals" di ES6 per iniettare variabili nel codice HTML. // Aggiungi ognuno al nostro div #zombies $("#zombies").append(`<div class="zombie"> <ul> <li>Name: ${zombie.name}&lt;/li&gt; &lt;li&gt;DNA: ${zombie.dna}</li> <li>Level: ${zombie.level}&lt;/li&gt; &lt;li&gt;Wins: ${zombie.winCount}</li> <li>Losses: ${zombie.lossCount}&lt;/li&gt; &lt;li&gt;Ready Time: ${zombie.readyTime}</li> </ul> </div>`); }); } } function createRandomZombie(name) { // Ci vorrà del tempo, quindi aggiorna l'interfaccia utente per informare l'utente che // la transazione è stata inviata $("#txStatus").text("Creating new zombie on the blockchain. This may take a while..."); // Invia la tx (Id della transazione) al nostro contratto: return cryptoZombies.methods.createRandomZombie(name) .send({ from: userAccount }) .on("receipt", function(receipt) { $("#txStatus").text("Successfully created " + name + "!"); // La transazione è stata accettata nella blockchain, ridisegniamo l'interfaccia utente (UI) getZombiesByOwner(userAccount).then(displayZombies); }) .on("error", function(error) { // Fai qualcosa per avvisare l'utente che la sua transazione è fallita $("#txStatus").text(error); }); } function feedOnKitty(zombieId, kittyId) { $("#txStatus").text("Eating a kitty. This may take a while..."); return cryptoZombies.methods.feedOnKitty(zombieId, kittyId) .send({ from: userAccount }) .on("receipt", function(receipt) { $("#txStatus").text("Ate a kitty and spawned a new Zombie!"); getZombiesByOwner(userAccount).then(displayZombies); }) .on("error", function(error) { $("#txStatus").text(error); }); } function levelUp(zombieId) { $("#txStatus").text("Leveling up your zombie..."); return cryptoZombies.methods.levelUp(zombieId) .send({ from: userAccount, value: web3.utils.toWei("0.001", "ether") }) .on("receipt", function(receipt) { $("#txStatus").text("Power overwhelming! Zombie successfully leveled up"); }) .on("error", function(error) { $("#txStatus").text(error); }); } function getZombieDetails(id) { return cryptoZombies.methods.zombies(id).call() } function zombieToOwner(id) { return cryptoZombies.methods.zombieToOwner(id).call() } function getZombiesByOwner(owner) { return cryptoZombies.methods.getZombiesByOwner(owner).call() } window.addEventListener('load', function() { // Verifica se Web3 è stato iniettato dal browser (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Usa il provider Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Gestire il caso in cui l'utente non ha installato Metamask // Probabilmente mostrare loro un messaggio che richiede di installare Metamask } // Ora puoi avviare la tua app e accedere a web3 liberamente: startApp() }) </script> </body> </html>

Come puoi vedere, interagire con il tuo contratto tramite Web3.js è piuttosto semplice: una volta impostato l'ambiente, le funzioni call e send non sono così diverse da una normale API web.

C'è un altro aspetto che vogliamo trattare — l'iscrizione agli eventi dal tuo contratto.

In ascolto per nuovi zombi

Se ricordi, in zombiefactory.sol c'è un evento chiamato NewZombie che abbiamo lanciato ogni volta che veniva creato un nuovo zombie:

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

In Web3.js puoi registrare (subscribe) un evento in modo che il tuo provider web3 attivi qualche logica nel tuo codice ogni volta che viene lanciato:

cryptoZombies.events.NewZombie()
.on("data", function(event) {
  let zombie = event.returnValues;
  // Possiamo accedere ai 3 valori di ritorno di questo evento sull'oggetto `event.returnValues`:
  console.log("E' nato un nuovo zombi!", zombie.zombieId, zombie.name, zombie.dna);
}).on("error", console.error);

Notare che questo genererebbe un avviso ogni volta che viene creato un QUALSIASI zombi nella nostra DApp — non solo per l'utente corrente. E se volessimo solo gli avvisi per l'utente corrente?

Usando indexed (indicizzazione)

Per filtrare gli eventi ed ascoltare solo le modifiche relative all'utente corrente, il nostro contratto di Solidity dovrà usare la parola chiave indexed, come abbiamo fatto nell'evento Transfer della nostra implementazione ERC721:

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);

In questo caso, poiché _from e _to sono indexed, significa che possiamo filtrarli nel nostro listener di eventi nel nostro front-end:

// Usa `filter` per lanciare questo codice solo quando `_to` è uguale a `userAccount`
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
  let data = event.returnValues;
  // L'utente attuale ha appena ricevuto uno zombi!
  // Fai qualcosa qui per aggiornare e mostrare l'interfaccia utente
}).on("error", console.error);

Come puoi vedere, usare i campi event e indexed può essere una pratica molto utile per ascoltare le modifiche del tuo contratto e rifletterle nel front-end della tua app.

Interrogazione di eventi passati

Possiamo anche interrogare gli eventi passati usando getPastEvents ed usare i filtri fromBlock e toBlock per dare a Solidity un intervallo di tempo per i log degli eventi ("block" in questo caso fa riferimento al numero del blocco su Ethereum):

cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: "latest" })
.then(function(events) {
  // `events` è una matrice di oggetti `event` che possiamo replicare, come abbiamo fatto sopra
  // Questo codice ci fornirà un elenco di tutti gli zombi creati
});

Poiché è possibile utilizzare questo metodo per interrogare i registri degli eventi dall'inizio, questo presenta un caso d'uso interessante: Utilizzo degli eventi come forma di archiviazione più economica.

Se ricordi, il salvataggio dei dati nella blockchain è una delle operazioni più costose di Solidity. Ma l'utilizzo degli eventi è molto più economico in termini di gas.

Il compromesso è che gli eventi non sono leggibili all'interno dello stesso contratto intelligente. Ma è un caso d'uso importante da tenere a mente se si dispone di alcuni dati che si desidera registrare sulla blockchain in modo da poterli leggere in futuro dal front-end dell'app.

Ad esempio potremmo usarlo come un record storico delle battaglie zombi: potremmo creare un evento ogni volta che uno zombi attacca un altro e registrare chi vince. Il contratto intelligente non ha bisogno di questi dati per calcolare i risultati futuri, ma sono dati utili per gli utenti che possono navigare nel front-end dell'app.

Facciamo una prova

Aggiungiamo un po' di codice per ascoltare l'evento Transfer ed aggiorniamo l'interfaccia utente della nostra app se l'utente corrente riceve un nuovo zombi.

Dovremo aggiungere questo codice alla fine della funzione startApp per assicurarci che il contratto cryptoZombies sia stato inizializzato prima di aggiungere un listener di eventi.

  1. Alla fine di startApp () copia/incolla il blocco del codice di sopra che sta' in ascolto su cryptoZombies.events.Transfer

  2. Per la riga che dovrà aggiornare l'interfaccia utente, usa getZombiesByOwner(userAccount).then(displayZombies);