Skip to content

Latest commit

 

History

History
537 lines (422 loc) · 19 KB

04-calling-contract-functions.md

File metadata and controls

537 lines (422 loc) · 19 KB
title actions requireLogin material
Calling Contract Functions
verificarResposta
dicas
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> <script> var cryptoZombies; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); } function getZombieDetails(id) { return cryptoZombies.methods.zombies(id).call() } // 1. Defina `zombieToOwner` aqui // 2. Defina `getZombiesByOwner` aqui window.addEventListener('load', function() { // Verificando se o Web3 foi injetado pelo navegador (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Use o provedor de Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Caso o usuário não tem web3. Provavelmente // mostre a ele uma mensagem dizendo-lhe para instalar o Metamask // afim de usar nosso aplicativo. } // Agora você pode iniciar seu aplicativo e acessar o web3js livremente: startApp() }) </script> </body> </html>
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); } }
pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieAttack 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); } } }
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; } }
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"); } }
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); } }
/** * @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; } }
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; } }
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; }
<!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> <script> var cryptoZombies; function startApp() { var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS"; cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress); } 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() { // Verificando se o Web3 foi injetado pelo navegador (Mist/MetaMask) if (typeof web3 !== 'undefined') { // Use o provedor de Mist/MetaMask web3js = new Web3(web3.currentProvider); } else { // Caso o usuário não tem web3. Provavelmente // mostre a ele uma mensagem dizendo-lhe para instalar o Metamask // afim de usar nosso aplicativo. } // Agora você pode iniciar seu aplicativo e acessar o web3js livremente: startApp() }) </script> </body> </html>

Nosso contrato está pronto! Agora podemos usar o Web3.js para conversar com ele.

O Web3.js tem dois métodos que usaremos para chamar funções em nosso contrato: call e send.

Call

call é usado para funções view e pure. Ele só é executado no nó local e não cria uma transação no blockchain.

Recordando: view e pure são funções de leitura e não mudam de estado no blockchain. Eles também não custam nenhum gas, e o usuário não será solicitado a assinar uma transação com o MetaMask.

Usando o Web3.js, você poderia chamar uma função chamada myMethod com o parâmetro 123 da seguinte forma:

myContract.methods.myMethod(123).call()

Send

send criará uma transação e alterará os dados no blockchain. Você precisará usar send para quaisquer funções que não sejam view ou pure.

Nota: send irá enviar uma transação e exigirá que o usuário pague o gas, e irá abrir seu Metamask para solicitar uma transação. Quando usamos o Metamask como nosso provedor web3, tudo isso acontece automaticamente quando chamamos send(), e não precisamos fazer nada especial em nosso código. Muito legal!

Usando o Web3.js, você poderia "enviar" uma transação chamando uma função chamada "myMethod" com o parâmetro "123" da seguinte forma:

myContract.methods.myMethod(123).send()

A sintaxe é quase idêntica a call().

Obtendo dados de zumbis

Agora vamos olhar para um exemplo real de usar call para acessar dados em nosso contrato.

Lembre-se que fizemos nosso array de zumbis public:

Zombie[] public zombies;

Em Solidity, quando você declara uma variável public, ela cria automaticamente uma função pública "getter" com o mesmo nome. Então, se você quisesse procurar o zumbi com o id 15, você o chamaria como se fosse uma função: zombies(15).

Aqui está como nós escreveríamos uma função JavaScript em nosso front-end que pegaria um id de zumbi, consultaria nosso contrato para aquele zumbi e retornaria o resultado:

Nota: Todos os exemplos de código que estamos usando nesta lição estão usando ** versão 1.0 ** do Web3.js, que usa promises em vez de callbacks. Muitos outros tutoriais que você verá on-line estão usando uma versão mais antiga do Web3.js. A sintaxe mudou muito com a versão 1.0, então se você estiver copiando código de outros tutoriais, verifique se eles estão usando a mesma versão que você!

function getZombieDetails(id) {
  return cryptoZombies.methods.zombies(id).call()
}

// Chame a função e faça algo com o resultado:
getZombieDetails(15)
.then(function(result) {
  console.log("Zumbi 15: " + JSON.stringify(result));
});

Vamos ver o que está acontecendo aqui.

cryptoZombies.methods.zombies(id).call() irá se comunicar com o nó do provedor Web3 e informar para retornar o zumbi com o index id de Zombie[] public zombies em nosso contrato.

Observe que isso é assíncrono, como uma chamada de API para um servidor externo. Então, o Web3 retorna uma promise aqui. (Se você não está familiarizado com promises em JavaScript... Hora de fazer a lição de casa antes de continuar!)

Quando a promise é resolvida (o que significa que recebemos uma resposta do provedor web3), nosso código de exemplo continua com a instrução then, que registra result no console.

result será um objeto JavaScript que se parece com isto:

{
  "name": "H4XF13LD MORRIS'S COOLER OLDER BROTHER",
  "dna": "1337133713371337",
  "level": "9999",
  "readyTime": "1522498671",
  "winCount": "999999999",
  "lossCount": "0" // Obviamente.
}

Poderíamos então ter alguma lógica de front-end para analisar esse objeto e exibi-lo de maneira significativa no front-end.

Vamos testar

Nos adiantamos e copiamos getZombieDetails no código para você.

  1. Vamos criar uma função semelhante para zombieToOwner. Se você se lembra de ZombieFactory.sol, nós tínhamos um mapeamento parecido com:

     mapping (uint => address) public zombieToOwner;   

  Defina uma função JavaScript chamada zombieToOwner. Similar ao getZombieDetails acima, ele terá um id como parâmetro, e retornará uma chamada Web3.js para zombieToOwner em nosso contrato.

  1. Abaixo disso, crie uma terceira função para getZombiesByOwner. Se você se lembra de ZombieHelper.sol, a definição da função ficou assim:

     function getZombiesByOwner(address _owner)   

  Nossa função getZombiesByOwner tomará owner como parâmetro e retornará uma call Web3.js para getZombiesByOwner.