Skip links
erc20 rsk openzeppelin logos

Desarrollo de Token ERC20 en RSK con OpenZeppelin

Introducción

En el último artículo (en ingles), hemos visto cómo construir un nodo RSK en nuestra computadora, seleccionar la red adecuada para el desarrollo, configurar Tuffle para conectar y desplegar nuestros futuros contratos, agregar cuentas a nuestro nodo y obtener fondos para usarlos para pagar el gas.

Debería tener ahora su nodo en la red seleccionada totalmente sincronizado, y al menos una cuenta con fondos configurados en los archivos de configuración de Truffle y del nodo RSK para nuestras implementaciones.

En este artículo, discutiremos el despliegue y la interacción de Smart-Contracts sobre la red RSK. Nuestro contrato será un token ERC20, basado en las bibliotecas de OpenZeppelin, y lo implementaremos directamente en el Mainnet.

Creando el Contrato

Lo primero que debemos lograr es saber cómo usar Truffle.

Cuando lo hacemos

$ truffle init

Dentro de una carpeta vacía, además de crear el archivo de configuración, también creamos carpetas para nuestros proyectos y el contrato de migración para mantener un registro de los cambios sobre el mismo contrato.

Los archivos de código .sol de los contratos se encuentran en

~/Truffle/contracts

Los scripts de migración están en

~/Truffle/migrations

Los contratos compilados están en

~/Truffle/build

Y la prueba está en

~/Truffle/test

Trabajaremos solo en las primeras 2 carpetas por ahora.

Dentro de la carpeta Truffle, importamos las bibliotecas de OpenZeppelin con

$ npm install -E openzeppelin-solidity

Estas bibliotecas instalarán no solo las bibliotecas principales de nuestro token, sino también las bibliotecas de propiedad, matemáticas seguras y muchas otras instalaciones. Vale la pena mencionar que estas bibliotecas han sido revisadas para lograr altos estándares de seguridad, por lo que los contratos que dependen de ellas son menos susceptibles a la piratería cuando se usan correctamente.

Nuestras bibliotecas se instalarán en

~/Truffle/node_modules/openzeppelin-solidity/contracts

Después de eso, podemos importar una biblioteca ABCD.sol a nuestro contrato de esta manera:

import 'zeppelin-solidity/contracts/token/ERC20/ABCD.sol';

Para crear nuestro Token ERC20, importaremos 2 bibliotecas de ese repositorio: el StandardToken.sol, que tiene la funcionalidad principal de un Token y ya importa un montón de bibliotecas más por sí mismo como SafeMath.sol y Ownable.sol. Esto nos permite establecer el control del propietario sobre las funciones en los contratos.

Para heredar los atributos y funciones de las bibliotecas, simplemente definimos nuestro contrato como StandardToken y como Ownable usando la palabra clave “es” de esta manera:

contract CoinFabrikToken is StandardToken, Ownable { }

Después de eso, tenemos todas las funciones de esas bibliotecas y de sus bibliotecas importadas hacia arriba.

A continuación, definimos el nombre del Token como CoinFabrik, su símbolo, 18 decimales para la precisión del Token (el estándar en redes similares a Ethereum, que nos da la posibilidad de usar funciones de conversión Ether de web3) y el suministro inicial de tokens a 1000 Me gusta esto:

string public name = 'CoinFabrik';
string public symbol = 'CF';
uint8 public decimals = 18;
uint public INITIAL_SUPPLY = 1000;

También vamos a crear otra cadena, una variable no pública no relevante para la funcionalidad Token, para mostrar el uso de las propiedades de la biblioteca Ownable, que solo permite que el creador interactúe con algunas funciones designadas. Ya veremos eso más tarde.

Con nuestros parámetros ya definidos, ahora es el momento de asignarlos a las variables Token a través de la función constructora. Hasta ahora, la función constructora se definía como una función que tenía el mismo nombre que el contrato, pero de ahora en adelante, habrá una función llamada “constructor ()” ya definida que reemplazará el método anterior. El compilador de Solidity te avisará si llamas al constructor como antes.

El número de INITIAL_SUPPLY multiplicado por la precisión de los decimales se asignará a la totalSuply_ del contrato de BasicToken con

totalSupply_ = INITIAL_SUPPLY * (10 ** uint (decimales));

Y deposítalos en la cuenta del creador

balances[msg.sender] = totalSupply_;

Con esto, tenemos un Token simple y estándar listo para ser utilizado pero, como dijimos, vamos a agregar algunas funcionalidades usando el contrato de Ownable. Primero, definiremos un par de funciones: una que modifique el estado de nuestra variable no pública, pero solo si tiene permisos de propietario, y la otra, que devuelve el mensaje de la cadena. Las definiciones son las siguientes:

function setON(string _n) public onlyOwner returns (bool) {
    Owner = _n;
    return true;
  }

  function getON() public view returns (string) {
    return Owner;
  }

Ambos son públicos, por lo que cualquiera puede intentar llamarlos, pero para el primero, solo la dirección del propietario no causará una reversión. Si usted es el propietario y se llama a la función, la cadena se guarda en nuestra variable Owner (con letras mayúsculas) y también devolverá un valor verdadero que podemos verificar en la transacción.

Como la variable Owner no es pública y no tiene un Getter, necesitamos una función que devuelva el valor de la variable sin cambiar el estado de la cadena de bloques. Esta es la segunda función.

También crearemos una función alternativa que emite un evento si alguien llama erróneamente nuestro contrato

function () public payable {
    if (msg.value > 0) {
      emit Yes('Thanks for donating SBTC! :)');
    } else {
      emit No('Error 404: Function not found :P');
    }
}

Finalmente, agregamos una capacidad destruible al contrato en el que el propietario es el único que puede ejecutarlo.

Nuestro Token simple está terminado. El código todo juntosdebería verse

pragma solidity ^0.4.17;

import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol';
import "zeppelin-solidity/contracts/ownership/Ownable.sol";

contract CoinFabrikToken is StandardToken, Ownable {
  string public name = 'CoinFabrik';
  string public symbol = 'CF';
  uint8 public decimals = 18;
  uint public INITIAL_SUPPLY = 1000;

  string Owner;

  event Yes(string);
  event No(string);

  constructor() public {
    totalSupply_ = INITIAL_SUPPLY * (10**uint(decimals));
    balances[msg.sender] = totalSupply_;
  }

  function setON(string _n) public onlyOwner returns (bool) {
    Owner = _n;
    return true;
  }

  function getON() public view returns (string) {
    return Owner;
  }

  function () public payable {
    if (msg.value > 0) {
      emit Yes('Thanks for donating SBTC! :)');
    } else {
      emit No('Error 404: Function not found :P');
    }
  }

  function destroy() public onlyOwner {
    selfdestruct(owner);
  }

}

Creando la Migración

Para cada contrato, necesitamos decirle a Truffle qué contrato es el que queremos implementar y dónde podemos encontrarlo. Esto se hace a través de un archivo de migración en la carpeta ~ / Truffle / migrations.

La migración 02_deploy_token.js debería verse como

var CoinFabrikToken = artifacts.require("./CoinFabrikToken.sol");
module.exports = function(deployer) {
  deployer.deploy(CoinFabrikToken);
};

Hemos configurado Truffle, nuestro nodo está sincronizado, nuestro contrato ya está escrito y nuestra migración configurada; es tiempo de implementación.

Implementación

Si hemos detenido nuestro nodo anteriormente, lo pondremos nuevamente en línea y luego lo conectaremos con Truffle con

$ sudo service rsk start
$ cd ~/Truffle/ && truffle console --network rsk

Allí, compilamos nuestro contrato con

truffle(rsk)> compile --all

No debemos recibir ningún error o advertencia para nuestro contrato. Luego migramos nuestro contrato con

truffle(rsk)> migrate --reset

Dado que el tiempo es caro, podemos ejecutar ambos comandos en una línea con

truffle(rsk)> migrate --all --reset

El contrato de migración se implementará primero. Truffle nos proporciona los hashes de transacción de cada operación, por lo que podemos verificar los detalles o los registros más adelante. Aquí está el resultado completo que he recibido

truffle(rsk)> migrate --all --reset
Compiling ./contracts/CoinFabrikToken.sol...
Compiling ./contracts/Migrations.sol...
Compiling zeppelin-solidity/contracts/math/SafeMath.sol...
Compiling zeppelin-solidity/contracts/ownership/Ownable.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/BasicToken.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol...
Compiling zeppelin-solidity/contracts/token/ERC20/StandardToken.sol...
Writing artifacts to ./build/contracts

Using network 'rsk'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xf00d4ecf2b5752022384f7609fe991aa72dda00a0167a974e8c69864844ae270
  Migrations: 0x1dc2550023bc8858a7e5521292356a3d42cdcbe9
Saving successful migration to network...
  ... 0x3e759e8ff8a7b8e47a441481fa5573ccf502b83f3d591ad3047e622af0f9169e
Saving artifacts...
Running migration: 2_deploy_token.js
  Deploying CoinFabrikToken...
  ... 0x300c8bb1e434e2aa4b13dcc76087d42fcbe0cb953989ca53a336c59298716433
  CoinFabrikToken: 0xc341678c01bcffa4f7362b2fceb23fbfd33373ea
Saving successful migration to network...
  ... 0x71771f7ee5d4e251e386979122bdda8728fa519d95a054572751bb10d40eb8c5
Saving artifacts...

Si verificamos las transacciones, podemos calcular el costo de gas de todo el proceso de implementación. En mi caso, fue: gas 2340788 (277462 + 42008 + 1994310 + 27008).

Entonces, al cambiarlo a SBTC real, obtenemos 2340788 * 183000000/10 ^ 18 = 0,000428364 SBTC. Eso es alrededor de ~ 4 USD en el momento de escribir este artículo.

Nuestro contrato ahora está implementado en 0xc341678c01bcffa4f7362b2fceb23fbfd33373ea.

Felicidades!

Interacción con el contrato

Simplificación de sintaxis

Con la dirección dada por la migración de Truffle, y con la ABI del contrato, creamos una instancia de la misma para que la sintaxis sea más fácil para manejar las funciones. Para hacer esto, después de implementarlo, escribimos

truffle(rsk)> var cfToken = web3.eth.contract(CoinFabrikToken.abi).at(CoinFabrikToken.address)

En caso de que el contrato ya esté implementado, y conociendo su dirección y ABI, podemos simplemente hacer

truffle(rsk)> var cfToken = web3.eth.contract(‘Contract_ABI’).at(‘Contract_ADDRESS’)

Donde Contract_ABI es el ABI comprimido en una línea y Contract_ADDRESS no necesita explicación.

Creé 2 cuentas antes, y ahora las cambiamos de nombre por conveniencia

truffle(rsk)> var acc0 = web3.eth.accounts[0]
truffle(rsk)> var acc1 = web3.eth.accounts[1]

acc0 es el que implementó el contrato. Acc0 se agregó a los archivos de configuración truffle.js y node.conf.

Control de propiedad

Primero probaremos la función de propiedad de nuestro contrato utilizando la biblioteca que hemos discutido.

Si llamamos a la función getON desde cualquier cuenta, dado que es pública y no tiene ningún problema de propiedad, obtenemos

truffle(rsk)> cfToken.getON()
''

Ahora, la función setON tiene una propiedad de propiedad. Cualquier transacción hecha desde una cuenta diferente será descartada. Vemos, por ejemplo, que intentar firmar el contrato con mi nombre desde acc1 no cambiará su valor.

truffle(rsk)> cfToken.setON('Andres Bachfischer', {from: acc1})
0x5f115190b60238240bedf36d1c5bb69a443a0f8ee971b0fc40fe5ca9c727d47c

Con el hash de la transacción vemos que el valor devuelto era falso y la función no se ejecutó correctamente. Volviendo a llamar a la función getON, vemos que la variable no cambió su valor.

Firmando ahora la misma transacción, pero desde la cuenta del propietario acc0, obtenemos un estado ‘0x01’ y la función se ejecuta correctamente.

truffle(rsk)> cfToken.setON('Andres Bachfischer', {from: acc0})
0x0c894fa7e5369573fb14addeaed4cd9d5b6cd1425cb4eeeae16cb4e1fa8e0364

Volviendo a llamar a la función getON, vemos que la biblioteca de propiedad funcionó como esperábamos.

truffle(rsk)> cfToken.getON()

Ownable.sol también tiene una función que nos permite cambiar el propietario del contrato a otra dirección. No lo usaremos Sin embargo, su uso es el siguiente

truffle(rsk)> cfToken.transferOwnership(acc1, {from: acc0})

Con eso, acc1 sería el nuevo propietario del contrato.

Pasemos al Token.

Operaciones del Token

Lo primero que hacemos es verificar si los saldos del Token fueron asignados correctamente en la creación del contrato.

Comprobamos nuestros saldos en cada cuenta de esta manera:

web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = ‘1000’
web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = ‘0’

Entonces podemos ver que todos los tokens fueron asignados correctamente a nuestra cuenta inicial.

La primera transacción que haremos es transferir tres tokens a la segunda cuenta, acc1, tres veces.

Para hacerlo para la primera transacción

truffle(rsk)> cfToken.transfer(acc1, web3.toWei(88.8), {from: acc0})
0xd45437b777f1430e7cec57bd80b261ce8f87bf8a3f9a113fecd20563403c4d9c

truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = '733.6'
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = '266.4'

Vemos que los tokens tomados de nuestra cuenta de implementación tenían la misma cantidad que los recibidos en el acc1.

Con el contrato StandardToken también obtenemos permisos de permisos para gastar tokens en nombre de cierta cuenta, en este caso, acc1. Si queremos hacer esto antes de obtener la aprobación, la transacción fallará (estado ‘0x00’)

truffle(rsk)> cfToken.transferFrom(acc1, acc0, web3.toWei(5), {from: acc0})
0x5cee7cf60849283a0088d71483a606ba2101b500e13f972abada4f75781596bf

Después de verificar que acc0 no puede enviar desde acc1

truffle(rsk)> web3.fromWei(cfToken.allowance(acc1, acc0, {from: acc0}).toString(10)) // = '0'

Autorizamos a acc0 a gastar 10 tokens en nombre de acc1, a partir de una transacción realizada por acc1

truffle(rsk)> cfToken.approve(acc0, web3.toWei(10), {from: acc1})
0x6e1a202f4ca7f43dfb28034952d54a572993b986a55857790aa51854afbc1fb4

En el registro de salida, vemos que la función se completó con éxito con verdadero y el registro muestra la cantidad permitida para acc0 para gastos. Comprobando con tolerancia

truffle(rsk)> web3.fromWei(cfToken.allowance(acc1, acc0, {from: acc0}).toString(10)) // = '10'

Ahora si ejecutamos nuevamente la transacción de gasto

truffle(rsk)> cfToken.transferFrom(acc1, acc0, web3.toWei(5), {from: acc0})
0x41f750eabb6e0d3ab576aac0333b0d337ca61808aae1eeafa9d8e2a0b81b979b

obtenemos una transacción exitosa con el estado ‘0x01’.

Comprobando los saldos de nuevo

truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc0).toString(10)) // = '738.6'
truffle(rsk)> web3.fromWei(cfToken.balanceOf(acc1).toString(10)) // = '261.4'

Por último, si firmamos una transacción llamando a una función que no está disponible, se llamará a nuestra función alternativa. Firmando una transacción como

truffle(rsk)> web3.eth.sendTransaction({from: acc0, to: cfToken.address})
0x4106a287fc60669bf9682a73ec4c457b094c086ec7408a5dea95d200688c4ee9

Nos devolverá un registro cuyos datos representan la cadena “Error 404: Function not found :P” en hexadecimal

(‘0x00…00204572726f72203430343a2046756e6374696f6e206e6f7420666f756e64203a50’).

Nuestra última función, que no vamos a ejecutar por razones obvias, es la función de suicidio. Necesitamos que el contrato no se destruya para mostrar las transacciones. Para llamarlo, el propietario debe hacer

truffle(rsk)> cfToken.destroy({from: acc0})

Conclusiones

En esta segunda parte del tutorial, he mostrado un ejemplo para desarrollar un contrato inteligente simple en la red RSK. Hemos visto cómo

  • Importar bibliotecas y contratos desde el paquete OpenZeppelin,
  • Crear un Token simple usando estas bibliotecas,
  • Configurar el proceso de migración para Truffle,
  • Implementar nuestro contrato a la red principal de RSK,
  • Interactuar con el contrato a través de diferentes cuentas,
  • Verificar el registro del bloque para obtener realimentacion sobre las transacciones.

Como hemos visto, el uso de una red RSK para el despliegue e interacción de Solidity Smart Contracts es prácticamente el mismo que en un nodo Ethereum. Por supuesto, esta sigue siendo una red beta y se esperan problemas e inconvenientes, principalmente en el nodo, pero el equipo de RSK Labs está haciendo un gran trabajo para resolverlos tan rápido como puedan cuando aparezcan. Con el tiempo, se logrará robustez.

Estén atentos para el próximo artículo (en ingles), donde hablaré sobre la implementación y la interacción con Web3.py.