After the proposal ends, an error is reported by calling the queue() method

This is my proposal address
https://www.tally.xyz/gov/cr13

When I clicked on queue(), the page reported an error displaying RPC Error:

Internal JSON-RPC error {code: -32603, message: 'Internal JSON-RPC error.', data: {...}}

This is my timeline contract code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

    error NotOwner();
    error AlreadyQueued(bytes32);
    error TimestampNotInRange(uint256, uint256);
    error NotQueued(bytes32);

contract RayTimelock {

    event Queue(address indexed target, uint256 indexed amount, string func, bytes data, uint256 timestamp, bytes32 transactionId);
    event Execute(address indexed target, uint256 indexed amount, string func, bytes data, uint256 timestamp, bytes32 transactionId);
    event Cancel(bytes32 indexed transactionId);

    address public immutable owner;

    uint256 public constant MIN_DELAY = 10;
    uint256 public constant MAX_DELAY = 1000;

    mapping(bytes32 => bool) public queued;

    modifier onlyOwner {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    receive() external payable {}

    function queue(address target, uint256 amount, string calldata func, bytes calldata data, uint256 timestamp) external onlyOwner {
        bytes32 transactionId = getTransactionId(target, amount, func, data, timestamp);
        if (queued[transactionId]) revert AlreadyQueued(transactionId);
        if (timestamp < block.timestamp + MIN_DELAY || timestamp > block.timestamp + MAX_DELAY) revert TimestampNotInRange(block.timestamp, timestamp);
        queued[transactionId] = true;
        emit Queue(target, amount, func, data, timestamp, transactionId);
    }

    function getTransactionId(address target, uint256 amount, string calldata func, bytes calldata data, uint256 timestamp) public pure returns (bytes32 transactionid) {
        return keccak256(abi.encode(target, amount, func, data, timestamp));
    }

    function cancel(bytes32 transactionId) external onlyOwner {
        if (!queued[transactionId]) revert NotQueued(transactionId);
        queued[transactionId] = false;
        emit Cancel(transactionId);
    }

    function getTimestamp() external view returns (uint256) {
        return block.timestamp + 15;
    }
}

This is my Governor contract code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract CrGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
    Governor("CrGovernor")
    GovernorSettings(1 /* 12 seconds */, 300 /* 1 hours */, 0)
    GovernorVotes(_token)
    GovernorVotesQuorumFraction(4)
    GovernorTimelockControl(_timelock)
    {}

    // The following functions are overrides required by Solidity.

    function votingDelay()
    public
    view
    override(IGovernor, GovernorSettings)
    returns (uint256)
    {
        return super.votingDelay();
    }

    function votingPeriod()
    public
    view
    override(IGovernor, GovernorSettings)
    returns (uint256)
    {
        return super.votingPeriod();
    }

    function quorum(uint256 blockNumber)
    public
    view
    override(IGovernor, GovernorVotesQuorumFraction)
    returns (uint256)
    {
        return super.quorum(blockNumber);
    }

    function state(uint256 proposalId)
    public
    view
    override(Governor, GovernorTimelockControl)
    returns (ProposalState)
    {
        return super.state(proposalId);
    }

    function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description)
    public
    override(Governor, IGovernor)
    returns (uint256)
    {
        return super.propose(targets, values, calldatas, description);
    }

    function proposalThreshold()
    public
    view
    override(Governor, GovernorSettings)
    returns (uint256)
    {
        return super.proposalThreshold();
    }

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

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

    function _executor()
    internal
    view
    override(Governor, GovernorTimelockControl)
    returns (address)
    {
        return super._executor();
    }

    function supportsInterface(bytes4 interfaceId)
    public
    view
    override(Governor, GovernorTimelockControl)
    returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

Hello @Chen_Ray

I had a look at your deployment. According to what Tally tells me:

  • your governor is at address 0xbAb1C7507611C5B6E2EC690EBb9Da6518f22fB98 on mumbai
  • your timelock is at address 0x54dE9c7Efe55A2a5646fA578FEa3e337a93294D6 on mumbai

Since queue is the first operation that interracts with the timelock, and this is the first proposal on this governor, the timelock is a good candidate.

It turns out that:

  • the code of the timelock is not verified
  • the timelock address doesn't have any events recorded, which is strange since our timelock does emit events during construction
  • when decompiling the bytecode deployed at the timelock's address, we can see that is includes the following public functions
0x188ec356 getTimestamp()
0x228250c9 queued(bytes32)
0x4125ff90 MAX_DELAY()
0x4b9915a5 Unknown
0x8da5cb5b owner()
0x9f81aed7 MIN_DELAY()
0xc4d252f5 cancel(bytes32)
0xd3330d87 Unknown

This is clearly not an OpenZeppelin's TimelockController. The GovernorTimelockControl module that you use is designed to work with our TimelockController. I'll have to either deploy a compatible timelock, or write a module that is compatible with the timelock you intend to use. Not having a timelock at all is also an option.