Assignment - Openzeppelin Templates

I crated the modifyCap() function

And create new role: CAPMOD_ROLE

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

//Todos los imports adaptados a la ruta relativa de este ordenador

import "../node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.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";
import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.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 RuchiCoin is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPMOD_ROLE = keccak256("CAPMOD_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) {
           require(cap_ > 0, "ERC20Capped: cap is 0");
        _cap = cap_;
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(CAPMOD_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
    }


    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _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);
    }

function modifyCap(uint256 cap) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have cap mod role to change cap");
        _cap=cap;
    }


    /**
     * @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
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

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 MyTokenPreset 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 capVal) ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAP_ROLE, _msgSender());
        _cap = capVal;
    }

    /**
     * @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);
    }

      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 updateCap(uint newCap) public virtual{
        require(ERC20.totalSupply() <= newCap, "ERC20PresetMinterPauser: total supply > new cap");
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have CAP role to change cap");
        _cap = newCap;
    }
}

1 Like
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.0 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

pragma solidity >=0.6.0 <0.8.1;

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/AccessControl.sol";
import "../node_modules/@openzeppelin/contracts/utils/Context.sol";

contract MyToken is Context, AccessControl, ERC20Burnable, ERC20Pausable {
    using SafeMath for uint256; //added after have seen the solution video
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant RAISERCAP_ROLE = keccak256("RAISERCAP_ROLE");

    uint256 private _cap;

    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(RAISERCAP_ROLE, _msgSender());
    }

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

    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 raise_cap(uint256 new_cap) public virtual returns (uint256) {
        require(
            hasRole(RAISERCAP_ROLE, _msgSender()),
            "ERC20PresetMinterPauser: must have RAISERCAP role to pause"
        );
        require(
            totalSupply() <= new_cap,
            "New cap can't be less than the minted supply"
        );
        require(new_cap > 0, "The new cap must be more than 0");
        _cap = new_cap;
        return _cap;
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override(ERC20, ERC20Pausable) {
        super._beforeTokenTransfer(from, to, amount);
        if (from == address(0)) { //added after have seen the solution video
            require(
                totalSupply().add(amount) <= cap(),
                "ERC2Capped: cap exceeded"
            );
        }
    }
}

2 Likes

This assignment sounded simple but had some unexpected problems with inheritance that required overriding several functions (even though I wasn’t using them). I found help both online (some notes/links shared in my code) as well as on this forum, thank you everyone!


[EDITS]

  • I didn’t utilize SafeMath (added after seeing this in solution video).

My solution:

NOTE: I was confused on why everyone’s code (including the solution) was so different from mine to find out that I utilized inheritance on the ERC20PresetMinterPauser contract, while everyone else used copy-pasting the contract code… I’m not sure if the preset’s shouldn’t be inherited? I didn’t think of copy-pasting, if the preset was called a template then copying makes more sense to me. :thinking:

pragma solidity ^0.8.7;

import "../node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControl.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";

// to handle the "Derived contract must override function ..." errors, see this
// link:
// https://forum.openzeppelin.com/t/typeerror-derived-contract-must-override-function-beforetokentransfer/2469/10
contract CustomExtensions is AccessControl, ERC20PresetMinterPauser {
    using SafeMath for uint256; // added after seeing solution (I'm use to using "+=")

    string private _name = "CustomExtensions";
    string private _symbol = "CEX";
    uint256 private _totalSupply;
    uint256 private _cap;
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    /**
     * Sets supply `_cap` and grants the ADMIN_ROLE to `_admin`.
     */
    constructor(uint256 cap_, address _admin)
        ERC20PresetMinterPauser(_name, _symbol)
    {
        _setCap(cap_);
        grantRole(ADMIN_ROLE, _admin);
    }

    /**
     * Requires that the address has the `ADMIN_ROLE`.
     */
    modifier isAdmin() {
        require(hasRole(ADMIN_ROLE, msg.sender), "Invalid permissions");
        _;
    }

    /**
     * Returns the supply `_cap`.
     */
    function cap() public view returns (uint256) {
        return _cap;
    }

    /**
     * Updates the max supply `_cap`.
     */
    function updateCap(uint256 cap_) public isAdmin {
        require(_totalSupply <= cap_, "Cap must be <= _totalSupply");
        require(cap_ != _cap, "Will not update _cap to same _cap");
        _setCap(cap_);
    }

    /**
     * Mints the specified `_amount` for `msg.sender`.
     */
    function mint(uint256 _amount) public {
        _mint(msg.sender, _amount);
    }

    /**
     * Mints a specified `_amount` for a specific `_to` address.
     */
    function mintFor(uint256 _amount, address _to) public {
        _mint(_to, _amount);
    }

    /**
     * See `AccessControl.supportsInterface()`.
     */
    function supportsInterface(bytes4 _interfaceId)
        public
        view
        virtual
        override(AccessControl, AccessControlEnumerable)
        returns (bool)
    {
        return super.supportsInterface(_interfaceId);
    }

    mapping(bytes32 => RoleData) private _roles;

    /**
     * See `AccessControl._revokeRole()`.
     */
    function _revokeRole(bytes32 _role, address _account)
        internal
        override(AccessControl, AccessControlEnumerable)
    {
        super._revokeRole(_role, _account);
    }

    /**
     * See `AccessControl._grantRole()`.
     */
    function _grantRole(bytes32 _role, address _account)
        internal
        override(AccessControl, AccessControlEnumerable)
    {
        super._grantRole(_role, _account);
    }

    /**
     * Mints the sent `_amount` to the `_sender` address if the current
     * `_totalSupply + _amount` doesn't exceed the supply `_cap`.
     *
     * See `ERC20._mint()`.
     */
    function _mint(address _sender, uint256 _amount) internal override(ERC20) {
        require(_totalSupply + _amount <= _cap, "This exceeds contracts cap");
        uint256 prevSupply = _totalSupply;
        super._mint(_sender, _amount);
        _totalSupply.add(_amount);
        assert(_totalSupply == prevSupply + _amount);
    }

    /**
     * Sets the `_cap` value.
     */
    function _setCap(uint256 cap_) private {
        _cap = cap_;
    }
}

1 Like
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/utils/Context.sol";
import "../node_modules/@openzeppelin/contracts/access/AccessControlEnumerable.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/ERC20.sol";

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

    uint256 private _cap; //remove immutable when adding modifyCap()

    constructor(string memory name, string memory symbol, uint cap_) ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

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

        require(cap_ > 0, "Cap is 0");
        _cap = cap_;
    }

    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);
    }

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

    function setCap(uint256 new_cap) public virtual returns (uint256) {
      require(hasRole(CAPSETTER_ROLE, _msgSender()), "MyToken2: must have cap setter role to modify cap");
      require(new_cap > 0, "MyToken2: cap must be greater than 0");
      require(new_cap >= ERC20.totalSupply(), "MyToken2: cap is less than total supply");
      _cap = new_cap;
      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

Solution with minimal usage of openzeppelin contracts:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

pragma solidity ^0.8.0;

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/AccessControl.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 AccessControl, 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) {
        require(_cap > 0, "Error: Cap must be greater than 0");
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

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

        cap = _cap;
    }

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

    function changeCap(uint256 amount) public {
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have cap_role to change cap limit");
        require(amount >= ERC20.totalSupply(), "ERC20PresetMinterPauser: New Cap amount must be greater than the current totalSupply");

        cap = amount;
    }


    /**
     * @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");
        require(ERC20.totalSupply() + amount <= capAmt(), "Error: Max mint 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()), "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);

        // checking if the token got transferred from 0/dead address
        // as the mint is always done through that address
        if(from == address(0)) {
            require(ERC20.totalSupply() + amount < capAmt(), "Error: Cap amount exceeded!");
        }
    }
}

1 Like

Here is my solution :slight_smile: :

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

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 TijanToken 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 private _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the
     * account that deploys the contract.
     *
     * See {ERC20-constructor}.
     */
    constructor() ERC20("TijanToken", "TNT") {
        //Sets all roles to msg sender (contract deployer)!
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(CAPER_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());

        //sets market cap
        _cap = 10000;
            //uncoment for dynamic
            /*require(cap_ > 0, "ERC20Capped: cap is 0");
            _cap = cap_;*/        
    }

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

    /**
     * @dev Changes cap and returns new one. Only available for addresses with CAPER_ROLE!
     */
    function changeCap(uint256 _newCap) public returns (uint256) {
        uint _prevCap = totalSupply();
        require(_newCap > _prevCap, "New cap can not be smaller than current cap!");
        require(hasRole(CAPER_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have caper role to change cap!");
        _cap = _newCap;
        return _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");
        require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: 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()), "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)) { // When minting tokens
            require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        }
    }
}
1 Like

Before I try this exercies I still have to figure out a problem I have from the video “Extending ERC 20”
I followed along and encountered an error. Maybe some of you can help.

First of all, here is my token contract, the same as in the lecture.

pragma solidity 0.8.0;

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

contract MyToken is ERC20Capped, Ownable {
    constructor() ERC20("MyToken", "MTK") ERC20Capped(100000){
       _mint(msg.sender, 1000); 
    }
}

The problem is the following error when I want to compile the contract.

TypeError: Immutable variables cannot be read during contract creation time, which means they cannot be read in the constructor or any function or modifier called from it.     
  --> project:/node_modules/@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol:27:16:
   |
27 |         return _cap;
   |                ^^^^

Compilation failed. See above.

I don´t see what I could have done wrong. From my understanding there shouldn´t be an error because I have set the required cap with the constructor. I also did one thing different from the lecture I added a different path for importing ERC20Capped.sol because otherwise the terminal would mark it as an error.
Thanks in advance for the help!

1 Like

Hey @PaulS96, hope you are well.

Which is your node js version? if its 12 or below? cuz there is a bug with higher versions than 12 with truffle.

Take a look into this guide to downgrade nodejs: FAQ - How to downgrade Node.Js

I have able to copy/paste your contract using my truffle and openzeppeling like yours and it compiles properly for me.

Carlos Z

@thecil I have version 12 of node.js

This is my solution for the exercise. I still seem to have several technical problems as I mentioned in a previous post. As mentioned I am using Node.js version 12.
Edit: I also tried with the solution provided in the course and I have the exact same problems.

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 TokenExercise 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() ERC20("Test Token", "TTE") {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _setupRole(CAP_ROLE, _msgSender());

        _cap = 10000;
    }

    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _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");
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have cap change role to mint");
        require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: cap exceeded");
        _mint(to, amount);
    }

    function capChange(uint256 newCap) public virtual {
        require(hasRole(CAP_ROLE, _msgSender()), "ERC20PresetMinterPauser: must have cap change role to change token cap");
        require(newCap > _cap);
        _cap = newCap;        
    }

    /**
     * @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

My solution:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

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 ERC20PresetMinterPauserCapped is Context, AccessControlEnumerable, ERC20Burnable, ERC20Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant CAPCHANGE_ROLE = keccak256("CAPCHANGE_ROLE");

    uint256 _cap;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, `PAUSER_ROLE`, and `CAPCHANGE_ROLE` to the
     * account that deploys the contract.
     *
     * Sets the value of the `cap`. This value is immutable, it can only be
     * set once during construction.     
     *
     * See {ERC20-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(CAPCHANGE_ROLE, _msgSender());
    }

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

    function changeCap(uint256 newCap) public {
        require(hasRole(CAPCHANGE_ROLE, _msgSender()), "ERC20PresetMinterPauserCap: must have cap change role to change cap");
        require(newCap > 0, "ERC20PresetMinterPauserCap: cap is 0");
        _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 <= cap(), "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);
    }
}

Hey @PaulS96, hope you are well.

Sorry for the late reply, I copy/paste your contract and just download a fresh truffle and openzeppelin contracts, the only error i have is from not using the SPDX identifier.

Could you please share an screenshot of the error that is being show to you in the console?

Carlos Z

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

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_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_) public ERC20(name, symbol) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

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

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

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

    function setCap(uint256 cap_) public virtual {
        require(hasRole(CAP_ROLE, _msgSender()),"MyToken: must have cap role");
        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");
        require(ERC20.totalSupply() + amount <= cap(), "ERC20Capped: 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()), "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);
    }
}

Hi All,

Below you can find my solution.
At first I found it difficult to code the solution, but after some time, it almost looked like I coded this solution several times…
I think I’m understanding the whole concept behind it.

This is my token contract

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

pragma solidity ^0.8.10;

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.
 *
 * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._
 */
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;

    /**
     * @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, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _cap;
    }
    function newCap(uint256 _newCap) public {
        require(hasRole(CAPPER_ROLE, _msgSender()), "ERC20Capper: must have capper role to edit the cap");
        _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);
    }


    /**
     * @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);
    }
}

And this is my migrations file

const Migrations = artifacts.require("MyToken");

module.exports = function (deployer) {
  var name = 'MyToken';
  var symbol = 'MTKN';
  var cap_ = '500';
  deployer.deploy(Migrations, name, symbol, cap_);
};

I hope someone will check it to give me some tips & tricks to make the contract even better and saver.

1 Like

Hello there,
this is my solution:

./contracts/AssignmentToken.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

/*
  https://academy.moralis.io/lessons/custom-extensions-assignment

  Your assignment is to create a new ERC20 token contract 
  from the Openzeppelin template ERC20PresetMinterPauser and 
  add cap functionality. Meaning that there is a maximum cap of token issuance. 

  You should also build a new function that allows you to modify the cap.

  You should also study the AccessControl contract from Openzeppelin and
  implement a new role in the same ERC20 contract. This role should be the
  only one to be able to change the contract’s cap.
*/

import '../node_modules/@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol';


contract AssignmentToken is ERC20PresetMinterPauser {
  
  uint256 private _cap;
  bytes32 public constant CAPPER_ROLE = keccak256("CAPPER_ROLE");

  event CapChanged(uint256 cap);

  constructor(string memory name, string memory symbol, uint256 cap_) ERC20PresetMinterPauser(name, symbol) {
    _grantRole(CAPPER_ROLE, _msgSender());
    _setCap(cap_);
  }

  function setCap(uint256 cap_) public onlyRole(CAPPER_ROLE){
    _setCap(cap_);
  }

  function _setCap(uint256 cap_) internal {
    require(ERC20.totalSupply() <= cap_, "AssignmentToken: Total Supply would exceed cap");
    _cap = cap_;
    emit CapChanged(_cap);
  }

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

  /**
    * @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);
  }
}

and to dig more and start trying to do things professionally and get use with the test suite I’ve written some tests to check the contract

./test/AssignmentToken.js

// assertion api https://www.chaijs.com/api/assert/
// more docs at https://trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript


const AssignmentToken = artifacts.require("./AssignmentToken.sol");

const DeployNewAssignmentToken = async (name, symbol, initialCap) => {
  return AssignmentToken.new(name, symbol, initialCap);
}

contract("AssignmentToken", (accounts) => {
  it("Should init setting constructor arguments correctly", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    assert.equal(await token.name(), "Assignment Token");
    assert.equal(await token.symbol(), "ATKN");
    assert.equal(await token.cap(), 1000);
  });
  it("Should init all roles to the deployer", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const capperRole = await token.CAPPER_ROLE();
    const adminRole = await token.DEFAULT_ADMIN_ROLE();
    const minterRole = await token.MINTER_ROLE();
    const pauserRole = await token.PAUSER_ROLE();
    assert.isTrue(await token.hasRole(capperRole, accounts[0]));
    assert.isTrue(await token.hasRole(adminRole, accounts[0]));
    assert.isTrue(await token.hasRole(minterRole, accounts[0]));
    assert.isTrue(await token.hasRole(pauserRole, accounts[0]));
  });
  it("Cappers should be able to set cap", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const capperRole = await token.CAPPER_ROLE();
    await token.grantRole(capperRole, accounts[1]);
    assert.isTrue(await token.hasRole(capperRole, accounts[1]));
    await token.setCap(2000, {from: accounts[1]});
    assert.equal(await token.cap(), 2000);
  });
  it("Non cappers should not be allowed to change cap", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const capperRole = await token.CAPPER_ROLE();
    assert.isNotTrue(await token.hasRole(capperRole, accounts[1]));
    try {
      await token.setCap(2000, {from: accounts[1]});
      assert.fail("It's allowing non-cappers to change cap");
    } catch(error) {
      assert.isTrue(
        /^Returned error: VM Exception while processing transaction: revert AccessControl/.test(error.message)
      );
    }
  });
  it("Cap should not be allowed to be lower than the totalSupply", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const capperRole = await token.CAPPER_ROLE();
    await token.grantRole(capperRole, accounts[1]);
    assert.isTrue(await token.hasRole(capperRole, accounts[1]));
    await token.setCap(2000, {from: accounts[1]});
    assert.equal(await token.cap(), 2000);
    // now cap is 2000

    // minting tokens
    await token.mint(accounts[0], 1980);
    await token.mint(accounts[1], 10);
    assert.equal(await token.totalSupply(), 1990);
    await token.setCap(1990, {from: accounts[1]});
    assert.equal(await token.cap(), 1990);
    
    // now cap is 1990 and == totalSupply
    try {
      await token.setCap(1989, {from: accounts[1]});
      assert.fail("It's allowing cap to be < totalSupply");
    } catch(error) {
      assert.isTrue(
        /^Returned error: VM Exception while processing transaction: revert AssignmentToken: Total Supply would exceed cap/.test(error.message)
      );
    }
  });
  it("Pausers should be able to pause the contract", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const pauserRole = await token.PAUSER_ROLE();
    await token.grantRole(pauserRole, accounts[1]);
    assert.isTrue(await token.hasRole(pauserRole, accounts[1]));
    await token.pause({from: accounts[1]});
    assert.isTrue(await token.paused());
  });
  it("Non pausers should not be allowed to pause the contract", async function() {
    const token = await DeployNewAssignmentToken("Assignment Token", "ATKN", 1000);
    const pauserRole = await token.PAUSER_ROLE();
    assert.isNotTrue(await token.hasRole(pauserRole, accounts[1]));
    try {
      await token.pause({from: accounts[1]});
      assert.fail("It's allowing non-pausers to pause");
    } catch(error) {
      assert.isTrue(
        /^Returned error: VM Exception while processing transaction: revert ERC20PresetMinterPauser: must have pauser role to pause/
          .test(error.message)
      );
    }
    assert.isFalse(await token.paused());
  });
});

All tests passed using truffle test

1 Like

@thecil Now, after reinstalling Node, truffle and Openzeppelin I so far did´t have any problems. I just took my solution I provided and ran truffle develop and migrate without any issues.

1 Like

My Solution:

pragma solidity ^0.8.0;

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

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 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, "ERC20Capped: cap is 0");
        _cap = cap_;
    }

    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _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"
        );
        require(
            ERC20.totalSupply() + amount <= cap(),
            "ERC20Capped: cap exceeded"
        );

        _mint(to, amount);
    }

      /**
     * @dev Updates `cap` for mintable tokens
     *
     * See {ERC20-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `CAPPER_ROLE`.
     */

 
    function changeCap(uint256 capSize) public virtual {
        require(
            hasRole(CAPPER_ROLE, _msgSender()),
            "ERC20Capper: must have Capper role to set cap"
        );

        _cap = capSize;
    }

    /**
     * @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

Hi, this is my solution.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";

contract NewToken is ERC20PresetMinterPauser {
    bytes32 public constant CAP_MANAGER_ROLE = keccak256("CAP_MANAGER_ROLE");
    uint256 cap;

    event CapIncreased(uint256 newCap);

    constructor(uint256 cap_) ERC20PresetMinterPauser("NewToken", "NTK") {
        require(cap_ > 0, "NewToken: cap must be greater than 0");
        cap = cap_;
        _setupRole(CAP_MANAGER_ROLE, Context._msgSender());
    }

    function getCap() internal view virtual returns (uint256) {
        return cap;
    }

    function mint(address to, uint256 amount) public virtual override {
        require(
            ERC20.totalSupply() + amount <= getCap(),
            "NewToken: cap exceeded"
        );

        super.mint(to, amount);
    }

    function increaseCap(uint256 amount) public {
        require(
            hasRole(CAP_MANAGER_ROLE, Context._msgSender()),
            "NewToken: must have the cap manager role to modify cap"
        );

        cap = cap + amount;

        emit CapIncreased(cap);
    }
}
1 Like

Here is my solution.

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/presets/ERC20PresetMinterPauser.sol)

pragma solidity ^0.8.0;

import "../ERC20.sol";
import "../extensions/ERC20Burnable.sol";
import "../extensions/ERC20Pausable.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 UPDATECAP_ROLE = keccak256("UPDATECAP_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    uint private immutable _cap;
    uint public currentCap;
    /**
     * @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, uint cap_) ERC20(name, symbol) {
        require(cap_ > 0, "ERC20Capped: cap is 0");
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
        _cap = cap_;
    }

     function cap() public view virtual returns (uint256) {
        return _cap;
    }
    function updateCap(uint newCap) public virtual returns (uint) {
        require(hasRole(UPDATECAP_ROLE, _msgSender()), "Not Allowed To Update Cap Size!");
        currentCap = cap();
        currentCap = newCap;
        return currentCap;
    }

    /**
     * @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