Prueba de un contrato inteligente simple con la biblioteca Waffle
En este tutorial aprenderá a
- Probar los cambios en el saldo de la billetera
- Probar la emisión de eventos con argumentos específicos
- Afirmar que una transacción ha sido revertida
Supuestos
- Puede crear un nuevo proyecto de JavaScript o TypeScript
- Tiene algo de experiencia básica con pruebas en JavaScript
- Ha usado algunos administradores de paquetes como yarn o npm
- Posee conocimientos muy básicos sobre contratos inteligentes y Solidity
Primeros pasos
El tutorial demuestra la configuración y ejecución de la prueba usando yarn, pero no hay problema si prefiere npm. Proporcionaré las referencias adecuadas a la documentaciónopens in a new tab oficial de Waffle.
Instalar dependencias
Añadaopens in a new tab las dependencias ethereum-waffle y typescript a las dependencias de desarrollo de su proyecto.
yarn add --dev ethereum-waffle ts-node typescript @types/jestEjemplo de contrato inteligente
Durante el tutorial trabajaremos en un ejemplo de contrato inteligente simple: EtherSplitter. No hace mucho más que permitir que cualquiera envíe wei y los divida en partes iguales entre dos receptores predefinidos.
La función split requiere que la cantidad de wei sea par; de lo contrario, se revertirá. Para ambos receptores, realiza una transferencia de wei seguida de la emisión del evento Transfer.
Coloque el fragmento de código de EtherSplitter en src/EtherSplitter.sol.
1pragma solidity ^0.6.0;23contract EtherSplitter {4 address payable receiver1;5 address payable receiver2;67 event Transfer(address from, address to, uint256 amount);89 constructor(address payable _address1, address payable _address2) public {10 receiver1 = _address1;11 receiver2 = _address2;12 }1314 function split() public payable {15 require(msg.value % 2 == 0, 'No se permite una cantidad impar de wei');16 receiver1.transfer(msg.value / 2);17 emit Transfer(msg.sender, receiver1, msg.value / 2);18 receiver2.transfer(msg.value / 2);19 emit Transfer(msg.sender, receiver2, msg.value / 2);20 }21}Mostrar todoCompilar el contrato
Para compilaropens in a new tab el contrato, añada la siguiente entrada al archivo package.json:
1"scripts": {2 "build": "waffle"3 }A continuación, cree el archivo de configuración de Waffle en el directorio raíz del proyecto, waffle.json, y pegue allí la siguiente configuración:
1{2 "compilerType": "solcjs",3 "compilerVersion": "0.6.2",4 "sourceDirectory": "./src",5 "outputDirectory": "./build"6}Ejecute yarn build. Como resultado, aparecerá el directorio build con el contrato EtherSplitter compilado en formato JSON.
Configuración de la prueba
Probar con Waffle requiere el uso de los matchers de Chai y Mocha, por lo que necesita añadirlosopens in a new tab a su proyecto. Actualice su archivo package.json y añada la entrada test en la parte de scripts:
1"scripts": {2 "build": "waffle",3 "test": "export NODE_ENV=test && mocha -r ts-node/register 'test/**/*.test.ts'"4 }Si desea ejecutaropens in a new tab sus pruebas, simplemente ejecute yarn test.
Pruebas
Ahora cree el directorio test y el nuevo archivo test\EtherSplitter.test.ts.
Copie el siguiente fragmento y péguelo en nuestro archivo de prueba.
1import { expect, use } from "chai"2import { Contract } from "ethers"3import { deployContract, MockProvider, solidity } from "ethereum-waffle"4import EtherSplitter from "../build/EtherSplitter.json"56use(solidity)78describe("Ether Splitter", () => {9 const [sender, receiver1, receiver2] = new MockProvider().getWallets()10 let splitter: Contract1112 beforeEach(async () => {13 splitter = await deployContract(sender, EtherSplitter, [14 receiver1.address,15 receiver2.address,16 ])17 })1819 // añada las pruebas aquí20})Mostrar todoUnas palabras antes de empezar.
MockProvider viene con una versión de simulación de la cadena de bloques. También proporciona billeteras de simulación que nos servirán para probar el contrato EtherSplitter. Podemos obtener hasta diez billeteras llamando al método getWallets() en el proveedor. En el ejemplo, obtenemos tres billeteras: para el emisor y para dos receptores.
A continuación, declaramos una variable llamada «splitter», que es nuestro contrato de simulación EtherSplitter. Se crea antes de cada ejecución de una única prueba mediante el método deployContract. Este método simula la implementación de un contrato desde la billetera pasada como primer parámetro (la billetera del emisor en nuestro caso). El segundo parámetro es la ABI y el bytecode del contrato probado. Allí pasamos el archivo json del contrato EtherSplitter compilado desde el directorio build. El tercer parámetro es un array con los argumentos del constructor del contrato, que en nuestro caso son las dos direcciones de los receptores.
changeBalances
Primero, comprobaremos si el método split cambia realmente los saldos de las billeteras de los receptores. Si dividimos 50 wei de la cuenta del emisor, esperaríamos que los saldos de ambos receptores aumentaran en 25 wei. Usaremos el matcher changeBalances de Waffle:
1it("Cambia los saldos de las cuentas", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalances(3 [receiver1, receiver2],4 [25, 25]5 )6})Como primer parámetro del matcher, pasamos un array de las billeteras de los receptores y, como segundo, un array de los aumentos esperados en las cuentas correspondientes.
Si quisiéramos comprobar el saldo de una billetera específica, también podríamos usar el matcher changeBalance, que no requiere pasar arrays, como en el siguiente ejemplo:
1it("Cambia el saldo de la cuenta", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalance(3 receiver1,4 255 )6})Tenga en cuenta que en ambos casos de changeBalance y changeBalances pasamos la función split como un callback porque el matcher necesita acceder al estado de los saldos antes y después de la llamada.
A continuación, probaremos si el evento Transfer se emitió después de cada transferencia de wei. Pasaremos a otro matcher de Waffle:
Emit
1it("Emite un evento en la transferencia al primer receptor", async () => {2 await expect(splitter.split({ value: 50 }))3 .to.emit(splitter, "Transfer")4 .withArgs(sender.address, receiver1.address, 25)5})67it("Emite un evento en la transferencia al segundo receptor", async () => {8 await expect(splitter.split({ value: 50 }))9 .to.emit(splitter, "Transfer")10 .withArgs(sender.address, receiver2.address, 25)11})Mostrar todoEl matcher emit nos permite comprobar si un contrato emitió un evento al llamar a un método. Como parámetros para el matcher emit, proporcionamos el contrato de simulación que predecimos que emitirá el evento, junto con el nombre de ese evento. En nuestro caso, el contrato de simulación es splitter y el nombre del evento es Transfer. También podemos verificar los valores precisos de los argumentos con los que se emitió el evento: pasamos tantos argumentos al matcher withArgs como espera nuestra declaración de evento. En el caso del contrato EtherSplitter, pasamos las direcciones del emisor y del receptor junto con la cantidad de wei transferida.
revertedWith
Como último ejemplo, comprobaremos si la transacción fue revertida en caso de una cantidad impar de wei. Usaremos el matcher revertedWith:
1it("Se revierte cuando la cantidad de wei es impar", async () => {2 await expect(splitter.split({ value: 51 })).to.be.revertedWith(3 "No se permite una cantidad impar de wei"4 )5})La prueba, si se supera, nos asegurará que la transacción fue revertida. Sin embargo, también debe haber una coincidencia exacta entre los mensajes que pasamos en la declaración require y el mensaje que esperamos en revertedWith. Si volvemos al código del contrato EtherSplitter, en la declaración require para la cantidad de wei, proporcionamos el mensaje: «No se permite una cantidad impar de wei». Esto coincide con el mensaje que esperamos en nuestra prueba. Si no fueran iguales, la prueba fallaría.
¡Felicitaciones!
¡Ha dado su primer gran paso hacia la prueba de contratos inteligentes con Waffle!
Última actualización de la página: 17 de diciembre de 2025