contract MinGov is Ownable {

  uint256 public proposalTime;
  uint256 public first;
  uint256 public size;

  struct Proposal {
    address subject;
    uint32 created;
    bool canceled;
    bytes msgData;
  }

  mapping(uint256 => Proposal) public proposals;

  event NewProposal(uint256 indexed proposalId, address indexed subject, bytes msgData);
  event Execution(uint256 indexed proposalId, address indexed subject, bytes msgData);

  constructor(uint256 _proposalTime) public {
    proposalTime = _proposalTime;
    first = 1;
    size = 0;
  }

  function propose(address _subject, bytes memory _msgData) public onlyOwner {
    require(size < 5);
    proposals[first + size] = Proposal(
      _subject,
      uint32(now),
      false,
      _msgData
    );
    emit NewProposal(first + size, _subject, _msgData);
    size++;
  }

  function cancel(uint256 _proposalId) public onlyOwner() {
    Proposal storage prop = proposals[_proposalId];
    require(prop.created > 0);
    require(prop.canceled == false);
    prop.canceled = true;
  }

  function withdrawTax(address _token) public onlyOwner {
    IERC20 token = IERC20(_token);
    token.transfer(owner(), token.balanceOf(address(this)));
  }

  function finalize() public {
    for (uint256 i = first; i < first + size; i++) {
      Proposal memory prop = proposals[i];
      if (prop.created + proposalTime <= now) {
        if (!prop.canceled) {
          bool rv;
          bytes4 sig = getSig(prop.msgData);
          // 0x8f283970 = changeAdmin(address)
          // 0x3659cfe6 = upgradeTo(address)
           // 0x983b2d56 = addMinter(address)
          if (sig == 0x8f283970||sig == 0x3659cfe6||sig == 0x983b2d56) {
            // this changes proxy parameters
            (rv, ) = prop.subject.call(prop.msgData);
          } else {
            // this changes governance parameters to the implementation
            rv = AdminableProxy(address(uint160(prop.subject))).applyProposal(prop.msgData);
          }
          if (rv) {
            emit Execution(i, prop.subject, prop.msgData);
          }
        }
        delete proposals[i];
        first++;
        size--;
      }
    }
  }

  // proxy function to manage validator slots without governance delay
  function setSlot(uint256 _slotId, address, bytes32) public onlyOwner {
    // extract subject
    address payable subject = address(uint160(_slotId >> 96));
    // strip out subject from data
    bytes memory msgData = new bytes(100);
    assembly {
      calldatacopy(add(msgData, 32), 0, 4)
      calldatacopy(add(msgData, 56), 24, 76)
    }
    // call subject
    require(AdminableProxy(subject).applyProposal(msgData), "setSlot call failed");
  }

  function getSig(bytes memory _msgData) internal pure returns (bytes4) {
    return bytes4(_msgData[3]) >> 24 | bytes4(_msgData[2]) >> 16 | bytes4(_msgData[1]) >> 8 | bytes4(_msgData[0]);
  }

  // proxy function to manage tokens without governance delay
  // token types: 0 = ERC20, 1 = ERC721, 2 = ERC1948
  function registerToken(address payable _subject, address _token, uint256 _type) public onlyOwner {
    require(
      AdminableProxy(_subject).applyProposal(
        abi.encodeWithSignature("registerToken(address,uint256)", _token, _type)
      ),
      "registerToken call failed"
    );
  }

}
function setSlot(uint256 _slotId, address, bytes32) public onlyOwner {
  // extract subject
  address payable subject = address(uint160(_slotId >> 96));
  // strip out subject from data
  bytes memory msgData = new bytes(100);
  assembly {
    calldatacopy(add(msgData, 32), 0, 4)
    calldatacopy(add(msgData, 56), 24, 76)
  }
  // call subject
  require(AdminableProxy(subject).applyProposal(msgData), "setSlot call failed");
}

// proxy function to manage validator slots without governance delay