Assignment - Openzeppelin Templates

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *  - a change cap role that allows for changing the token cap
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract MyToken is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CHANGE_CAP_ROLE = keccak256("CHANGE_CAP_ROLE");

    uint256 private _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, `PAUSER_ROLE` and 'CHANGE_CAP_ROLE'to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     * See {ERC20Capped-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 cap) ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CHANGE_CAP_ROLE, _msgSender());

        require(cap > 0, "ERC20Capped: cap is 0");
        _cap = cap;
    }

    /**
     * From ERC20Capped.sol
     * @dev Returns the cap on the token's total supply.
     */
    function getCap() public view virtual returns (uint256) {
        return _cap;
    }

    /**
     * @dev Resets the cap of the token.
     *
     * Requirements:
     *
     * -The caller must have the CHANGE_CAP_ROLE role
     */
    function setCap(uint256 _newCap) public virtual {
        require(hasRole(CHANGE_CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have change cap role to mint");
        _cap = _newCap;
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(ERC20.totalSupply() + amount <= getCap(), "ERC20Capped: cap exceeded");
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }
}

1 Like

Here is my solution:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../extensions/ERC20Burnable.sol";
import "../extensions/ERC20Pausable.sol";
import "../extensions/ERC20Capped.sol";
import "../../../access/AccessControlEnumerable.sol";
import "../../../utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAP_ROLE = keccak256("CAP_ROLE");
    uint256 immutable private _cap; // added 
    

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 tokenBalance, uint256 marketCap) ERC20(name, symbol) {
        require(marketCap > 0, "ERC20Capped: cap is 0");
        require(tokenBalance <= marketCap, "ERC20Capped: cap exceeded");
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAP_ROLE, _msgSender());

        
        _cap = marketCap; //set _cap to marketCap
        mint(_msgSender(), tokenBalance);
    }



    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

        function cap() public view virtual returns (uint256) {
        return _cap;
    }

    function setCap(uint256 _newCap) public virtual returns(uint256) {
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20Capped: must have CAP role to set market cap");
        require(_newCap > 0, "ERC20Capped: the cap has to be bigger than 0");
        _newCap = _cap;
        return _cap;
    }

        function _mint(address account, uint256 amount) internal virtual override {
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have Cap role to cap the supply.");
        super._mint(account, amount);
    }





    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }
}

had quite some fun doing this :smiley: :smiley:

1 Like

My solution

Code
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";


/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract ERC20PresetMinterPauser is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");
    uint256 private _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {
        _cap = cap_;
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAPPER_ROLE, _msgSender());
    }

    function cap() public view virtual returns (uint256) {
        return _cap;
    }

    function setCap(uint256 cap_) public {
        require(hasRole(CAPPER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have capper role to change cap");
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);

        if ( from == address(0) )
        {
            require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        }
    }
}

1 Like

This is my code, I have some questions thou (bellow):

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract ERC20PresetMinterPauserCapper is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");

    uint256 private _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAPPER_ROLE, _msgSender());

        require(cap_ > 0, "ERC20PresetMinterPauserCapper: cap is 0");
        _cap = cap_;
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauserCapper: must have minter role to mint");
        require(ERC20.totalSupply() + amount <= cap(), "ERC20PresetMinterPauserCapper: cap exceeded");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauserCapper: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauserCapper: must have pauser role to unpause");
        _unpause();
    }

    function cap() public view virtual returns (uint256) {
        return _cap;
    }

    function setCap(uint256 cap_) public virtual {
        require(hasRole(CAPPER_ROLE, _msgSender()), "ERC20PresetMinterPauserCapper: must have capper role to set cap");
        require(cap_ >= totalSupply(), "You can't set cap lower than total supply");
        require(cap_ > 0, "ERC20PresetMinterPauserCapper: cap is 0");
        _cap = cap_;
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }
}

  1. I watched the solution video afterwards and I found out that this require statement is in _beforeTokenTransfer function. However I put it into mint function instead. Could this be a problem?
require(ERC20.totalSupply() + amount <= cap(), "ERC20PresetMinterPauserCapper: cap exceeded");
  1. I tested my solution and it works with only one exception, when I try to grantRole or _setupRole, it says that MINTER_ROLE is not defined. However in constructor it worked. What is a problem? Thanks.
instance.grantRole(MINTER_ROLE, accounts[1])
instance._setupRole(MINTER_ROLE, accounts[1])

image

1 Like

Solution:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract capAssignment is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPER_ROLE = keccak256("CAPER_ROLE");
    uint256 _cap;
    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(CAPER_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }
    
        /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _cap;
    }




    function _mint(address account, uint256 amount) internal virtual override {
        require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        super._mint(account, amount);
    }

    
    function setnewCap(uint256 _newCap) public {
        require(hasRole(CAPER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have caper role to unpause");
        _cap = _newCap;
    }

}

1 Like

Hi @Tomahawk

  1. I watched the solution video afterwards and I found out that this require statement is in _beforeTokenTransfer function. However I put it into mint function instead. Could this be a problem?

Check inside the mint function is alright.

I tested my solution and it works with only one exception, when I try to grantRole or _setupRole , it says that MINTER_ROLE is not defined.However in constructor it worked.

The tests are in another environment, if you use MINTER_ROLE Javascript thinks itā€™s a variable, therefore it will throw an error as that variable has not been declared.

You can do that in your smart contract because that variable is declared there:

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");

Therefore the constructor can use them.

Make sure to pass a string ā€œMINTER_ROLEā€ in your tests.

Cheers,
Dani

1 Like

Thanks, so there is no need to have it in _beforeTokenTrasnfer function, such has Filip (code bellow)?. Or would it be more safe if we use it in both functions?

function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);

        if (from == address(0)) { //when mint
        require(totalSupply() + amount <= cap(), "ERC20PresetMinterPauserCapper: cap exceeded");
        }
    }

Btw I tried it with string ā€œMINTER_ROLEā€ before as well and there is also an error. Where is it the best to test it then?
image

Hereā€™s my solution code. In my code Iā€™ve left my _beforeTokenTransfer function empty.
However after watching Filipā€™s solution video, Iā€™d like to double check if itā€™s also advisable to add the checking code into the _beforeTokenTransfer function (as Fillip does, see function below) - I donā€™t think itā€™s necessary given that the OZ _mint function already contains a similar check, but perhaps Iā€™ve missed something?

function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);

        if (from == address(0)) { //when mint
        require(totalSupply() + amount <= cap(), "ERC20PresetMinterPauserCapper: cap exceeded");
        }
    }

My solution is below (without any code in _beforeTokenTransfer function):

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract MyToken is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAP_ADMIN_ROLE = keccak256("CAP_ADMIN_ROLE");

    uint256 private _cap; 

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, `PAUSER_ROLE` and
     * 'CAP_ADMIN' to the account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    /**
     * @dev Sets the value of the `cap`. This value is immutable, it can only be
     * set once during construction.
     * Note: ERC20Capped.sol constructor code was copied & pasted into MyToken constructor.
     */
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;

        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAP_ADMIN_ROLE, _msgSender());
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }

    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _cap;
    }

    /**
     * @dev Changes the contract's token cap.
     *     *
     * Requirements:
     *
     * - the caller must have the `CAP_ADMIN_ROLE`.
     */
    function changeCap(uint256 newCap) public virtual {
        require(hasRole(CAP_ADMIN_ROLE, _msgSender()), "ERC20Capped: have cap admin role");
        _changeCap(newCap);
    }

    function _changeCap(uint256 newCap) internal virtual {
        _cap = newCap;
    }

    /**
     * @dev See {ERC20-_mint}.
     */
    function _mint(address account, uint256 amount) internal virtual override {
        require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        super._mint(account, amount);
    }
}

Assignment Code;

pragma solidity  0.8.4;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

contract MyToken is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");
    uint private _cap;

    
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {
        _cap = cap_;
        
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAPPER_ROLE, _msgSender());
    }

    function cap() public view virtual returns(uint256){
        return _cap;
    }
    function setCap(uint256 cap_)public {
        require(hasRole(CAPPER_ROLE, _msgSender()), "MyToken: must have capper role to change cap");
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

 
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "MyToken: must have minter role to mint");
        _mint(to, amount);
    }

    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "MyToken: must have pauser role to pause");
        _pause();
    }

  
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "MyToken: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);

        if(from == address(0))
        {
            require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        }
    }
}
1 Like
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

/**
 * @dev {ERC20} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract IshoboyToken is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAP_ROLE = keccak256("CAP_ROLE");

    uint256 private _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor(string memory name, string memory symbol, uint256 cap_) ERC20(name, symbol) {

        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAP_ROLE, _msgSender());
        
        //set cap
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

    function cap() public view virtual returns (uint256) {
        return _cap;
    }

    function setCap(uint256 cap_) public {
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have cap role to change cap");
        require(cap_ >= totalSupply(), "ERC20Cap: cap must be greater or equal than totalSupply"); 
        
        _cap = cap_;
    }

    /**
     * @dev Creates `amount` new tokens for `to`.
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 amount) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
        _mint(to, amount);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC20Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
    }

}
1 Like

Hey @Tomahawk,

There is no need to have it in both functions, and have it in the mint function is correct.

Also checked the OZ contract now and I see that grantRole asks for a bytes32 argument.
Just convert your sting to bytes32.

An quick example I wrote for you in the contract migration.

const ERC20PresetMinterPauserCapper = artifacts.require("ERC20PresetMinterPauserCapper");

module.exports = async function (deployer) {
  deployer.deploy(ERC20PresetMinterPauserCapper, "Token", "TKN", 1000000);

  const contract = await ERC20PresetMinterPauserCapper.deployed();

  await contract.grantRole(web3.utils.fromUtf8("MINTER_ROLE"), "0x840876a652b050b8bb11210c671a8fc7964652cb");

  const result = await contract.hasRole(web3.utils.fromUtf8("MINTER_ROLE"),"0x840876a652b050b8bb11210c671a8fc7964652cb");
  console.log(result)
};

Happy learning,
Dani

Assignment Solution

Finished this assignment so here is my solution. I wanted to change things up again as i feel you learn more by doing some things from scratch and expanding the assigment. I wanted to only inherit from the ERC20.sol and to include minting, burning, pausing and cap/increaseCap functonality in the contract so what i did was i started out with the small contarct that we coded in the first video instead of the ERC20PresetMinterPauser contract. I feel like i did learn more because i had to do more myself. However on the other hand my contrarct is not as complex under the hood and may not be as secure as it does not inherit from the AccessControl contract so there is no functionality to grant a user specific roles such as minter or pauser. However i use the onlyOwners modifier to grant admin only access for these operations.

I also stopped inheriting from the ERC20Capped function because i was struggling to implement the increaseCap( ) function whilst inheriting from this contratct, so i decided just to writ emy own version of the cap/IncreaseCap( ) functonality.

Now that i have finished this i am going to go and study the AccesControl.sol file because i think its very interesting and i then will try to implement it in my current program instead of just using the onlyOwners modifier as a way to grant admin privelages. Or else i will just implement increaseCap in the ERC20PresetMinterPauser but probably the former because i think i will learn way more.

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

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";

//our token to be mintable
//our token to be burnable
//our token to be pausable
//our token to have a cap


//here ERC20Cappped inherits from ERC20 so we only referance ERC20Capped
contract MyToken is ERC20, Ownable {


    bool private _paused;
    uint256 private _cap;
    //the ERC20 contratc requires us to define a constructor
    //however we do need to specify the ERC20 and ERC20Capped constructors
    constructor(uint256 cap) ERC20("MyToken", "MTKN") {
        require(cap > 0, "The inital cap must be equal to a value greater than 0");
        _cap = cap;
        _paused = false;
    }


    //all required events
    event Minted(address minter, uint256 amount);
    event capIncreased(address by, uint256 amount);
   

    function cap() public view returns(uint256) {

        return _cap;
    }

    //function that modifies cap only callable by admin
    function IncreaseCap(uint256 _amount) public onlyOwner returns (uint256 ) {

        require(_amount > 0);
        
        //increase cap
        _cap = _cap + _amount;
        //emmit the cap increase transfer event to log
        emit capIncreased(msg.sender, _amount);

        return _cap;
    }

    //execute ERC20Capped outside the constructor
    function _mint(uint256 _amount) public returns(bool _success) {
        
        require(!_paused);
        //the mint is called from ERC20Capped which inherits mint
        //from ERC20 but also has its own functionality on top namely
        //the cap requirment
        _mint(msg.sender, _amount);
        emit Minted(msg.sender, _amount);
        _success = true;

        return _success;

    }

    
    //transfer funds function
    function transfer(address recipient, uint256 amount) public virtual override returns (bool _success) {

        require(recipient == msg.sender);
        //only allow trasnfers when the contratc is not paused
        require(!_paused);
        //call the ERC20.sol transfer function
        ERC20.transfer(recipient, amount);
        _success = true;

        return _success;
    }

    //returns true if contract is paused, false otherwise
    function isPaused() public view returns (bool) {
        return _paused;

    }

    //function that pauses the contract. Can only be called by owner
    function pause() public onlyOwner {

        require(!_paused, "This contratc is already paused" );
        //set _paused to true
        _paused = true;    
    }

    //function that pauses the contract. Can only be called by owner
    function unpause() public onlyOwner {

        require(_paused, "This contratc is already unpaused" );
        //set _paused to true
        _paused = false;    
    }
    
    //function which burns tokens
    function burn(address account, uint256 amount) public virtual {
        //require(account == msg.sender);
        //calls the burn function in the ERC20 Contratc
        _burn(account, amount);
    }

    //get balance of account pased in
    function accountBalance(address account) public view returns (uint256) {

        //calls the balanceOf function in ERC20.sol
        return balanceOf(account);
    }

    //require the cap check here. This funcion gets executed in the ERC20._mint function so if the cap
    //is exceded an error will throw
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override virtual {
        //require that the current total supply plus the amount does
        //not exceed the cap
        require(totalSupply() + amount <= _cap, "Cap is exceeded");

        //call the ERC20 beforeTokenTransfer function
        ERC20._beforeTokenTransfer(from, to, amount);

        
    }
}
1 Like

Good morning everyone. Below is my implementation of the functions Fillip asked us to write/utilize, ALONG WITH AN IMPORTANT QUESTION.

The contract compiles fine, and I can set token caps just fine as well. So, technically speaking, the contracts meets the intent of the assignment as long as I donā€™t try sending any bytes32 values to the contract.

Anytime I try calling any of the functions that take a bytes32 type as an argument, I run into the same issue that @Tomahawk was running into. I havenā€™t been able to resolve the issue yet. Here is a part of the error message:

truffle(develop)> TokenContract.getRoleAdmin("MINTER_ROLE")
Uncaught:
Error: invalid arrayify value (argument="value", value="MINTER_ROLE00000000000000000000000000000000000000000000000000000", code=INVALID_ARGUMENT, version=bytes/5.0.5)

Iā€™ve tried lots of wacky stuff to get the functions to play nice, including writing a separate function to output what SHOULD be the literal value of MINTER_ROLE, and then copying/pasting that value to the function. I tried a bunch of the suggestions I found online, too. Some just feel super-convoluted, and many include methods and libraries weā€™ve not even come close visiting in the course yet.

This feels like one of those errors that has a simple solution. I canā€™t imagine Iā€™m the only one who is hung up by this issue. Guidance would be greatly appreciated. Thanks!

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";


contract MyToken is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
   bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
   bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
   bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");

   uint256 private _cap;

   event CapChanged(uint indexed newCap, uint indexed prevCap, address indexed execuBy);


   
   //throwing error on migration constructor(string memory name, string memory symbol) ERC20("MyToken", "TRIQ"){
   constructor() ERC20("MyToken", "TRIQ"){
       _cap = 100;
       _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

       _setupRole(MINTER_ROLE, _msgSender());
       _setupRole(PAUSER_ROLE, _msgSender());
       _setupRole(CAPPER_ROLE, _msgSender());//added role for changing caps
   }


   function mint(address to, uint256 amount) public virtual {
       require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have minter role to mint");
       _mint(to, amount);
   }


   function pause() public virtual {
       require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to pause");
       _pause();
   }


   function unpause() public virtual {
       require(hasRole(PAUSER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have pauser role to unpause");
       _unpause();
   }

   function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20, ERC20Pausable) {
       super._beforeTokenTransfer(from, to, amount);
   }

   //========================= Capped functionality ============================

   function _changeCap(uint256 cap_) public returns(bool _CapSuccess){ //my own function
       require(hasRole(CAPPER_ROLE, _msgSender()), "Must be authorized to execute a cap change");
       require(cap_ > _cap, "Token Cap cannot decrase"); //keeps from decreasing the cap.  If functionality
       uint oldCap = _cap;                             //needs extended to lowering the cap, supporting
       _setCap(cap_);                                 //functions required
       emit CapChanged(cap_, oldCap, _msgSender());
       return true;
   }

   function _setCap(uint256 cap_) private {
       require(cap_ > 0, "ERC20Capped: cap is 0");
       _cap = cap_;
   }


   function cap() public view virtual returns (uint256) {
       return _cap;
   }

   function _mint(address account, uint256 amount) internal virtual override {
       require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
       super._mint(account, amount);
   }
}

1 Like

Okay makes sense. However I tried it with commands bellow and even though that grantRole now goes through it still says that accounts[1] does not have a MINTER ROLE.

 let instance = await SekynToken.deployed()
instance.grantRole(web3.utils.fromUtf8("MINTER_ROLE"), accounts[1])
instance.mint(accounts[1], 1, {from: accounts[1]})

image

@Melshman

Hi @Tomahawk

Call the grantRole function then hasRole and console log the result, should be true.
If is not true there is something wrong in the code most likely, in that case create a repo on github and share the link.

1 Like

@dan-i it was false :confused: So here is the link for github:

https://github.com/tommahawk25/Token-MinterPauserCapper

Hi @Tomahawk

Please push the whole repository not just few files, I want to be able to deploy using the exact configuration you have in your machine.

@dan-i oh okay, I didinā€™t know I can do that (I am new to github). Here is the repo then, but I had to omit node_modules with @openzeppelin because it was too big to upload. Hope this will be already fine.

https://github.com/tommahawk25/ERC20PresetMinterPauserCapper

@dan-i I had probably one mistake in migration file as in picture bellow (in red fields I kept Migration there instead of contract name, because Philip had it that way in one of his videos). I fixed it. Now it result displayed true, but still when I run mint function it tells me that I need role.

image

Hi @Tomahawk

Iā€™ve suggested you the wrong algo to convert to bytes32, my bad for this one :slight_smile:

Proceed as follow c is my contract instance:

await c.grantRole(web3.utils.soliditySha3("MINTER_ROLE"), accounts[1]);

await c.hasRole(web3.utils.soliditySha3("MINTER_ROLE"),accounts[1])
this should console true.

await c.mint(accounts[1], 10, {from: accounts[1]})

You will be able to mint.

Cheers,
Dani

2 Likes