Crea una cuenta o inicia sesión

¡Continúa aprendiendo sin ningún costo! Únete y comienza a potenciar tu carrera

Curso de Introducción a OpenZeppelin

Curso de Introducción a OpenZeppelin

Sebastián Leonardo Perez

Sebastián Leonardo Perez

Reto: control de acceso

5/19
Recursos

Es momento de afianzar los conocimientos y poner a prueba todo lo aprendido hasta aquí.

Desafío de roles y acceso en un contrato

Utilizando como base el siguiente contrato inteligente que puedes encontrar por defecto en el entorno de Remix.

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

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 * @custom:dev-run-script ./scripts/deploy_with_ethers.ts
 */
contract Storage {

    uint256 number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public {
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}

Implementa las siguientes funcionalidades:

  • Implementa dos roles: “Admin” y “Writer”.
  • El rol “Admin” podrá asignar nuevos “Writer”.
  • El rol “Writer” será el único habilitado a utilizar la función store() del contrato base.
  • La función retrieve() no tendrá limitaciones de acceso.
  • Utiliza modificadores para validar los permisos del usuario.

Ponte a prueba. Te aconsejo no hacerte spoiler con las soluciones de los compañeros. Has un esfuerzo primero por implementar tu solución antes de continuar.

Finalmente, comparte tu solución y compárala con otras. Nunca hay una sola solución a un mismo problema.


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

Aportes 35

Preguntas 6

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Interesante la mejora para facilitar la adición de los roles.
Desafio completado en Hardhat

Si tienen duda de donde provienen las funciones como:

hasRole()
_grantRole()
_revokeRole()

Son funciones heradas del AccessControl.sol que pueden encontrar el contrato aqui
que necesitarias para una parte del reto.

Reto:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract Storage is AccessControl{

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

    uint256 number;

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

    function store(uint256 num) public {
        require( hasRole(ROL_WRITER, msg.sender), "Esta funcion solo puede ser utilizada por el WRITER" );
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }

    modifier onlyAdmin(){
         require( hasRole( ROL_ADMIN, msg.sender ), "Esta funcion solo puede ser utilizada por el ADMIN" );

         _;
     }

    function agregarRol( bytes32 role, address account ) public onlyAdmin() {
        _grantRole( role, account );
    }
    
    function quitarRol( bytes32 role, address account ) public onlyAdmin() {
        _revokeRole( role, account );
    }

}

Pequeña satisfacción de ver que la solución que implementé fue exactamente igual que el profe. Dejaré mi código en GitHub.

Reto completado

He usado herencia y la librería de Access Control para el Ownable y Roles.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.8 <0.9.0;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../contracts/1_Storage.sol";


contract primerRetoOpenZeppelin is Ownable, AccessControl, Storage {

    // Constantes en bytes32 incluiendo encriptacion para la asignación de roles predefinidos.
    // Constants in bytes32, including encryption for predefined role assignment.

    bytes32 public constant ADMIN_ROL = keccak256("Admin_rol");
    bytes32 public constant WRITER_ROL = keccak256("Writer_rol");

    // Instancia de constructor, para asignar por defecto un rol administrativo al contrato.
    // Cuando se utiliza Ownable, el constructor predeterminado define que la cuenta utilizada es el propietario que implementa el contrato.
    // Constructor instance, to assign an administrative role to the default contract.
    // When Ownable is used, the default constructor defines that the account used is the owner that implements the contract.
    constructor(){
        _grantRole(ADMIN_ROL, msg.sender);
    }

    //Modifiers belonging to the ADMIN and WRITER roles.
    //Modificadores pertenencientes a los roles ADMIN y ESCRITOR.
    modifier AdminRole(){
        require(hasRole(ADMIN_ROL,msg.sender), "Function valid for the Contract Administrator");
        _;
    }
    modifier WriterRole(){
        require(hasRole(WRITER_ROL,msg.sender), "Function valid for the Writer of the contract");
        _;
    }

    // Required feature for the challenge, add writer and remove writer.
    // Funcion requeridas para el reto, agregar escritor, y remover escritor.

    function addWriter(bytes32 role, address account) 
    public 
    AdminRole
    {
        _grantRole(role,account);
    }

    function RemoveWriter(bytes32 role, address account) 
    public 
    AdminRole
    {
        _revokeRole(role,account);
    }

    function store(uint256 num) 
    public override
    WriterRole
    {
        number = num;
    }

}

Los requerimientos del reto están en el contrato, añadiendo otros plus como:

  • Librerías: Access Control Ownable, AccessControl.

  • Convierte en el propietario del contrato a quien lo despliega.

  • Transferir a otro ownership y renunciar automaticamente.

  • Renunciar al contrato pone como owner 0 al contrato. Aunque se puede seguir teniendo rol de ADM.

  • El rol ADM se pone por defecto al desplegar el contrato.

  • El ADM puede agregar Role Writer y remover.

  • El Writer puede accionar a la función Store.

  • La función receive es accesible por todos.

Esta es mi solución al reto, usando un Enum con las acciones Grant y Revoke para tener una sola función al momento de agregar o quitar el rol Writer.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is AccessControl{

    enum RoleAction { Grant, Revoke}

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

    uint256 number;

    modifier onlyWriter() {
        require(hasRole(ROL_WRITER,msg.sender),"Only WRITER can call this function");
        _;
    }

    modifier onlyAdmin() {
        require(hasRole(ROL_ADMIN,msg.sender),"Only ADMIN can call this function");
        _;
    }

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

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter{
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }

    function modifyWriterRole(address account, RoleAction actionToDo) public onlyAdmin{
        if(actionToDo == RoleAction.Grant){
            _grantRole(ROL_WRITER,account);
        }
        else if(actionToDo == RoleAction.Revoke){
            _revokeRole(ROL_WRITER,account);
        }
    }
}

Este es mi desafío, lo quise hacer también con herencia para aplicar los conceptos aprendidos en los anteriores cursos:
Aclaración: Para esto tuve que modificar la función store del código de 1_Storage.sol, agregarle virtual para luego poder agregarle el modifier:

  • 1_Storage.sol:
// Único cambio aplicado al contrato base:
function store(uint256 num) public virtual {
        number = num;
    }
  • Solución del reto en el contrato StorageChallenge.sol::
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract StorageChallenge is AccessControl, Storage {

    bytes32 public constant ADMIN = keccak256("ADMIN");
    bytes32 public constant WRITER = keccak256("WRITER");

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

    modifier isAdmin() {
        require(hasRole(ADMIN, msg.sender), "Only the admin can  access to this function");
        _;
    }

    modifier isWriter() {
        require(hasRole(WRITER, msg.sender), "Only the WRITER can  access to this function");
        _;
    }

    function giveRole(address _writer) external isAdmin {
        _grantRole(WRITER, _writer);
    }

    function revokeRole(address _writer) external isAdmin {
        _revokeRole(WRITER, _writer);
    }

    function store(uint256 num) public override isWriter {}
}

Desafío cumplido:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.2 <0.9.0;


import "@openzeppelin/contracts/access/AccessControl.sol";
/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 * @custom:dev-run-script ./scripts/deploy_with_ethers.ts
 */
contract Storage is AccessControl {
    bytes32 public constant Admin = keccak256("Admin");
    bytes32 public constant Writer = keccak256("Writer");

    uint256 number;

    constructor() {
        _grantRole(Admin, msg.sender);        
        _setRoleAdmin(Writer, Admin);
    }

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public  onlyRole(Writer){
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}

En mi solución al reto utilicé el DEFAULT_ADMIN_ROLE para que asigne y quite ADMIN_ROLE

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

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

contract Storage is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant WRITER_ROLE = keccak256("WRITER_ROLE");

    uint256 number;

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    modifier onlyAdmin() {
        require(hasRole(ADMIN_ROLE, msg.sender), "Caller is not an admin");
        _;
    }

    modifier onlyWriter() {
        require(hasRole(WRITER_ROLE, msg.sender), "Caller is not a writer");
        _;
    }

    modifier onlyDefaultAdmin() {
        require(
            hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
            "Caller is not a default admin"
        );
        _;
    }

    function grantWriterRole(address writer) public onlyAdmin {
        grantRole(WRITER_ROLE, writer);
    }

    function revokeWriterRole(address writer) public onlyAdmin {
        revokeRole(WRITER_ROLE, writer);
    }

    function grantAdminRole(address admin) public onlyDefaultAdmin {
        grantRole(ADMIN_ROLE, admin);
    }

    function revokeAdminRole(address admin) public onlyDefaultAdmin {
        revokeRole(ADMIN_ROLE, admin);
    }

    function store(uint256 num) public onlyWriter {
        number = num;
    }

    function retrieve() public view returns (uint256) {
        return number;
    }
}

2 - 0 Ganando

Mi código:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract Storage is AccessControl {

    bytes32 public constant ROLE_ADMIN = keccak256("ROLE_ADMIN");
    bytes32 public constant ROLE_WRITER = keccak256("ROLE_WRITER");

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

    modifier onlyAdmin(){
        require(hasRole(ROLE_ADMIN, msg.sender), "This function can only be executed by the admin");
        _;
    }

    modifier onlyWriter() {
        require(hasRole(ROLE_WRITER, msg.sender), "This function can only be executed by a user");
        _;
    }

    uint256 number;

    function grantWriter(address account) public onlyAdmin() {
        _grantRole(ROLE_WRITER,account);

    }

    function RevokeWriter(address account) public onlyAdmin() {
        _revokeRole(ROLE_WRITER,account);

    }

    function store(uint256 num) public onlyWriter {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }
}

Mi código:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract Storage2 is AccessControl{

    uint256 number;

    bytes32 public constant Admin = keccak256("Admin");
    bytes32 public constant Writer = keccak256("Writer");

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

    function store(uint256 num) public onlyWriter {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }

    modifier onlyAdmin() {
        require(hasRole(Admin, msg.sender), "Esta funcion solo puede ser utilizada por el Admin");
        _;
    }

    modifier onlyWriter() {
        require(hasRole(Writer, msg.sender), "Esta funcion solo puede ser utilizada por un Writer");
        _;
    }

    function addWriter(address account) public onlyAdmin {
        _grantRole(Writer, account);
    }

    function popWriter(address account) public onlyAdmin {
        _revokeRole(Writer, account);
    }

}

Mi pequeño aporte trate de practicar la herencia de paso

//SPDX-License-Identifier: GPL-3.0;

pragma solidity >=0.7.0 < 0.9.0;

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

contract Desafio is AccessControl, Storage {
    
    bytes32 public constant ADMIN = keccak256("ADMIN");
    bytes32 public constant WRITER = keccak256("WRITER");
    
    constructor(){
        _grantRole( ADMIN, msg.sender);
    }
    modifier OnlyAdmin(){
        require(hasRole(ADMIN,msg.sender),"Solamente un administrador puede realizar esta accion");
        _;
    }
    modifier OnlyWriter(){
        require(hasRole(WRITER,msg.sender),"Solamente un writer puede realizar esta accion");
        _;
    }
    function store(uint256 num) public OnlyWriter override {
        number = num;
    }

    function retrieve() public view  override  returns (uint256){
        return number;
    }

    function addWriter(address user) OnlyAdmin public {
        _grantRole(WRITER,user);
    }

    function removeWriter(address user) OnlyAdmin public  {
        _revokeRole(WRITER,user);
    }
    
}   

Me encantó esta clase! vamos por más retos!

Así quedó

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

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


contract RolesDeAcceso is AccessControl {

    bytes32 roleAdmin = keccak256("ROL_ADMIN");
    bytes32 roleWriter = keccak256("ROL_WRITER");

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

    modifier onlyAdmin() {
        require(hasRole("ROL_ADMIN", msg.sender), "Solo el admin puede usar esta funcion");
        _;
    }

    modifier onlyWriter() {
        require(hasRole("ROL_WRITER", msg.sender), "Solo el writer puede usar esta funcion");
        _;
    }

    function setWriter(address cuentaWriter) public onlyAdmin{
        _grantRole("ROL_WRITER", cuentaWriter);
    }

    function removeWriter(address cuentaWriter) public onlyAdmin{
        _revokeRole("ROL_WRITER", cuentaWriter);
    }
    
    // Aquí comienza el contrato storage

    uint256 number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter{
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
    
}
// SPDX-License-Identifier: GLP-3.0

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

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

contract ContratoConRoles is AccessControl {

    bytes32 rolAdmin = keccak256("ROL_ADMIN");
    bytes32 rolWriter = keccak256("ROL_WRITER");
    uint256 number;

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

    function store(uint256 num) public onlyWriter {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }

    modifier onlyWriter() {
        require(hasRole(rolWriter, msg.sender), "Only user role writer");
        _;
    }

    modifier onlyAdmin() {
        require(hasRole(rolAdmin, msg.sender), "Only admin");
        _;
    }

    function addWriter(address account) public onlyAdmin {
        _grantRole(rolWriter, account);
    }

    function removeWriter(address account) public onlyAdmin {
        _revokeRole(rolWriter, account);
    }
}```

Mi solución al reto.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/access/AccessControl.sol";

contract Storage is AccessControl {

    bytes32 private constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 private constant WRITER_ROLE = keccak256("WRITER_ROLE");
    uint256 number;

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

    function store(uint256 num) public onlyWriter {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }

    modifier onlyWriter(){
        require(hasRole(WRITER_ROLE, msg.sender), "Only Writer can use his function");
        _;
    }

     modifier onlyAdmin(){
        require(hasRole(ADMIN_ROLE, msg.sender), "Only Admin can use his function");
        _;
    }

    function addWriter(address _account)public onlyAdmin {
        _grantRole(WRITER_ROLE, _account);
    }

    function removeWriter(address _account)public onlyAdmin{
        _revokeRole(WRITER_ROLE, _account);
    }

    
}

Otra manera de utilizar los modificadores:

function deleteRoleWriter(address _writer) public onlyRole(ROL_ADMIN) {
        _revokeRole(ROL_WRITER, _writer);
    }

Haciendo uso de la herencia

Por acá dejo mis soluciones utilizando foundry para realizar pruebas.

Desafio completado

Esta es mi solución antes de ver la clase:
Roles.sol & _Storoage.sol

Solución al reto:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

/**
' * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is AccessControl {

    uint256 number;

    //Roles
    bytes32 public constant ROLE_ADMIN = keccak256("ROLE_ADMIN");
    bytes32 public constant ROLE_WRITER = keccak256("ROLE_WRITER");

    constructor(){
        //Creator will have the admin role
        _grantRole(ROLE_ADMIN, msg.sender);
    }


    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter {
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }

    function addWriter(bytes32 role, address account) public onlyAdmin {
        _grantRole(role, account);
    }

    function removeWriter(bytes32 role, address account) public onlyAdmin{
        _revokeRole(role,account);
    }

    /**
    * @dev Verify the user has the admin role
    */
    modifier onlyAdmin(){
        require(hasRole(ROLE_ADMIN, msg.sender), "Only admin can do this");
        _;
    }

    /**
    * @dev Verify the user has the writer role
    */
    modifier onlyWriter(){
        require(hasRole(ROLE_WRITER, msg.sender), "Only writer can do this");
        _;
    }
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

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

contract Storage is AccessControl {

    bytes32 public constant ADMIN = keccak256("ADMIN");
    bytes32 public constant WRITER = keccak256("WRITER");   

    uint256 number;

    constructor(){
        _setupRole(ADMIN, msg.sender);
    }

    modifier functionAccess ( bytes32 _role ) {
        require(
            hasRole( _role , msg.sender),
            "Sender not authorized"
        );
        _;
    }


    function addRol( address account  ) public functionAccess( ADMIN ) {
        _grantRole(WRITER, account);
    }

    function revokeRol( address account ) public functionAccess( ADMIN ) {
        _revokeRole(WRITER, account);
    }
    
    function store(uint256 num) public functionAccess( WRITER ) {
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }
}```

Reto:

<// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is AccessControl {
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant WRITER_ROLE = keccak256("WRITER_ROLE");
    uint256 number;

    constructor() {
        _setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE);
        _setRoleAdmin(WRITER_ROLE, ADMIN_ROLE);

        _grantRole(ADMIN_ROLE, _msgSender());
        _grantRole(ADMIN_ROLE, address(this));
    }

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyRole(WRITER_ROLE) {
        number = num;
    }

    /**
     * @dev Return value
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256) {
        return number;
    }
}
> 

Aqui solución del reto, sin override del contrato Storage:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract ContratoConRoles is AccessControl, Storage {

    bytes32 public constant rolAdmin = keccak256("ROL_ADMIN");
    bytes32 public constant rolWriter = keccak256("ROL_WRITER");

    modifier onlyWriter() {
        require(hasRole(rolWriter,msg.sender),"Only WRITER can call this function");
        _;
    }

    modifier onlyAdmin() {
        require(hasRole(rolAdmin,msg.sender),"Only ADMIN can call this function");
        _;
    }
    constructor() {
        _grantRole(rolAdmin, msg.sender);
    }


    function agregarRol(address account) public onlyAdmin{
       _grantRole(rolWriter,account);
    }
    
    function quitarRol(address account) public onlyAdmin{
       _revokeRole(rolWriter,account);
    }
    function agregarStorage(uint256 num) public onlyWriter{
        store (num);
    }
    
    function recuperarStorage() public view returns (uint256){
        return retrieve();
    }
}

Reto:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is AccessControl{

    uint256 number;
    bytes32 constant public ROL_ADMIN = keccak256("ROL_ADMIN");
    bytes32 constant public ROL_WRITER = keccak256("ROL_WRITER");
    
    constructor(){
        _grantRole(ROL_ADMIN, msg.sender);
    }

    modifier onlyAdmin(){
        require(hasRole(ROL_ADMIN, msg.sender),"You dont have access of admin");
        _;
    }

    modifier onlyWriter(){
        require(hasRole(ROL_WRITER, msg.sender),"You dont have access of writer");
        _;
    }
    
    function addWriterRole(address newWriter) public onlyAdmin{
        _grantRole(ROL_WRITER,newWriter);
    }

    function removeWriterRole(address removeWriter) public onlyAdmin{
        _revokeRole(ROL_WRITER,removeWriter);
    }

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter{
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}

Mi implementación utilizando el modificador onlyRole() que se encuentra en AccessControl.sol:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

import "oz-access/AccessControl.sol";

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is AccessControl {

    bytes32 public Admin = keccak256("ADMIN");
    bytes32 public Writer = keccak256("WRITER");

    uint256 number;

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

    /// @notice grants Writer role to newWriter, only an admin can do this
    function addWriter(address newWriter) public onlyRole(Admin) {
      _grantRole(Writer, newWriter);
    }

    /// @notice revokes Writer role to exWriter, only an admin can do this
    function removeWriter(address exWriter) public onlyRole(Admin) {
      _revokeRole(Writer, exWriter);
    }

    /**
     * @dev Store value in variable, only a writer can do this
     * @param num value to store
     */
    function store(uint256 num) public onlyRole(Writer) {
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

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

contract Storage is AccessControl{

    bytes32 public constant ROL_ADMIN = keccak256("ROL_ADMIN");
    bytes32 public constant ROL_WRITER = keccak256("ROL_WRITER");
    uint256 number;

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

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) IsWriter() public {
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }


    // Ya que el reto no implica crear roles de manera dinamica solo uso el parametro account
    function addWriter(address account) public IsAdmin(){
        _grantRole(ROL_WRITER, account);
    }

    function deleteWriter(address account) public {
        _revokeRole(ROL_WRITER, account);
    }

    modifier IsAdmin(){
        require(hasRole(ROL_ADMIN, msg.sender), "this function require ROL_ADMIN");
        _;
    }

    modifier IsWriter(){
        require(hasRole(ROL_ADMIN, msg.sender), "this function require ROL_WRITER");
        _;
    }


}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import “@openzeppelin/contracts/access/AccessControl.sol”;

contract Reto is AccessControl {
uint256 contador;
bytes32 rolAdmin = keccak256(“ROL_ADMIN”);
bytes32 rolEscritor = keccak256(“ROL_ESCRITOR”);

constructor() {
    contador = 10;
    _grantRole(rolAdmin, msg.sender);
}

modifier soloEscritura() {
    require(
        hasRole(rolEscritor, msg.sender),
        "Debe tener el rol de escritura para ejecutar la funcion"
    );
    _;
}

modifier soloAdmin() {
    require(
        hasRole(rolAdmin, msg.sender),
        "Debe tener rol de admin para ejecutar la funcion"
    );
    _;
}

function asignarContador(uint256 numero) public soloEscritura {
    contador = numero;
}

function asignarEscritor(address usuario) public soloAdmin {
    _grantRole(rolEscritor, usuario);
}

function eliminarEscritor(address usuario) public soloAdmin {
    _revokeRole(rolEscritor, usuario);
}

}

Buen ejercicio creo que por fin entendí para que sirve el _setRoleAdmin lo único que aun no me queda
del todo claro es si es correcto hacer _setRoleAdmin(_setRoleAdmin, _setRoleAdmin) que admin sea su
propio admin para que pueda pasar los controles cuando se usa

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/access/AccessControl.sol";

contract Storage is AccessControl {
    bytes32 public constant ROL_ADMIN = keccak256("ROL_ADMIN");
    bytes32 public constant ROL_WRITER = keccak256("ROL_WRITER");
    uint256 number;

    constructor() {
        _grantRole(ROL_ADMIN, _msgSender());
        _setRoleAdmin(ROL_WRITER, ROL_ADMIN);
    }

    function addRoleWriter(address account) public onlyRole(getRoleAdmin(ROL_WRITER)) {
        _grantRole(ROL_WRITER, account);
    }

    function removeRoleWriter(address account) public onlyRole(getRoleAdmin(ROL_WRITER)) {
        _revokeRole(ROL_WRITER, account);
    }

    function store(uint256 num) public onlyRole(ROL_WRITER){
        number = num;
    }

    function retrieve() public view returns (uint256){
        return number;
    }
}

Lo habia logrado sin modificadores, cuando vi el video dije, upss se me olvido XD

Hola en un principio me quedó así, le agregue la función de cambiar admin, por el momento parece funcionar, si encuentran algún error me dice XD

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

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

contract ControlAccess is AccessControl{
    bytes32 ROL_ADMIN = keccak256("ROL_ADMIN");
    bytes32 ROL_WRITER = keccak256("ROL_WRITER");


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

    uint256 number;

    function setWriter(address direccion) public onlyAdmin{
        
        _grantRole(ROL_WRITER,direccion);

    }
    function revokeWriter(address direccion) public onlyAdmin{
        
        _revokeRole(ROL_WRITER,direccion);

    }
    function changeAdmin(address nuevoAdmin) public onlyAdmin{
        _revokeRole(ROL_ADMIN,msg.sender);
        _grantRole(ROL_ADMIN,nuevoAdmin);
    }

    modifier onlyAdmin{
        require(hasRole(ROL_ADMIN,msg.sender), "No eres admin, Sal de ahi!");
        _;
    }
    modifier onlyWriter{
        require(hasRole(ROL_WRITER,msg.sender), "No eres Writer, Sal de ahi!");
        _;
    }

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter{
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}

Me encanto el desafío

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

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

contract RetroControlAccesso is AccessControl {

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

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

    modifier onlyAdmin  {
        require(hasRole(ROL_ADMIN, msg.sender),"Esta funcion solo puede ser utilizada por el rol ADMIN");
        _;
    }

    modifier onlyWriter {
        require(hasRole(ROL_WRITER, msg.sender),"Esta funcion solo puede ser utilizada por el rol WRITER");
        _;
    }

    function agregarRolWriter(address account) public onlyAdmin {
        _grantRole(ROL_WRITER,account);
    }

    function quitarRolWriter(address account) public onlyAdmin {
        _revokeRole(ROL_WRITER,account);
    }
}


/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */
contract Storage is RetroControlAccesso {

    uint256 number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter {
         number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }
}
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

/**
 * @title Storage
 * @dev Store & retrieve value in a variable
 */

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

contract Storage is AccessControl {

    bytes32 rolAdmin = keccak256("ROL_ADMIN");
    bytes32 rolWriter = keccak256("ROL_WRITER");

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

    uint256 number;

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint256 num) public onlyWriter{
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public view returns (uint256){
        return number;
    }

    
    modifier onlyWriter() {
        require(hasRole(rolWriter, msg.sender), "Solo pueden ejecutar la funcion los roles Writer");
        _;
    }

    modifier onlyAdmin() {
        require(hasRole(rolAdmin, msg.sender), "Solo pueden ejecutar la funcion los roles Admin");
        _;
    }

    function agregarWriter(address cuenta) public onlyAdmin {
        _grantRole(rolWriter, cuenta);
    }

    function quitarWriter(address cuenta) public onlyAdmin {
        _revokeRole(rolWriter, cuenta);
    }




}

Me encanto esta clase!!