Skip links
erc20 rsk openzeppelin logos

Desarrollo del Token ERC20 en RSK con OpenZeppelin y Web3.py

Motivación 

En mi ultimo articulo (en ingles) mostré como escribir un Solidity ERC20 Token para RSK Mainnet, como importar y usar lsa librerias y contratos de OpenZeppelin , y como usar Truffle para hacer deploy e interactuar con nuestro contrato.

A pesar de que tuvimos éxito en nuestra misión y cumplimos nuestros objetivos usando Truffle, eventualmente esta suite presenta fallos cuando se están mandando transacciones, implementando o manejando cuentas. En nuestro caso, mientras seguíamos las instrucciones del articulo anterior, tuvimos problemas manejando las cuentas nuevas creadas en Truffle y mandando transacciones.

El primer problema que tuve fue el congelamiento de una parte de los fondos en SBTC que el SRK LAbs Team me dio para testear en Mainnet (gracias a Maximiliano). A pesar de que desbloquee la cuenta, el nodo me pidió la private key que fue creada con la cuenta (como vimos en el primer articulo (en ingles)):

truffle(rsk)> web3.personal.newAccount(‘somePassword’)
truffle(rsk)> web3.personal.unlockAccount(web3.eth.accounts[0],'somePassword')

Por esa razón, y por la seguridad de los pocos SBTC que me quedaban, agregue la función suicida al contrato en caso de que algo fallara mientras hacia el articulo.

El segundo problema apareció cuando trate de interactuar con el contrato ya implementado. Mientras mandaba una transacción, Truffle manda el cuerpo de esta al nodo con los datos y el resto de parámetros (como vi en vivo en rsk.log file).  Sin embargo, después de 20 minutos la transacción nunca fue incluida en ningún bloque y tampoco el recibo, ni su has, eran conocidos por el nodo.

La forma en la que trabaja alrededor de esto fue para configurar (en ingles) el parámetro cors en el archivo node.conf para permitir a Metamask conectarse al nodo. Entonces, la transacción se envió codificando la función en cuestión y sus parámetros en una cadena hexadecimal e ingresado en el campo de datos. Después de enviarlo con el mismo valor nonce que la transacción enviada con Truffle, finalmente se incluyó.

No me malinterpreten, la suite Truffle es una gran herramienta para los desarrolladores, pero si no pueden vivir con algunos problemas de vez en cuando mientras desarrollan contratos inteligentes, manténgase enfocados y continúen leyendo.

La herramienta de la que hablare aquí es la versión de Python de Web3: Web3.py.

Introducción

Como rskj se comporta como un nodo Ethereum (es decir, expone la misma API JSON-RPC), podemos utilizar otras implementaciones para interactuar con él. La versión más popular de Web3 es la escrita en JavaScript (Web3.js). Pero desde noviembre, ha habido una mayor tasa de actualizaciones en el proyecto Web3.py, lo que mejora su usabilidad dramáticamente. Esta versión también permite una experiencia de interacción más amigable que Web3.js.

En este artículo discutiremos el despliegue y la interacción del mismo contrato Token ERC20 utilizado en el último artículo, para comparar la usabilidad con ambas herramientas.

Desde rskj 4.2, RSK Testnet vuelve a estar en línea. Por ese motivo, y debido a que no pudimos usarlo antes, vamos a implementar y probar nuestros contratos en esa red para este artículo.

Cambiando a Python

Web3.py está escrito en Python3. Si nunca antes has usado Python, probablemente necesites algunos paquetes. De lo contrario, cuando instalemos Web3.py en nuestro entorno, tendremos errores durante la compilación. Los instalamos con:

$ sudo apt-get install gcc
$ sudo apt-get install python3-dev

Configurando el ambiente

Queremos mantener separada la instancia de Web3.py del resto de nuestros módulos personales de Python. Para hacerlo, necesitamos crear un entorno virtual de Python con:

$ sudo apt install virtualenv
$ virtualenv -p python3 ~/web3_py

Entonces, activamos la instancia con:

$ source web3_py/bin/activate

En la terminal, deberíamos poder ver el símbolo del sistema como:

(web3_py) user@computer:~$

Eso significa que nuestro entorno virtual para Python ahora está activo. Ahora, instalaremos las bibliotecas del proyecto con:

(web3_py) user@computer:~$ pip install web3

No deberían tener ningún error.

Para mejorar la apariencia del intérprete, recomiendo instalar IPython (no, no desde la tienda de aplicaciones) con:

(web3_py) user@computer:~$ pip install ipython

Si queremos tener un descanso y salir del entorno virtual, podemos hacerlo con:

(web3_py) user@computer:~$ deactivate

Vamos a usar la red de prueba para implementar nuestros contratos, así que siéntanse libres de revisar nuevamente el primer artículo en el que hemos discutido cómo cambiar las redes (y ¡modifíquelo ustedes mismos!).

Ahora, tenemos un entorno adecuado para comenzar nuestros scripts.

Instalando SOLC

No es tarea de Python compilar nuestro .sol (código de contrato de Solidity). Esa es una tarea para nuestro compilador Solidity: Solc. Para instalar la última versión estable de la misma, lo hacemos con:

$ sudo add-apt-repository ppa:ethereum/ethereum
$ sudo apt-get update
$ sudo apt-get install solc

Con eso, deberíamos poder compilar nuestros contratos de esta manera:

$ solc <contract>.sol --bin --abi --optimize -o <output-dir>/

Donde <contract>.sol es el archivo de código de Solidity <output-dir> es donde estarán nuestros archivos ABI y bytecodes.

Configuración de inicio

Una vez que abrimos Python desde nuestro entorno virtual, necesitamos importar bibliotecas como Web3 y Json, pero antes hay un problema que debe ser resuelto. En algunos casos, incluso con Python3, el autocompletar con doble TAB no funciona. Para resolver esto, debemos importar algunas bibliotecas haciendo lo siguiente:

>>> try:
...    import readline
... except ImportError:
...    print("Module readline not available.")
... else:
...    import rlcompleter
...    readline.parse_and_bind("tab: complete")

Después de eso, deberíamos tener esta característica activada. Ahora, para importar el resto de las bibliotecas, simplemente hacemos:

>>> from web3 import Web3
>>> import json

Entonces estamos listos para conectar Python a nuestro nodo, que ya ha ejecutado, supongo. De lo contrario, comenzamos con:

$ sudo service rsk start

El objeto de conexión se instancia de la siguiente manera:

>>> web3 = Web3(Web3.HTTPProvider("http://MACHINE:PORT"))

donde MACHINE y PORT son la IP, o el nombre, y el puerto que hemos asignado en el archivo node.conf. Por defecto, es localhost: 4444.

Para comprobar si todo funcionó correctamente, verificamos el número de bloque en el intérprete de Python de la siguiente manera:

>>> web3.eth.blockNumber

Ahora, procedemos a implementar el contrato.

Deploy del token ERC20 con Web3.py

Vamos a implementar el contrato Token ERC20 basado en las bibliotecas de OpenZeppelin. Para ser coherente con mis artículos anteriores, renombro las dos cuentas como antes, escribiendo:

>>> acc0 = web3.eth.accounts[0]
>>> acc1 = web3.eth.accounts[1]

En otra terminal, vamos a la carpeta donde está el código de contrato, y luego lo compilamos usando Solc de esta manera:

$ solc CoinFabrikToken.sol --bin --abi --optimize -o Compiled/

Dentro de la carpeta Compiled encontramos no solo el ABI y el bytecode de nuestro contrato, sino también sus dependencias compiladas. Sin embargo, solo necesitamos los del contrato principal.

Para importar ambos archivos en la máquina virtual de Python, podemos hacerlo así:

>>> with open("./Compiled/CoinFabrikToken.abi") as contract_abi_file:
...   contract_abi = json.load(contract_abi_file)
>>> with open("./Compiled/CoinFabrikToken.bin") as contract_bin_file:
...   contract_bytecode = '0x' + contract_bin_file.read()

Entonces, podemos crear la instancia del contrato, dándole el ABI y el bytecode con:

>>> cfToken = web3.eth.contract(abi=contract_abi, bytecode=contract_bytecode)

Y después de eso, creamos (¡y desplegamos!) La escritura del contrato:

>>> tx_hash = cfToken.constructor().transact(transaction=tx_args0)

Guardamos el hash de la transacción por dos razones: para verificar el estado, la dirección y más parámetros, y porque podemos usarlo con la función waitForTransactionReceipt para saber cuándo se extrae el contrato.

Para calcular la dirección en la que se implementará el contrato, necesitamos la dirección utilizada para implementar el contrato, acc0 y la independencia de la transacción. Tenemos ambas cosas, así que importamos un par de bibliotecas y luego llamamos a la función de la siguiente manera (gracias a M. Stolarz por este hallazgo):

>>> import rlp
>>> from eth_utils import keccak, to_checksum_address

>>> contract_address = to_checksum_address('0x' + keccak(rlp.encode([bytes(bytearray.fromhex(acc0[2:])), web3.eth.getTransaction(tx_hash).nonce]))[-20:].hex())

La dirección se establece en nuestra instancia de esta manera:

>>> cfToken = web3.eth.contract(address = contract_address, abi = contract_abi, bytecode = contract_bytecode)

Después de eso, nuestro contrato será escrito en blockchain.

Una de las facultades de Python es que podemos hacer un script de todos los comandos y luego ejecutarlos todos juntos así:

>>> exec(open("deploy_cfToken.py").read())

Esto significa que podemos implementar el contrato en una línea como Truffle (¡pero sabiendo lo que hace! = D).

Nuestro contrato Token implementado de la red de prueba está ahora en 0x7C0c436e1E8dCd270a7A306B5AE8A2996f6A25dD .

Interacción con el contrato

Después de implementar el Token, la instancia ya debe estar creada para llamarla fácilmente. Para llamar a una función en la cual el blockchain no se modifica, como un getter, hacemos:

>>> cfToken.functions.FUNC_CALL().call()

Donde FUNC_CALL es una función invocable del contrato que no modifica la cadena de bloques.

Si queremos modificar el blockchain (para establecer un valor, hacer un cálculo, etc.), la sintaxis correcta es:

>>> cfToken.functions.FUNC_SET(PARAMETERS).transact(TXARG)

Donde FUNC_SET es una función del contrato que modifica la cadena de bloques, PARAMETERS son los valores que toma la función y TXARG son los argumentos para la transacción escritos como:

>>> txargs0 = {“from”: acc0 , “gasPrice”: 0, “gas”: 3000000}

Podemos verificar que la variable para nuestro nombre esté vacía con:

>>> cfToken.functions.getON().call() // ‘’

Entonces, como antes, si intentamos modificar la variable sin ser el propietario, acc1, la transacción no tiene éxito.

>>> cfToken.functions.setON('Andrés Bachfischer').transact(txargs1)

[Sugerencia: si obtiene un error del Servidor interno, probablemente su cuenta esté bloqueada nuevamente. Tiene un tiempo de espera.]

Entonces, si enviamos la misma transacción de acc0 en lugar de acc1, obtenemos:

>>> cfToken.functions.setON('Andrés Bachfischer').transact(txargs0)

Vemos que el estado nos da una operación exitosa.

Ahora nos enfocaremos en las funciones del Token, verificamos los saldos originales de nuestras cuentas escribiendo:

>>> web3.fromWei(cfToken.functions.balanceOf(acc0).call(),'ether') // = 1000
>>> web3.fromWei(cfToken.functions.balanceOf(acc1).call(),'ether') // = 0

Ahora, realizamos una transferencia de la cuenta de propietario a nuestra segunda cuenta ejecutando:

>>> cfToken.functions.transfer(acc1, web3.toWei(88.8, 'ether')).transact(txargs0)

Y obtenemos los nuevos saldos:

>>> web3.fromWei(cfToken.functions.balanceOf(acc0).call(),'ether') // = 911.2
>>> web3.fromWei(cfToken.functions.balanceOf(acc1).call(),'ether') // = 88.8

Pasando a la siguiente función, verificamos que acc0 no tenga la asignación en nombre de acc1

>>> web3.fromWei(cfToken.functions.allowance(acc1, acc0).call(txargs0),'ether') // = 0

Por lo tanto, para otorgar un límite de 10 tokens a acc0 desde acc1, debes hacerlo con:

>>> cfToken.functions.approve(acc0, web3.toWei(10,'ether')).transact(txargs1)

Gastemos en nombre de acc1:

>>> cfToken.functions.transferFrom(acc1, acc0, web3.toWei(5,'ether')).transact(txargs0)

Vemos que la transacción fue exitosa, como se esperaba.

Finalmente, enviamos una transacción al contrato sin llamar a ninguna función, que activará la función fallback (comportamiento predeterminado):

Esto nos advertirá que hicimos algo mal o que la función no existe.

Conclusiones

Para agregar a los logros del artículo anterior (en ingles), en este tutorial hemos aprendido a:

  • Configurar un entorno virtual de desarrollador adecuado
  • Instalar el compilador Solc,
  • Compilar nuestros contratos co Solc,
  • Instalar y configurar paquetes de Python,
  • Conectar Python a nuestro nodo,
  • Implementar el contrato compilado en la red de prueba utilizando Python,
  • Interactuar con el contrato usando Python,
  • Escribir scripts automáticos de Python para cargar el contrato compilado e implementarlo.

Vimos que la sintaxis de ambas herramientas es similar. Truffle es más fácil de usar desde cero porque poner el código de contrato en Solidez es suficiente para que pueda implementarse desde la suite. Con dos comandos simples, su Contrato inteligente está listo para funcionar. Pero debido a que tuvimos problemas al usar el paquete Truffle cuando enviamos transacciones, se necesitó un cambio.

La implementación de Web3.py es mucho mejor para un desarrollador en el momento de la escritura. No solo recuperó los fondos de una cuenta bloqueada hecha en Truffle, sino que también tiene una versión más nueva de Web3 y permite la ejecución de scripts no triviales. Las operaciones en Truffle a veces pueden ser más complejas de lo necesario. Sí, tal vez necesite configurar más paquetes y bibliotecas desde el principio, sin tener en cuenta que necesita compilar el código usted mismo, pero a la larga me pareció que esta herramienta era más estable que Truffle para desarrollar contratos.