How to work around private state variable when adding custom functionality to inherited contract code

Hi, I am working with OZ's Governance contracts and has started from the template governance contract the OZ's Wizard generated (non-compound). I am trying to add some custom functions on top on the standard that inheriting the OZ governance contracts provides. In particular, I am trying to add a custom execute function. It has mostly the same components as the default execute function, except with different ProposalState conditions.

My issue comes from trying to implement this line
_proposals[proposalId].executed = true;
which is the same as the default execute function. The problem is that _proposals is a private mapping and so not visible on my inherited level of code. I am trying my best to stick to the intended way of using OZ's contracts, which is to import, add functionality (if needed) and use. Which is to say to not alter OZ's contract code. But the private mapping _proposals is making this hard.

What are your recommendations such that I can implement my custom execute function? Something like a getter for _proposals (I cant find one in OZ contracts)? Here is that custom function:

function customExecute(
      address[] memory targets,
      uint256[] memory values,
      bytes[] memory calldatas,
      bytes32 descriptionHash
  ) external payable returns (uint256) {
      uint256 proposalId = hashProposal(
          targets,
          values,
          calldatas,
          descriptionHash
      );

      ProposalState status = state(proposalId);
      require(
          status == ProposalState.Expired,
          "Governor: proposal not expired"
      );
      _proposals[proposalId].executed = true; // <----- not visible

      emit ProposalExecuted(proposalId);

      _execute(proposalId, targets, values, calldatas, descriptionHash);

      return proposalId;
  }

Hello @junhuang-ho

If you want to customize the execute mechanism, the go-to-solution is to override the _execute() function. This is what we do int GovernorTimelockControl and GovernorTimelockCompound.

Hi @Amxx, I do not want to override any functionality, I like to keep all existing functionality, and add a "new" functionality that is very similar to Governor.execute:

function execute(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) public payable virtual override returns (uint256) {
        uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);

        ProposalState status = state(proposalId);
        require(
            status == ProposalState.Succeeded || status == ProposalState.Queued,
            "Governor: proposal not successful"
        );
        _proposals[proposalId].executed = true;

        emit ProposalExecuted(proposalId);

        _beforeExecute(proposalId, targets, values, calldatas, descriptionHash);
        _execute(proposalId, targets, values, calldatas, descriptionHash);
        _afterExecute(proposalId, targets, values, calldatas, descriptionHash);

        return proposalId;
    }

The generated code OZ wizard gave me has this overriden _execute:

function _execute(
        uint256 proposalId,
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal override(Governor, GovernorTimelockControl) {
        super._execute(proposalId, targets, values, calldatas, descriptionHash);
    }

So I was thinking I could just make my additional function like this (using the exposed _execute):

function customExecute(
      address[] memory targets,
      uint256[] memory values,
      bytes[] memory calldatas,
      bytes32 descriptionHash
  ) external payable returns (uint256) {
      uint256 proposalId = hashProposal(
          targets,
          values,
          calldatas,
          descriptionHash
      );

      ProposalState status = state(proposalId);
      require(
          status == ProposalState.Expired,
          "Governor: proposal not expired"
      );
      _proposals[proposalId].executed = true; // <----- not visible

      emit ProposalExecuted(proposalId);

      _execute(proposalId, targets, values, calldatas, descriptionHash);

      return proposalId;
  }

At the end, I want to have 2 version of execute, Governor.execute and customExecute. Of course, for customExecute, _proposals is not visible as it is private.

My current solution is to completely ignore _proposals and add my own state variable like so:

mapping(uint256 => bool) expiredProposalsExecuted; // <---- new state variable
...
...
...code
...

function customExecute(
      address[] memory targets,
      uint256[] memory values,
      bytes[] memory calldatas,
      bytes32 descriptionHash
  ) external payable returns (uint256) {
      uint256 proposalId = hashProposal(
          targets,
          values,
          calldatas,
          descriptionHash
      );

      ProposalState status = state(proposalId);
      require(
          status == ProposalState.Expired,
          "Governor: proposal not expired"
      );
      // _proposals[proposalId].executed = true; // <----- not visible, comment out
     expiredProposalsExecuted[proposalId] = true;

      emit ProposalExecuted(proposalId);

      _execute(proposalId, targets, values, calldatas, descriptionHash);

      return proposalId;
  }

Not ideal, but I am open to any suggestions if there are any.