Ir al contenido principal

Cómo simular contratos inteligentes de Solidity para probarlos

soliditycontratos inteligentespruebassimular
Intermedio
Markus Waas
soliditydeveloper.com(opens in a new tab)
2 de mayo de 2020
4 minuto leído minute read

Los objetos simulados(opens in a new tab) son un patrón de diseño común en la programación orientada a objetos. Viene de la antigua palabra francesa «mocquer» con el significado de «reírse de algo» y evolucionó a «imitar a algo real» que es, en realidad, lo que hacemos en programación. Por tanto, ríase todo lo que quiera de sus contratos inteligentes si quieres, pero simúlelos siempre que pueda. ¡Le simplifica la vida!

Pruebas unitarias de contratos con simulaciones

Simular un contrato significa básicamente crear una segunda versión del contrato que se comporta de manera muy similar al original, pero de una forma que el desarrollador puede controlar fácilmente. A menudo suele uno acabar con contratos complejos cuando lo único que quiere es hacer pruebas unitarias en partes pequeñas del contrato.. El problema es: ¿qué sucedería si esta pequeña parte requiere un estado de contrato muy específico que es complicado para comenzar?

Puede escribir una lógica de configuración de prueba compleja cada vez que el contrato se encuentre en el estado requerido, o escriba una simulación. Simular un contrato es fácil con herencia. Simplemente crea un segundo contrato simulado que hereda del original. Ahora puede sobrescribir funciones a su imitación. Veámoslo mejor poniendo un ejemplo.

Ejemplo: ERC20 privado

Usamos el ejemplo de un contrato ERC-20 que tiene un tiempo inicial privado. El propietario puede administrar usuarios privados y solo ellos estarán autorizados a recibir tókenes al principio. Una vez transcurrido un periodo específico, cualquiera podrá usar los tókenes. Si le pica la curiosidad, estamos usando el gancho _beforeTokenTransfer(opens in a new tab) de los nuevos contratos v3 de OpenZeppelin.

1pragma solidity ^0.6.0;
2
3import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4import "@openzeppelin/contracts/access/Ownable.sol";
5
6contract PrivateERC20 is ERC20, Ownable {
7 mapping (address => bool) public isPrivateUser;
8 uint256 private publicAfterTime;
9
10 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
11 publicAfterTime = now + privateERC20timeInSec;
12 }
13
14 function addUser(address user) external onlyOwner {
15 isPrivateUser[user] = true;
16 }
17
18 function isPublic() public view returns (bool) {
19 return now >= publicAfterTime;
20 }
21
22 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
23 super._beforeTokenTransfer(from, to, amount);
24
25 require(_validRecipient(to), "PrivateERC20: invalid recipient");
26 }
27
28 function _validRecipient(address to) private view returns (bool) {
29 if (isPublic()) {
30 return true;
31 }
32
33 return isPrivateUser[to];
34 }
35}
Mostrar todo
Copiar

Y ahora vamos a simularlo.

1pragma solidity ^0.6.0;
2import "../PrivateERC20.sol";
3
4contract PrivateERC20Mock is PrivateERC20 {
5 bool isPublicConfig;
6
7 constructor() public PrivateERC20(0) {}
8
9 function setIsPublic(bool isPublic) external {
10 isPublicConfig = isPublic;
11 }
12
13 function isPublic() public view returns (bool) {
14 return isPublicConfig;
15 }
16}
Mostrar todo
Copiar

Recibirás uno de los siguientes mensajes de error:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.

Ya que estamos usando la nueva versión 0.6 de Solidity, tenemos que añadir la palabra clave virtual para funciones que puedan ser sobrescritas y sobrescribir para la función de sobrescribir. Entonces, añadámoslas a ambas funciones isPublic.

Ahora, en sus pruebas unitarias, puede usar PrivateERC20Mock en su lugar. Cuando quiera probar el comportamiento durante un tiempo privado de uso, utilice setIsPublic(false) al igual que setIsPublic(true) para probar el tiempo público de uso. Por supuesto, en nuestro ejemplo también podemos usar únicamente ayudas de tiempo(opens in a new tab) para cambiar los tiempos según corresponda. Esperamos que la idea de simular le haya quedado ahora clara y puede imaginar situaciones en las que todo no es tan sencillo como simplemente hacer avanzar el tiempo.

Simular varios contratos

Puede volverse un tanto caótico si tiene que crear otro contrato para cada imitación única. Si esto le preocupa, puede revisar la biblioteca MockContract(opens in a new tab). Le permite sobrescribir y cambiar los comportamientos de los contratos sobre la marcha. Sin embargo, esto solo funciona para simular la activación de otro contrato, por lo que no funcionará para nuestro ejemplo.

Simular puede ser aún más eficaz

Los poderes de la simulación no terminan aquí.

  • Añadir funciones: no solo sobrescribir una función específica es útil, también lo es añadir funciones adicionales. Un buen ejemplo para los tókenes es contar con una función adicional mint para permitir a cualquier usuario consiga los nuevos tókenes sin coste.
  • Uso en redes de prueba: cuando implemente y pruebe sus contratos en redes de pruebas junto con su DApp, considere el usar una versión simulada. Evita el tener que sobreescribir las funciones, a menos que sea realmente necesario. Al fin y al cabo, se trata de probar la lógica real. Pero agregar, por ejemplo, una función de reinicio puede ser útil para simplemente restablecer el contrato a su estado inicial, sin requerir un nuevo despliegue. Obviamente, no haría eso en un contrato de red principal.

Última edición: @nhsz(opens in a new tab), 15 de agosto de 2023

¿Le ha resultado útil este tutorial?