Crea una cuenta o inicia sesi贸n

隆Contin煤a aprendiendo sin ning煤n costo! 脷nete y comienza a potenciar tu carrera

脷ltima oportunidad para asegurar tu aprendizaje por 1 a帽o a precio especial

Antes: $249

Currency
$189/a帽o

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscr铆bete

Termina en:

0D
0H
5M
37S
Curso de Introducci贸n a OpenZeppelin

Curso de Introducci贸n a OpenZeppelin

Sebasti谩n Leonardo Perez

Sebasti谩n Leonardo Perez

Control de acceso basado en roles

3/19
Recursos

En determinados contratos, a veces no es suficiente disponer solo de un owner del mismo. Podemos necesitar tener dos, tres o N cantidad de roles diferentes. Cada uno con sus respectivas responsabilidades.

Contratos con m煤ltiples roles

Podemos definir la cantidad de roles que vamos a necesitar en nuestro contrato. Sin embargo, a煤n requerimos que exista un 煤nico due帽o que asigne posteriormente los roles a otras cuentas.

Importa y hereda AccessControl desde OpenZeppelin para comenzar a definir tus propios roles.

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract ContratoConRoles is AccessControl {

    bytes32 public constant ROL_ADMIN = keccak256("ROL_ADMIN");
    bytes32 public constant ROL_USUARIO = keccak256("ROL_USUARIO");

    constructor() {
        _grantRole(ROL_ADMIN, msg.sender);
    }

    function soloAdmin() public  {
        require(hasRole(ROL_ADMIN, msg.sender), "Esta funcion solo puede ser utilizada por el ADMIN");
    }

    function soloUsuario() public  {
        require(hasRole(ROL_USUARIO, msg.sender), "Esta funcion solo puede ser utilizada por un USUARIO");
    }

    function agregarRol(bytes32 role, address account) public {
        require(hasRole(ROL_ADMIN, msg.sender), "Esta funcion solo puede ser utilizada por el ADMIN");

        _grantRole(role, account);
    }

}

La cuenta que despliega el contrato, ser谩 la due帽a del mismo por defecto, asignando este en el constructor del contrato. Luego de esto, puede utilizar la funci贸n agregarRol() para asignar roles a otras cuentas.

La creaci贸n de roles se realiza de la forma:

bytes32 public constant ROL_ADMIN = keccak256("ROL_ADMIN");

La funci贸n keccak256() encripta y convierte en bytes32 el nombre del rol para poder asignarlo a las cuentas posteriormente de forma segura.

La misma crea una funci贸n p煤blica para que puedas obtener el hash del rol y poder utilizarlo.

ROL_ADMIN = 0x7e5b835f8ef15bb117d49cb6d0658113bc08d41d8a6edf12d3d1feb5c5875330
ROL_USUARIO = 0x575582185c1302752a00c2a38ce472577737eb5e4ab29b34daf1ccaa4c9fe08a

La asignaci贸n de los roles debe hacerse en bytes32 y no con el String con el que fue definido:

Manipulaci贸n de roles

Es importante destacar que, as铆 como el owner del contrato puede asignar roles, tambi茅n puede quitar roles con la funci贸n revokeRole(address).

Las propias cuentas pueden renunciar a un rol si consideran que ya no lo necesita o no lo quieren con la funci贸n renounceRole(bytes32,address).

Puede no ser necesario crear un ROL_ADMIN, ya que el contrato AccessControl ya dispone de este rol por defecto.

bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

A partir de todas estas caracter铆sticas que necesitas para el manejo de roles, puedes desarrollar tus contratos de la forma m谩s conveniente.

Conclusi贸n

Sabemos que la principal filosof铆a de Blockchain es la descentralizaci贸n, pero en determinados casos de uso se puede requerir tareas centralizadas en pocos usuarios.

No necesariamente un contrato va a manipular grandes fondos de dinero, y si es el caso, pueden existir razonables motivos para utilizar roles y privilegios para administrar estos. Lo importante es que los usuarios del contrato sepan que esto es as铆 y est茅n de acuerdo.

Por ejemplo, un contrato que guarda informaci贸n de estudiantes y sus notas. Los propios estudiantes no deben ser capaz de modificar sus datos, solo un profesor debe poder hacerlo.

Implementa la administraci贸n de roles de forma inteligente para crear tus contratos y que cada tipo de cuenta tenga sus responsabilidades. El caso de uso que tengas que resolver determinar谩 c贸mo debes desarrollar tu contrato.


Contribuci贸n creada por: Kevin Fiorentino (Platzi contributor).

Aportes 11

Preguntas 3

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

Por ac谩 dejo un contrato donde pongo a prueba todo lo explicado en la clase y algunas cosas adicionales que entend铆 en la documentacion

Tambi茅n hice unos unit tests para probar los distintos casos de uso del contrato. Creo les puede interesar verlos tambien:

Pueden correr los unit tests con hardhat: npx hardhat test

Cuando el profe se refiere a la cuenta de admin por default del contrato (cuenta cero) es que el rol de admin se asigna a esta direcci贸n:

0x0000000000000000000000000000000000000000000000000000000000000000

Me imagino que esta direcci贸n es a la que se le asigna el rol de owner cuando renuncias a 茅l (clase pasada Ownable.sol). Si desplegamos el contrato sin el constructor (sin la asignaci贸n inicial) va a quedar inutilizable.

En lugar de utilizar require dentro del cuerpo de la funci贸n, tambi茅n se puede utilizar el modificador onlyRole(ROL_XXX).

Ejemplo:

function soloUsuario() public onlyRole(ROL_USUARIO) {
    
}

Parece ser que la diferencia entre鈥

_grantRole()
_setupRole()

Es que _serutRole() no requere que quien llame a la funci贸n tenga el rol de admin, pero si es necesario con _grantRole()

Definir roles suena a centralizar actividades o cosas, no s茅 si por ah铆 exista alg煤n ejemplo para poder ver c贸mo los aplican en la producci贸n.

Por otro lado, es interesante ver c贸mo en solidity todo es estados y m谩s estados, se juega mucho con reglas l贸gicas y hay que poner atenci贸n a lo que hacemos o podr铆amos dejar una puerta abierta que alg煤n usuario malicioso podr铆a explotar.

La asignaci贸n de los roles debe hacerse en bytes32 y no con el String con el que fue definido:

ROL_ADMIN = 0x7e5b835f8ef15bb117d49cb6d0658113bc08d41d8a6edf12d3d1feb5c5875330
ROL_USUARIO = 0x575582185c1302752a00c2a38ce472577737eb5e4ab29b34daf1ccaa4c9fe08a

Si en las pruebas quieren agregar m谩s usuarios a alguno de los roles en el constructor deben asignar ROL_ADMIN como Admin de los otros roles, de lo contrario no les dejar谩.

Por ejemplo, si quieren que la cuenta sender tenga el ROL_ADMIN y pueda agregar usuarios al ROL_USUARIO, pueden usar el siguiente c贸digo en el constructor:

constructor() {
	_grantRole(ROL_ADMIN, msg.sender);
	_setRoleAdmin(ROL_USUARIO, ROL_ADMIN);
}

La funci贸n agregarRol no me funcion贸, este es el error que obtengo al ejecutarla:

"AccessControl: account 0x5b38da6a701c568545dcfcb03fcb875f56beddc4 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000"

Para solucionarlo:

  • Elimin茅 la constante ROL_ADMIN.
  • Modifiqu茅 el constructor a lo siguiente:
constructor() {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
  • Modifiqu茅 agregarRol a lo siguiente:
function agregarRol(bytes32 role, address account) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Only admin");
    _grantRole(role, account);
}

En mi caso me sali贸 un error de:
Warning: Function state mutability can be restricted to view
鈥> contracts/2_Roles.sol:17:5:

Lo solucion茅 a帽adiendo al final del public la palabra view, pero no se si estar谩 bien o no鈥

No es necesario crear un ROL_ADMIN ya que el contrato ya dispone de este rol por defecto.

bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

El contrato de Access Control

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";


abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;


    modifier onlyRole(bytes32 role) {
        _checkRole(role, _msgSender());
        _;
    }


    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }


    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }


    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(uint160(account), 20),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }


    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }


    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }


    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }


    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }


    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }


    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }


    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }


    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}