Cómo simular contratos inteligentes de Solidity para probarlos
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;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";4import "@openzeppelin/contracts/access/Ownable.sol";56contract PrivateERC20 is ERC20, Ownable {7 mapping (address => bool) public isPrivateUser;8 uint256 private publicAfterTime;910 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {11 publicAfterTime = now + privateERC20timeInSec;12 }1314 function addUser(address user) external onlyOwner {15 isPrivateUser[user] = true;16 }1718 function isPublic() public view returns (bool) {19 return now >= publicAfterTime;20 }2122 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {23 super._beforeTokenTransfer(from, to, amount);2425 require(_validRecipient(to), "PrivateERC20: invalid recipient");26 }2728 function _validRecipient(address to) private view returns (bool) {29 if (isPublic()) {30 return true;31 }3233 return isPrivateUser[to];34 }35}Mostrar todoCopiar
Y ahora vamos a simularlo.
1pragma solidity ^0.6.0;2import "../PrivateERC20.sol";34contract PrivateERC20Mock is PrivateERC20 {5 bool isPublicConfig;67 constructor() public PrivateERC20(0) {}89 function setIsPublic(bool isPublic) external {10 isPublicConfig = isPublic;11 }1213 function isPublic() public view returns (bool) {14 return isPublicConfig;15 }16}Mostrar todoCopiar
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