Using AccessControl with ERC20 token (UpgradeSafe) throwing "Defining structs like `RoleData` is not yet supported"

As per @abcoathup’s suggestion to use OpenZeppelin ERC20 implementation, am creating a very simple upgradable ERC20 token as below:

// contracts/MyToken.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";

contract MyToken is Initializable, ERC20UpgradeSafe, AccessControlUpgradeSafe {
    
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

    function initialize(address admin, uint256 initialSupply) public initializer {
        __ERC20_init("MyToken", "MYT");
        __AccessControl_init_unchained();
        _mint(admin, initialSupply * (10 ** uint256(decimals())));
        _setupRole(BURNER_ROLE, admin);
        _setupRole(DEFAULT_ADMIN_ROLE, admin);
    }

    function burn(address from, uint256 amount) public {
       require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner");
       _burn(from, amount);
   }

   function revokeRole(bytes32 role, address account) public override {
       require(
           role != DEFAULT_ADMIN_ROLE,
           "ModifiedAccessControl: cannot revoke default admin role"
       );
 
       super.revokeRole(role, account);
   }

}

and deployed as below:

// migrations/3_deploy_mytoken.js
const MyToken = artifacts.require('MyToken');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer, network, accounts) {
  //await deployProxy(MyToken, ['0x63e4C1725fEB9EDc83d7951cFeD6e0fC3132A26f', 1000], { deployer, unsafeAllowCustomTypes: true, initializer: 'initialize' });
  await deployProxy(MyToken, ['0x63e4C1725fEB9EDc83d7951cFeD6e0fC3132A26f', 1000], { deployer, initializer: 'initialize' });
};

Note that I’ve commented the deployProxy line with unsafeAllowCustomTypes: true. The compile fails:

name@name-MBP:~/projects/folder$ npx truffle test
Using network 'development'.


Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.

Error: Contract `MyToken` is not upgrade safe

../@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol:53: Defining structs like `RoleData` is not yet supported
    If you have manually checked for storage layout compatibility, you can skip this check with the `unsafeAllowCustomTypes` flag
    https://zpl.in/upgrades/error-007

I understand I need to use unsafeAllowCustomTypes: true in migration script as per this https://docs.openzeppelin.com/upgrades-plugins/1.x/api-truffle-upgrades

But all I want is for MyToken’s initialize function to setup DEFAULT_ADMIN_ROLE to be the address passed in the function function initialize(address admin, ...).

How do I set that without using unsafeAllowCustomTypes: true in the migration script?

1 Like

Hi @megatower66,

Sorry for the delay in responding. I am still catching up after having a public holiday.

When using AccessControl which contains a struct we need to use unsafeAllowCustomTypes: true in the migrations script as the Upgrades Plugins doesn’t currently support checking structs.

There is an open issue to support checking for structs: https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/95

So your migration script looks fine to me with unsafeAllowCustomTypes: true.

// migrations/3_deploy_mytoken.js
const MyToken = artifacts.require('MyToken');
 
const { deployProxy } = require('@openzeppelin/truffle-upgrades');
 
module.exports = async function (deployer, network, accounts) {
  await deployProxy(MyToken, ['0x63e4C1725fEB9EDc83d7951cFeD6e0fC3132A26f', 1000], { deployer, unsafeAllowCustomTypes: true, initializer: 'initialize' });
  //await deployProxy(MyToken, ['0x63e4C1725fEB9EDc83d7951cFeD6e0fC3132A26f', 1000], { deployer, initializer: 'initialize' });
};

You would also need to add unsafeAllowCustomTypes: true in your tests.

Thanks @abcoathup. However, by using unsafeAllowCustomTypes: true in this particular context, can we still upgrade the token? By adding say more variables (by not changing the order of previously defined ones), and adding more functions in future? Or that wouldn’t be possible if one is inheriting AccessControl contract?

1 Like

Hi @megatower66,

More functionality can be added in future upgrades. We just need to ensure that we don’t change the storage layout, so as you said, only add state variables and don’t change the order or type of existing ones.

The Upgrade Safe fork of OpenZeppelin Contracts will be upgrade safe, except when stated otherwise. e.g. OpenZeppelin Contracts v2.x and OpenZeppelin Conracts v3.x had different storage layouts, so you couldn’t upgrade from 2.x to 3.x.

1 Like