Seguimos contando como montar un proyecto sobre Solidity; si ya te enseñamos los entornos de implementación y trabajo, ahora toca crear un Smart Contract básico, usando Truffle y OpenZeppelin, migrarlo en un entorno local y hacer testing para ver que todo está ok.
Para probar todo el proceso, probaremos con un Smart Contract que cumpla el ERC20, ERC827 y también que permita el minado de nuevas monedas… Quién sabe, igual en un futuro debamos acuñar más ! Como siempre, no entraremos mucho en el contrato en sí, ya que no es el foco de esta serie de post y porque, como os dijimos, Solidity tiene un muy buen manual para entender cómo funciona.
Lo primero que haremos será instalar los paquetes que necesitamos:
- zeppelin-solidity: Esta librería da un montón de funcionalidades para trabajar sin tener que reinventar la rueda. El objetivo de la empresa Open Zeppelin es auditar y ser un “sello de garantía” de los Smart contracts, por lo que puedes usar su código estando seguro de la calidad del mismo.
- Es muy aconsejable usar linters que nos revisen y nos den unas guías de trabajo unificadas, para Solidity tenemos Solhint, del que ya hablamos en el primer post para integrarlo en Webstorm.
- Usaremos Javascript, por lo que es necesario usar un linter de Javascript. En nuestro caso usamos Eslint con la configuración de Airbnb que es bastante estricta. En nuestro caso, como usamos máquinas Unix usaremos el sistema que indican en la documentación para tenerlo completamente actualizado.
1 2 3 4 5 6 |
npm install zeppelin-solidity npm install solhint --save-dev ( export PKG=eslint-config-airbnb; npm info "[email protected]" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "[email protected]" ) |
Lo primero será configurar los linters, para eso habrá que añadir un pequeño fichero de configuración para el Solhint ya que nosotros trabajamos siempre con 4 espacios y el estándar que usamos de Eslint.
1 2 3 4 5 6 |
{ "extends": "default", "rules": { "indent": ["warn", 2] } } |
1 2 3 4 5 6 7 |
{ "env": { "browser": false, "node": true }, "extends": ["airbnb"] } |
Y en el package.json actualizaremos los scripts para poder usar Solhint, cambiando el bloque de scripts por el siguiente
1 2 3 4 5 6 7 8 9 |
"scripts": { "check:unused": "npm-check ", "check:update": "npm-check -u", "t": "truffle", "test": "npm run test:linter && npm run test:truffle", "test:linter": "solhint 'contracts/**/*.sol'", "test:truffle": "truffle test", "testnet": "ganache-cli" }, |
Y si lo probamos con un “npm run test:linter” nos llevaremos una sorpresa, el Migrations.sol tiene unas cuantos errores. Así que adelante con ellos y a arreglarlo! (tienes una versión actualizada en Github)
El Smart Contrat que usaremos será el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pragma solidity ^0.4.17; import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; import "zeppelin-solidity/contracts/token/ERC827/ERC827Token.sol"; contract OurToken is ERC827Token, MintableToken { string public name = "OurToken"; string public symbol = "OT"; uint8 public decimals = 10; uint public initialSupply = 2000001; function OurToken() public { balances[msg.sender] = initialSupply; totalSupply_ = initialSupply; } } |
Para probar que todo funciona correctamente migraremos el contrato directamente contra la red de Ganache; para hacerlo tendremos que añadir a nuestro fichero de configuración de truffle el puerto y la network que está usando. Normalmente es el 7545 y la red 5777 pero comprobarlo por si en vuestro caso ha cambiado.
1 2 3 4 5 |
ganache: { host: 'localhost', port: 7545, network_id: '5777', }, |
Y lanzar el comando para compilar el Smart Contract y migrarlo a la red de Ganache. Recordar también que no hemos instalado nada en global, por lo que los comandos son ligeramente distintos a los habituales:
1 2 |
npm run t complie npm run t migrate -- --network ganache |
Si todo ha salido bien, tendréis el contracto subido y podréis ver algunos detalles en vuestro Ganache:
Pero, para probar las cosas, ¿qué mejor que hacerse unos tests? vamos a por uno básico y a ver si todo ha ido bien, este sería el test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
/* eslint-disable no-unused-vars */ /* eslint-disable no-undef */ const OurToken = artifacts.require('./OurToken.sol'); contract('OurToken', (accounts) => { before(async () => { ourTokenDeployed = await OurToken.deployed(); ourTokenOwner = accounts[0]; alice = accounts[1]; bob = accounts[2]; carol = accounts[3]; initialSupply = 2000001; }); it('Check the total balance', async () => { const totalBalance = await ourTokenDeployed.totalSupply.call(); assert.equal(totalBalance, initialSupply, 'Balance is different'); }); it('Check the owner balance', async () => { const ownerBalance = await ourTokenDeployed.balanceOf.call(ourTokenOwner); assert.equal(ownerBalance, initialSupply, "Ups, owner doesn't have all the cash"); }); it('Transfer from Owner to Alice', async () => { const cash = 1000; const transfer = await ourTokenDeployed.transfer(alice, cash); let balance = await ourTokenDeployed.balanceOf.call(ourTokenOwner); assert.equal(balance, initialSupply - cash, "owner doesn't have all the cash"); balance = await ourTokenDeployed.balanceOf.call(alice); assert.equal(balance, cash, "Alice doesn't have all the cash"); }); it('Transfer from Alice to Bob', async () => { const initialCash = 1000; const cash = 10; const transfer = await ourTokenDeployed.transfer(bob, cash, { from: alice } ); let balance = await ourTokenDeployed.balanceOf.call(alice); assert.equal(balance, initialCash - cash, "Alice doesn't have all the cash"); balance = await ourTokenDeployed.balanceOf.call(bob); assert.equal(balance, cash, "Bob doesn't have all the cash"); }); it('Mint to Carol', async () => { const quantityToAdd = 12002; const transfer = await ourTokenDeployed.mint(carol, quantityToAdd); const totalBalance = await ourTokenDeployed.totalSupply.call(); assert.equal(totalBalance, initialSupply + quantityToAdd, 'Balance is different'); const balance = await ourTokenDeployed.balanceOf.call(carol); assert.equal(balance, quantityToAdd, "Carol doesn't have all the cash"); }); }); |
Algunos tips sobre el test:
- Las dos primeras líneas las usamos para quitar un par de reglas de Eslint; Tener en cuenta que estamos dentro de un test.
- Usamos el before para esperar a que el deploy esté completamente hecho y guardar algunas “variables globales”
- Aunque aquí usamos Ganache para las pruebas, en los casos locales usamos ganache-cli por ser ligeramente más rápido.
- Hemos probado sólo con “test positivos” a modo de prueba, sería bueno hacer pruebas con cosas que no se pueden hacer.
¡Vamos a probarlo!
1 |
npm run test:truffle -- --network ganache |
Y, si todo ha ido bien, tendrás una salida similar a esta:
Enhorabuena! Ya tienes tu primer Smart Contract funcionando! Ahora nos tocará publicarlo en una red de desarrollo para poder usarlo con nuestro proyecto antes de lanzarlo a producción.
Notas:
- Veréis que también hemos incluido npm-check lo usamos para ir controlando el uso de los paquetes mientras estamos en desarrollo, tanto para actualizar los paquetes como para gestionar aquellos que no se están utilizando.
- Recuerda entrar a Ganache para cotillear todas las transacciones que has realizado! 😉
Más al respecto:
- Trabajando con Smart Contracts en Ethereum – Parte I – Intro – Entornos de Implementación
- Trabajando con Smart Contracts en Ethereum – Parte II – Crear el entorno de trabajo local
- Trabajando con Smart Contracts en Ethereum – Parte III – Crear un Smart Contract, hacer tests y migrarlo a una red local
- Trabajando con Smart Contracts en Ethereum – Parte IV – Cómo integrar un Smart Contract con Node.js
- Si queréis estar informados y preguntar sobre desarrollo en blockchain, podéis visitar el canal de Telegram https://t.me/blockchainDevs
Foto cortesía de La Huella artworks.
© Todos los derechos reservados.
3 comments