ERC2771Context: TypeError: Immutable variables cannot be read during contract creation time


I’m trying to integrate Meta Transactions and when I inherit a contract with ERC2771Context I get the following error during compile time:

Compiling 24 files with 0.8.0
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.
  --> @openzeppelin/contracts/metatx/ERC2771Context.sol:18:29:
18 |         return forwarder == _trustedForwarder;
   |                             ^^^^^^^^^^^^^^^^^

:computer: Environment

HardHat: v2.3.0
@openzeppelin/contracts: v4.1.0


:1234: Code to reproduce


import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import "@openzeppelin/contracts/metatx/MinimalForwarder.sol";

contract BadgeRoles is AccessControlEnumerable, Pausable, ERC2771Context {
    /// @dev Roles
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant TEMPLATER_ROLE = keccak256("TEMPLATER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    constructor(MinimalForwarder forwarder) ERC2771Context(address(forwarder)) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(ADMIN_ROLE, _msgSender());
        _setupRole(TEMPLATER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());


import "./BadgeRoles.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract MakerBadges is BadgeRoles, ERC721Enumerable {
    /// @dev Libraries
    using Counters for Counters.Counter;
    using MerkleProof for bytes32[];

    Counters.Counter private _templateIdTracker;

    string public baseTokenURI;

    bytes32[] private roots;

    struct BadgeTemplate {
        string name;
        string description;
        string image;

    mapping(uint256 => BadgeTemplate) public templates;
    mapping(uint256 => uint256) public templateQuantities;

    /// @dev Events
    event NewTemplate(uint256 indexed templateId, string name, string description, string image);
    event TemplateUpdated(uint256 indexed templateId, string name, string description, string image);
    event BadgeActivated(uint256 indexed tokenId, uint256 indexed templateId);

    constructor(MinimalForwarder forwarder) ERC721("MakerBadges", "MAKER") BadgeRoles(forwarder) {
        baseTokenURI = "";

Tried different approaches but I get the same error.

Source Code:

Unfortunately immutable state variables cannot be read at construction time. This is a limitation of the compiler (though there are plans to make it possible in the future). See if you’re interested in the details.


Hi @cameel,

Thanks for your response!

I’m wondering @Amxx if OZ plans to remove the immutable keyword in the meantime to allow the community to experiment with meta txs and build on top of it or if you’d recommend any workaround to use it in the meantime.


@naszam This error only happens if you try to read the forwarder, from its “immutable slot”, during construction. The error comes from the fact that you are using _msgSender() in your constructor. You should either keep using msg.sender in the constructor (I don’t think meta-tx construction is a thing) … or pass the admin as a constructor argument and use that to setup your roles.

Immutable variable offer significant gas saving (particularly since the Berlin upgrade). So far the drawback as always been minimal and avoidable compared to the benefits.

1 Like

Hi @Amxx,

Thanks for your response!

I’ve tried using the msg.sender in the constructor but I still have the forwarder issue.
Do you have any plans to release a version without immutable keyword?
Also I’ve noticed that is not specified any visibility for the _trustedForwarder.

I’ve run into the same problem.

I’m using AccessControl in the constructor, which in turn calls _msgSender. The constructor is the correct place to do this access control, as the contract shouldnt exist without the correct access control. Doing this with an initialise function should also be avoided if possible.

It basically means that constructor use of AccessControl is not compatible with inheriting from ERC2771. Can the library be improved to allow for this compatiblity? I expect many people will be using AccessControl in the constructor.