Crypto Zombies

Tipos de datos

Variables

uint dnaDigits = 16; //unsigned integer

Estructuras

struct Zombie {
        string name;
        uint dna;
}

Arrays

uint[] memory values = new uint[](3);

Mapas o diccionarios

msg.sender es una variable global que se refiere a la dirección de la persona (o contact) que llamó a la función actual.

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

zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender]++;

Declaración de una función

function _createZombie(string memory _name, uint _dna) private {

}

El guión bajo lo ponemos para variables y funciones privadas.

Modificafores de acceso

  • public: La función será accesible desde cualquier sitio.
  • private: La función será accesible sólo desde el propio contract.
  • internal: La función será accesible sólo desde el propio contract y desde los contract hijos.
  • external: La función sólo será accesible desde contract externos.

Función que devuelve algo

Debemos especificar el tipo de dato devuelto en su definición.

function getZombiesByOwner(address _owner) external view returns(uint  [] memory){

Algunas funciones básicas

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

Procesar funciones que devuelven múltiples valores

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();
}

Eventos

emit NewZombie(id, _name, _dna);
event NewZombie(uint zombieId, string name, uint dna);

Encriptar un valor

La función keccak256 es una versión del algoritmo de encriptación SHA3. Esta función convierte la entrada en un número hexadecimal de 256 bits. Un pequeño cambio en la entrada repercutirá en un gran cambio en la salida.

Esta función espera un único parámetro de tipo bytes. Para empaquetar la entrada en el formato correcto llamaremos a abi.encodePacked.

uint(keccak256(abi.encodePacked('amor')));

Lanzar una excepción si se cumple no se cumple una condición

De esta forma, si el usuario no tiene ningún zombie, podrá continuar con el código, pero si ya tiene alguno se lanzará una excepción.

require(ownerZombieCount[msg.sender] == 0);

Herencia

contract Doge {
  
}

import "./dogecontract.sol";

contract BabyDoge is Doge {
  
}

Almacenando información

Storage: la información es almacenada de manera permanente en el blockchain. Las variables definidas fuera de una función son de tipo storage por defecto.

Memory: la información es almacenada de manera temporal y borrada entre llamadas al contract. Las variables definidas dentro de una función son de tipo memory por defecto.

Interfaces

Las necesitaremos para que nuestro contract pueda comunicarse con otro contract que no poseemos.

En una interfaz las funciones no tienen cuerpo.

Sólo definiremos en la interfaz las funciones de otros contracts con las que queremos interactuar.

Al incluir una interfaz en nuestro código, nuestro contract sabrá que apariencia tiene la función del otro contract con la que queremos interactuar, como llamarla y que tipo de respuesta podemos esperar.

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

Luego, para poder usar una interfaz tendremos que inicializarla:

NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);

Conceptos

Una vez que despliegas un contract en Ethereum, será inmutable. Por tanto, si tiene errores, no podrás modificarlo. Tendrás que decir a tus usuarios que utilicen un Contract ubicado en una dirección diferente.

Seguridad

Si tenemos un Contract que a su vez utiliza otro Contract llamado Kitty, tendremos que indicar la address de Kitty. Como nuestro código es inmutable, si Kitty tuviese errores, afectará a nuestro código y no podríamos modificarlo. Por tanto, interesante definir porciones de código que podamos alimentar desde fuera. El problema es que si las hacemos públicas, cualquier podría modificarlas. Si heredamos del Contract Ownable, podemos utilizar el modificar OnlyOwner, que nos permitirá ser los únicos que podamos modificar la address.

contract ZombieFactory is Ownable {
contract ZombieFeeding is ZombieFactory {

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

GAS

En Solidity, los usuarios deben pagar con Gas cada vez que ejecuten una función en nuestra DApp.

Cuanto más compleja es la operación que el usuario quiere realizar, mayor será el coste en gas. Cada operación individual tiene un coste en gas basado en los recursos de computación necesarios para ejecutar esa función. Por ejemplo, escribir en el storage tiene un coste mucho mayor que añadir dos enteros. El coste total en gas de una función es la suma de los costes de gas de ejecutar cada una de sus líneas de código.

Dado que ejecutar funciones cuesta dinero real a los usuarios, la optimización de código en Ethereum is mucho más importante que en otros lenguajes.

Cuando ejecutas una función, cada nodo de la red necesita ejecutar la misma función para verificar su salida.

La operación más costosa que puedes realizar es hacer cambios en el storage, ya que estos cambios deberán propagarse por toda la red blockchain. Por tanto, sólo escribiremos el el storage en el caso de que sea estrictamente necesario.

Hay varios tipos de unisigned integer: uint (equivalente a uint256), uint8, uint16, uint32… Usar uint, nmormalmentem, no consume mas gas que cualquier otro unsigned int. Sin embargo, ajustar el tipo de dato dentro de un struct si que reduce la cantidad de recursos consumidos.

Además, es mejor agrupar los datos del mismo tipo juntos, por ejemplo, un struct con los datos  uint c; uint32 a; uint32 b; consumirá menos recursos que uno con los datos uint32 a; uint c; uint32 b; porque los datos uint32 están agrupados.

Las funciones con el modificador view no cuestan gas porque no modifican el blockchain.

Time Units

La variable now devuelve los milisegundos que han pasado desde 1970.

Solidity también tiene unidades de tiempo como seconds, minutes, hours, days, weeks y years.

Modificadores

Los modificadores modifican la función a la que están vinculados.

// Modifier that requires this user to be older than a certain age:
modifier olderThan(uint _age, uint _userId) {
  require(age[_userId] >= _age);
  _;
}

// Must be older than 16 to drive a car (in the US, at least).
// We can call the `olderThan` modifier with arguments like so:
function driveCar(uint _userId) public olderThan(16, _userId) {
  // Some function logic
}