WEB3DEV Español

Cover image for Protegiendo contratos inteligentes Solidity con Roles y Pausable
Juan José Gouvêa
Juan José Gouvêa

Posted on

Protegiendo contratos inteligentes Solidity con Roles y Pausable

sol

Como ya sabemos, todo en las blockchains como Ethereum, Polygon, BNB Chain y muchas otras, es público, observable y los contratos inteligentes son interactivos para cualquier persona, sin importar quién sea.

Esto nos lleva a la pregunta:

  • ¿Cómo puedo proteger mis contratos inteligentes si literalmente cualquiera puede interactuar con ellos?
  • ¿Cómo puedo evitar que actores maliciosos aprovechen mis contratos inteligentes?

La solución está en Roles y Pausable.

En este artículo, mostraré cómo proteger tus contratos inteligentes con Roles y Pausable en OpenZeppelin Contracts. OpenZeppelin Contracts es una biblioteca para el desarrollo seguro de contratos inteligentes, tiene implementaciones de estándares como ERC-20 y ERC-721, y lo más importante es que ha sido auditada por varias empresas de seguridad.

¿Por qué es tan importante tener un contrato inteligente seguro?

Imaginemos que tienes un contrato inteligente para una colección de NFT que planeas comercializar y vender a coleccionistas. Las imágenes de esta colección de NFT permanecerán ocultas hasta más adelante, para evitar que los coleccionistas adivinen qué NFTs tienen mayor valor en términos de su rareza y otras características.

Planeas revelar las imágenes en un mes a través del contrato inteligente de NFT, pero antes de que puedas hacerlo, alguien descubrió que tu contrato inteligente no es seguro y encontró una manera de revelar tus NFTs. Ahora todos pueden ver las imágenes reales de los NFTs, lo que llevó a los coleccionistas a comprar solo los NFTs más raros de tu colección, mientras que los demás quedan allí, acumulando polvo y perdiendo valor.

Esta es solo una de las muchas formas en que las personas pueden aprovechar un contrato inteligente no seguro.

Entonces, ¿cómo podemos asegurar un contrato inteligente?

Como se mencionó anteriormente, usaremos la biblioteca OpenZeppelin Contracts con nuestro contrato inteligente, así que creemos un archivo .sol.

(Puedes hacer esto con tu IDE preferido o simplemente usar el IDE online de Remix que permite implementar contratos inteligentes también.)

Creemos nuestro contrato inteligente con el estándar ERC-1155. Comencemos con una base para el contrato...

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.15;

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

contract AccessControlWithRoles is ERC1155, AccessControl, Pausable {
   constructor() ERC1155("default") {}
}
Enter fullscreen mode Exit fullscreen mode

Estamos importando el ERC1155, los contratos AccessControl y Pausable de la biblioteca OpenZeppelin.

  • ERC1155 viene con funciones como transferencias en lotes y transferencias seguras.
  • AccessControl permite asignar y revocar roles a direcciones de billetera, lo que les otorga permisos para ejecutar ciertas funciones en un contrato inteligente.
  • Pausable permite desactivar funciones en un contrato inteligente cuando el contrato está pausado.

Ahora agreguemos algunas líneas más a nuestro contrato inteligente...

contract AccessControlWithRoles is ERC1155, AccessControl, Pausable {
    bytes32 public constant SECOND_ADMIN_ROLE = keccak256("SECOND_ADMIN_ROLE");

    uint256 private defAdminCount = 0;
    uint256 private secAdminCount = 0;
    uint256 private mintsLeft = 1000;

    constructor(
        address _defAdmin, 
        address _secAdmin
    ) ERC1155("default") {
        _setupRole(DEFAULT_ADMIN_ROLE, _defAdmin);
        _setupRole(SECOND_ADMIN_ROLE, _secAdmin);
    }
}
Enter fullscreen mode Exit fullscreen mode

Por defecto, AccessControl viene con un administrador predeterminado llamado DEFAULT_ADMIN_ROLE, si necesitas más de un administrador o función, necesitarás usar keccak256() para crear uno nuevo.

Agregamos algunas variables privadas con las que trabajaremos más adelante. También estamos asignando funciones de administrador a las dos direcciones de billetera pasadas en nuestro constructor.

Agreguemos algunas funciones de escritura debajo del constructor...

// Permite que el Administrador Predeterminado pausa el contrato
function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
    _pause();
}

// Permite que el Administrador Predeterminado despausa el contrato
function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
    _unpause();
}

// Incrementa el contador defAdminCount
function incDefAdminCount() public {
    require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "No autorizado: no tienes la función DEFAULT_ADMIN_ROLE");
    defAdminCount++;
}

// Incrementa el contador secAdminCount
function incSecAdminCount() public {
    require(hasRole(SECOND_ADMIN_ROLE, msg.sender), "No autorizado: no tienes la función SECOND_ADMIN_ROLE");
    secAdminCount++;
}

// Decrementa mintsLeft
function mintNft() public whenNotPaused {
    require(mintsLeft > 0, "Error: No quedan más NFTs por crear");
    mintsLeft--;
}
Enter fullscreen mode Exit fullscreen mode
  • La función pause() permite que las direcciones con DEFAULT_ADMIN_ROLE pausen el contrato.
  • La función unpause() permite que las direcciones con DEFAULT_ADMIN_ROLE reanuden el contrato.
  • La función incDefAdminCount() incrementa el contador defAdminCount solo si la dirección tiene el rol DEFAULT_ADMIN_ROLE.
  • La función incSecAdminCount() incrementa el contador secAdminCount solo si la dirección tiene el rol SECOND_ADMIN_ROLE.
  • La función mintNft() permite que cualquier dirección la llame siempre y cuando el contrato no esté pausado y mintsLeft sea mayor que 0.

Ahora agregaremos algunas funciones de lectura para que podamos leer nuestras variables...

// Retorna defAdminCount cuando se llama
function getDefAdminCount() public view returns (uint256) {
    return defAdminCount;
}

// Retorna secAdminCount cuando se llama
function getSecAdminCount() public view returns (uint256) {
    return secAdminCount;
}

// Retorna mintsLeft cuando se llama
function getMintsLeft() public view returns (uint256) {
    return mintsLeft;
}

// Sobrescritura requerida por Solidity
function supportsInterface(bytes4 interfaceId)
    public
    view
    override(ERC1155, AccessControl)
    returns (bool)
{
    return super.supportsInterface(interfaceId);
}
Enter fullscreen mode Exit fullscreen mode
  • La función getDefAdminCount() devuelve defAdminCount.
  • La función getSecAdminCount() devuelve secAdminCount.
  • La función getMintsLeft() devuelve mintsLeft.
  • supportsInterface() es una función de sobreescritura para resolver el error "Derived contract must override function 'supportsInterface'" que ocurrirá debido al contrato AccessControl.

Este es nuestro contrato inteligente, simple pero lo suficientemente conciso para mostrar lo que podemos hacer con AccessControl y Pausable.

Implementa el contrato con Remix Online IDE

Es hora de implementar nuestro contrato inteligente en una red.

Para los fines de este tutorial, implementaremos nuestro contrato en la testnet de Polygon, Mumbai. Si necesitas algunas MATIC para pruebas, puedes obtenerlas en el Mumbai Faucet de Alchemy.

Después de crear un archivo .sol en Remix, primero debes integrar el archivo antes de poder implementarlo.

img1

Haz clic derecho en el archivo Solidity y selecciona "Flatten".

Una vez integrado, puedes implementarlo.

img2

Selecciona "Injected Web3 - MetaMask" para conectarte a tu billetera MetaMask. Pega las direcciones de la billetera en los campos y haz clic en "transact" para implementar.

Aparecerá una ventana emergente de MetaMask para solicitarte confirmar la implementación de tu contrato.

Una vez implementado, podrás ver la dirección de tu contrato en la pestaña "Deployed Contracts". Copia la dirección para poder buscar tu contrato en Mumbai Polygonscan.

Verificar y publicar el contrato

Antes de poder leer nuestro contrato inteligente en Polygonscan, primero debemos verificar y publicar.

img3

Haz clic en "Verify and Publish" para publicar tu contrato.

Pega el código integrado en el cuadro y verifica. Después de una verificación exitosa, deberías ver un enlace para ver tu contrato. Haz clic en él.

A continuación, en "Contract", haz clic en "Write Contract".

Haz clic en getDefAdminCount, getSecAdminCount y getMintsLeft para ver cada uno de sus valores. Deben ser 0, 0 y 1000, respectivamente.

Contrato de escritura

Haz clic en "Write Contract" y luego en "Connect to Web3" para conectarte a tu billetera y poder llamar a las funciones de tu contrato.

img4

Haz clic en "Write" para llamar a las funciones. Si tu billetera tiene el rol correcto, las transacciones deberían tener éxito.

img5

Tus transacciones fallarán si no tienes el rol correcto.

img6

Deberías ver los cambios de valor en "Read Contract" si tus transacciones tuvieron éxito.

Esto fue para probar direcciones con diferentes roles, ahora veamos qué sucede cuando se pausa nuestro contrato.

Vuelve a "Write Contract" y haz clic en "Write" para la función mintNFT.

img7

Como el contrato aún no está pausado, la transacción debería ser exitosa y getMintsLeft debería disminuir en 1.

Ahora pausamos el contrato ejecutando la función pause en "Write Contract" e intentamos llamar a la función mintNFT nuevamente.

img8

Las transacciones de mintNFT deberían fallar hasta que la dirección de administrador predeterminado vuelva a desactivar el contrato.

Pensamientos finales

Ahí lo tienes. Así es como puedes asegurar mejor tus contratos inteligentes con Roles y Pausable.

  • Con Roles, puedes asegurarte de que solo las personas con los permisos adecuados puedan interactuar con tus contratos inteligentes.
  • Con Pausable, puedes pausar tus contratos inteligentes en cualquier momento para evitar que las personas aprovechen cualquier brecha que encuentren en tus contratos inteligentes.

La seguridad siempre ha sido mi mayor preocupación al trabajar con contratos inteligentes, espero que estas ideas te ayuden a escribir contratos inteligentes mejores en el futuro.

Si necesitas el código fuente, puedes encontrarlo aquí.

Gracias por leer. ¡Hasta la próxima! 👋

Este artículo fue escrito por Erin Lim y traducido por Juan José Gouvêa. El original en inglés se puede encontrar aquí.

Discussion (0)