// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

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";
// Ensure TimelockController is compiled so it can be deployed by name.
import "@openzeppelin/contracts/governance/TimelockController.sol";

/**
 * @title AereGovernorV2
 * @notice A corrected OpenZeppelin Governor for AERE Network that is robust to
 *         transient governance-supply inflation.
 *
 *         WHY THIS EXISTS. The prior AereGovernor
 *         (0xd16C2551Bf4f3d0DDF961282078917b2e8679999) used a plain
 *         GovernorVotesQuorumFraction, so a proposal's quorum was simply
 *         token.getPastTotalSupply(snapshot) * pct / 100, with no floor, no cap
 *         and no time-averaging. Because AereGovLock.lock() permissionlessly
 *         mints govAERE 1:1 from the abundant native token (2.8B supply), an
 *         attacker could lock a large amount right before a proposal snapshot,
 *         stay completely silent (never cast a vote), and thereby fix an
 *         unreachable quorum. Honest holders voting unanimously For could then
 *         never reach quorum, and the proposal would be Defeated. The attacker
 *         could not force-pass anything (the principal is returned after the min
 *         lock, and votes never favored a side), but could VETO every proposal.
 *
 *         THE FIX. The quorum DENOMINATOR is read at a timepoint that a lock
 *         placed after a proposal is created cannot inflate, and the result is
 *         then clamped by an absolute floor (with an optional ceiling):
 *
 *             lookbackPoint = timepoint - max(quorumLookback, votingDelay())
 *             supply        = token.getPastTotalSupply(lookbackPoint)   // clamped to 0 for early proposals
 *             fractional    = supply * quorumNumerator / quorumDenominator
 *             quorum        = max(quorumFloor, min(fractional, quorumCap))
 *
 *         WHY THE EARLIER TIMEPOINT IS THE ROBUST PART. A proposal's snapshot is
 *         proposalCreationBlock + votingDelay. A silent whale can only react to a
 *         proposal AFTER it exists, so its lock lands at or after the creation
 *         block, i.e. strictly after (snapshot - votingDelay). By reading the
 *         denominator at snapshot - max(quorumLookback, votingDelay()), which is
 *         at or before the creation block (minus a safety margin), that reactive
 *         lock is simply not in the measured supply. The bar therefore reflects
 *         the supply that already existed BEFORE the proposal, so the attacker
 *         cannot move it at all. This does not depend on choosing quorumCap
 *         perfectly: even a very loose cap cannot be reached, because fractional
 *         is now computed from a supply the attacker cannot inflate.
 *
 *         WHY votingDelay() IS FOLDED IN. quorumLookback is a fixed deployment
 *         constant, but votingDelay is a governable setting. Taking the max of the
 *         two guarantees the measured point stays at or before the creation block
 *         even if governance later raises votingDelay, so the window is always
 *         fully covered. quorumLookback should be set to at least votingDelay plus
 *         a small margin so that a whale front-running the propose transaction in
 *         the same block region is also excluded.
 *
 *         WHY THE FLOOR STAYS. quorumFloor bounds the requirement from BELOW so a
 *         genuinely low-turnout proposal still fails: a small or shrunken supply
 *         can never let a trivial amount of votes pass. quorumCap is retained as a
 *         secondary safety ceiling (defence in depth); it is no longer the primary
 *         defence, since the earlier-timepoint denominator already neutralizes the
 *         veto on its own. For very early proposals, where the lookback would reach
 *         before genesis, the measured point is clamped to 0 (supply reads 0) and
 *         the floor governs, so quorum can never be silently zero.
 *
 *         Everything else (counting, timelock execution, checkpointed vote source)
 *         is the audited OZ reference behaviour, unchanged. The only override is
 *         quorum(). The TimelockController is untouched and has no backdoor. All
 *         three knobs are fixed at deployment in govAERE base units / blocks and
 *         are publicly readable.
 */
contract AereGovernorV2 is
    Governor,
    GovernorSettings,
    GovernorCountingSimple,
    GovernorVotes,
    GovernorVotesQuorumFraction,
    GovernorTimelockControl
{
    /// @notice Absolute minimum quorum, in govAERE base units. quorum() never
    ///         returns less than this, so a low-turnout proposal cannot pass on a
    ///         shrunken supply.
    uint256 public immutable quorumFloor;

    /// @notice Secondary safety ceiling for quorum, in govAERE base units. quorum()
    ///         never returns more than this. With the earlier-timepoint denominator
    ///         this is defence in depth, not the primary anti-veto defence.
    uint256 public immutable quorumCap;

    /// @notice Blocks subtracted from a proposal's snapshot to pick the timepoint
    ///         at which the quorum denominator (total supply) is read. It must
    ///         cover the votingDelay window plus a small margin so that a lock
    ///         placed after (or front-running) the proposal cannot inflate the
    ///         denominator. At query time the effective lookback is
    ///         max(quorumLookback, votingDelay()), so raising votingDelay later
    ///         cannot shrink the protected window.
    uint256 public immutable quorumLookback;

    /**
     * @param votesToken              Checkpointed IVotes source (AereGovLock).
     * @param timelock                TimelockController that executes proposals.
     * @param initialVotingDelay      Voting delay, in blocks.
     * @param initialVotingPeriod     Voting period, in blocks.
     * @param initialProposalThreshold Proposal threshold, in vote units.
     * @param quorumPercent           Fractional quorum, e.g. 10 = 10% of supply.
     * @param _quorumFloor            Absolute minimum quorum (govAERE base units).
     * @param _quorumCap              Secondary quorum ceiling (govAERE base units).
     *                                Must be >= _quorumFloor.
     * @param _quorumLookback         Blocks before snapshot at which the quorum
     *                                denominator is read. Set to at least
     *                                initialVotingDelay plus a small margin.
     */
    constructor(
        IVotes votesToken,
        TimelockController timelock,
        uint256 initialVotingDelay,
        uint256 initialVotingPeriod,
        uint256 initialProposalThreshold,
        uint256 quorumPercent,
        uint256 _quorumFloor,
        uint256 _quorumCap,
        uint256 _quorumLookback
    )
        Governor("AereGovernorV2")
        GovernorSettings(initialVotingDelay, initialVotingPeriod, initialProposalThreshold)
        GovernorVotes(votesToken)
        GovernorVotesQuorumFraction(quorumPercent)
        GovernorTimelockControl(timelock)
    {
        require(_quorumCap >= _quorumFloor, "AereGovernorV2: cap < floor");
        quorumFloor = _quorumFloor;
        quorumCap = _quorumCap;
        quorumLookback = _quorumLookback;
    }

    /**
     * @notice Quorum required for a proposal whose snapshot is `timepoint`.
     *
     *         The denominator (total govAERE supply) is read at an EARLIER block,
     *         timepoint - max(quorumLookback, votingDelay()), which is at or before
     *         the proposal's creation block. A whale that locks govAERE after the
     *         proposal exists cannot inflate that historical supply, so it cannot
     *         raise the bar. The fractional quorum is then clamped into
     *         [quorumFloor, quorumCap]. For an early proposal whose lookback would
     *         reach before genesis, the point is clamped to 0 (supply reads 0) and
     *         the floor governs.
     */
    function quorum(uint256 timepoint)
        public
        view
        override(IGovernor, GovernorVotesQuorumFraction)
        returns (uint256)
    {
        // Cover at least the votingDelay window; a governable votingDelay increase
        // can never shrink the protected lookback below its deployed value.
        uint256 lookback = quorumLookback;
        uint256 vd = votingDelay();
        if (vd > lookback) {
            lookback = vd;
        }

        // Read the denominator at or before the proposal's creation block, clamped
        // to 0 for early proposals so the lookup never underflows or reverts.
        uint256 lookbackPoint = timepoint > lookback ? timepoint - lookback : 0;
        uint256 supply = token.getPastTotalSupply(lookbackPoint);

        // Same fraction as GovernorVotesQuorumFraction, but over a supply a late
        // lock cannot inflate.
        uint256 fractional = (supply * quorumNumerator(timepoint)) / quorumDenominator();

        // Secondary ceiling, then the absolute floor from below.
        uint256 capped = fractional < quorumCap ? fractional : quorumCap;
        return capped > quorumFloor ? capped : quorumFloor;
    }

    // ---------------------------------------------------------------------
    // The functions below are pure Solidity multiple-inheritance overrides,
    // identical to AereGovernor.
    // ---------------------------------------------------------------------

    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 proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
        return super.proposalThreshold();
    }

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

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