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

/// @title MultisigGovernanceV3
/// @notice N-of-M governance with a mandatory timelock and a deliberately
///         narrow, pause-only guardian path.
/// @dev Admin-controlled V3 contracts must be owned by this contract. The
///      guardian is never a signer and can only invoke `pause()` on the
///      deployment-supplied or timelock-added allowlist.
contract MultisigGovernanceV3 {
    /// @notice SHA-256 of the canonical JSON content of
    /// `ci/manifests/base-v3-governance-policy.json`.
    /// @dev This stable policy-content digest deliberately excludes source,
    /// artifact, and candidate hashes, avoiding a self-referential commitment.
    bytes32 public constant CANONICAL_POLICY_SHA256 =
        hex"c94784bc91de3b01c7ea27f945fbd27a3fb9a25f345bf681fc91fe119aee8eaf";
    uint256 public constant MAX_SIGNERS = 32;
    uint256 public constant MAX_GUARDIAN_PAUSE_TARGETS = 32;
    uint256 public constant MAX_TIMELOCK_SECONDS = 30 days;
    uint256 public constant MAX_GUARDIAN_ROTATION_DELAY = 30 days;
    uint256 public constant MAX_PROPOSAL_DATA_LENGTH = 24_576;
    uint256 public constant PROPOSAL_LIFETIME = 30 days;

    address[] public signers;
    mapping(address => bool) public isSigner;
    uint256 public threshold;
    uint256 public timelockSeconds;
    uint256 public immutable minimumTimelockSeconds;
    uint256 public immutable guardianRotationDelay;
    address public guardian;
    address public pendingGuardian;
    uint64 public pendingGuardianAcceptAfter;
    uint256 public governanceConfigVersion;

    mapping(address => bool) public isGuardianPauseTarget;
    uint256 public guardianPauseTargetCount;

    struct CallPolicy {
        bool allowed;
        uint96 maxValue;
        bytes32 runtimeCodehash;
    }
    mapping(address => mapping(bytes4 => CallPolicy)) public callPolicy;
    /// @notice Append-only exact address => runtime identity commitment.
    /// @dev A pinned address can never be rebound to another runtime. Every
    ///      privileged target and every address supplied as a protected
    ///      module/adapter argument must be present here.
    mapping(address => bytes32) public canonicalRuntimeCodehash;
    mapping(address => bool) public constructorPinnedPolicyTarget;
    bytes32 public policyCommitment;
    uint256 public policyVersion;

    struct Proposal {
        address to;
        uint256 value;
        bytes data;
        uint64 proposedAt;
        uint64 eta;
        uint256 requiredConfirmations;
        uint256 configVersion;
        bool executed;
        bool cancelled;
        uint256 confirmations;
    }

    Proposal[] public proposals;
    mapping(uint256 => mapping(address => bool)) public confirmed;
    mapping(uint256 => mapping(address => bool)) public cancellationConfirmed;
    mapping(uint256 => uint256) public cancellationConfirmations;
    mapping(uint256 => bytes32) public proposalPolicyCommitment;

    error NotSigner();
    error NotGuardian();
    error NotSelf();
    error AlreadyConfirmed();
    error AlreadyCancellationConfirmed();
    error ThresholdNotMet();
    error TimelockNotElapsed();
    error AlreadyExecuted();
    error Cancelled();
    error CallFailed();
    error InvalidConfig();
    error InvalidProposal();
    error ProposalTooLarge();
    error StaleProposal();
    error GuardianRotationNotReady();
    error NoPendingGuardian();
    error NotPendingGuardian();
    error GuardianTargetNotAllowed();
    error GuardianPauseFailed();
    error NativeValueNotAccepted();
    error PolicyDenied(address target, bytes4 selector, uint256 value);
    error ProposalExpired();

    event SignersUpdatedV3(address[] signers, uint256 threshold, uint256 indexed configVersion);
    event TimelockUpdatedV3(uint256 seconds_);
    event GuardianRotationProposedV3(
        address indexed currentGuardian, address indexed pendingGuardian, uint64 acceptAfter
    );
    event GuardianRotationCancelledV3(address indexed pendingGuardian);
    event GuardianUpdatedV3(address indexed previousGuardian, address indexed guardian);
    event GuardianPauseTargetConfiguredV3(address indexed target);
    event GuardianPausedV3(address indexed guardian, address indexed target);
    event ProposedV3(
        uint256 indexed proposalId,
        address indexed proposer,
        address indexed to,
        uint256 value,
        bytes data,
        uint64 eta,
        uint256 requiredConfirmations,
        uint256 configVersion
    );
    event ConfirmedV3(uint256 indexed proposalId, address indexed by, uint256 confirmations);
    event CancellationConfirmedV3(uint256 indexed proposalId, address indexed by, uint256 confirmations);
    event ExecutedV3(uint256 indexed proposalId, bytes returnData);
    event CancelledV3(uint256 indexed proposalId, address indexed by);
    event CallPolicySetV3(
        address indexed target,
        bytes4 indexed selector,
        bool allowed,
        uint96 maxValue,
        bytes32 runtimeCodehash,
        bytes32 policyCommitment
    );
    event CanonicalAddressPinnedV3(address indexed target, bytes32 indexed runtimeCodehash, bytes32 policyCommitment);

    modifier onlySigner() {
        if (!isSigner[msg.sender]) revert NotSigner();
        _;
    }

    modifier onlyGuardian() {
        if (msg.sender != guardian) revert NotGuardian();
        _;
    }

    modifier onlySelf() {
        if (msg.sender != address(this)) revert NotSelf();
        _;
    }

    constructor(
        address[] memory _signers,
        uint256 _threshold,
        uint256 _timelockSeconds,
        uint256 _minimumTimelockSeconds,
        address _guardian,
        uint256 _guardianRotationDelay,
        address[] memory _guardianPauseTargets,
        address[] memory _canonicalPolicyTargets,
        bytes32[] memory _canonicalPolicyTargetCodehashes
    ) {
        if (
            _minimumTimelockSeconds == 0 || _minimumTimelockSeconds > MAX_TIMELOCK_SECONDS
                || _timelockSeconds < _minimumTimelockSeconds || _timelockSeconds > MAX_TIMELOCK_SECONDS
                || _guardianRotationDelay == 0 || _guardianRotationDelay > MAX_GUARDIAN_ROTATION_DELAY
                || _guardian == address(0) || _guardianPauseTargets.length > MAX_GUARDIAN_PAUSE_TARGETS
                || _canonicalPolicyTargets.length != _canonicalPolicyTargetCodehashes.length
                || _canonicalPolicyTargets.length > 32
        ) revert InvalidConfig();

        minimumTimelockSeconds = _minimumTimelockSeconds;
        guardianRotationDelay = _guardianRotationDelay;
        _validateSignerConfig(_signers, _threshold, _guardian, address(0));
        _setSigners(_signers, _threshold);
        guardian = _guardian;

        for (uint256 i; i < _canonicalPolicyTargets.length; ++i) {
            address target = _canonicalPolicyTargets[i];
            bytes32 runtimeCodehash = _canonicalPolicyTargetCodehashes[i];
            if (target == address(0) || runtimeCodehash == bytes32(0) || canonicalRuntimeCodehash[target] != bytes32(0))
            {
                revert InvalidConfig();
            }
            canonicalRuntimeCodehash[target] = runtimeCodehash;
            constructorPinnedPolicyTarget[target] = true;
            policyCommitment = keccak256(
                abi.encode(policyCommitment, CANONICAL_POLICY_SHA256, target, runtimeCodehash, "POLICY_TARGET")
            );
            emit CanonicalAddressPinnedV3(target, runtimeCodehash, policyCommitment);
        }

        for (uint256 i; i < _guardianPauseTargets.length; ++i) {
            address target = _guardianPauseTargets[i];
            if (target == address(0) || target.code.length == 0 || isGuardianPauseTarget[target]) {
                revert InvalidConfig();
            }
            isGuardianPauseTarget[target] = true;
            bytes32 runtimeCodehash = target.codehash;
            bytes32 pinned = canonicalRuntimeCodehash[target];
            if (pinned != bytes32(0) && pinned != runtimeCodehash) revert InvalidConfig();
            canonicalRuntimeCodehash[target] = runtimeCodehash;
            constructorPinnedPolicyTarget[target] = true;
            policyCommitment = keccak256(
                abi.encode(policyCommitment, CANONICAL_POLICY_SHA256, target, runtimeCodehash, "GUARDIAN_PAUSE_TARGET")
            );
            unchecked {
                ++guardianPauseTargetCount;
            }
            emit GuardianPauseTargetConfiguredV3(target);
        }

        timelockSeconds = _timelockSeconds;
        emit SignersUpdatedV3(_signers, _threshold, governanceConfigVersion);
        emit TimelockUpdatedV3(_timelockSeconds);
        emit GuardianUpdatedV3(address(0), _guardian);
    }

    // ---------------------------------------------------------------------
    // Self-governed configuration (must be executed through a proposal)
    // ---------------------------------------------------------------------

    /// @notice Replaces the signer set and threshold after the existing
    ///         proposal threshold and timelock have been met.
    /// @dev Invalidates every prior pending proposal so confirmations from a
    ///      retired signer set can never execute under the new configuration.
    function setSigners(address[] calldata newSigners, uint256 newThreshold) external onlySelf {
        _validateSignerConfig(newSigners, newThreshold, guardian, pendingGuardian);
        _clearSigners();
        _setSigners(newSigners, newThreshold);
        unchecked {
            ++governanceConfigVersion;
        }
        emit SignersUpdatedV3(newSigners, newThreshold, governanceConfigVersion);
    }

    /// @notice Changes the operating timelock but never below the immutable
    ///         floor set during construction.
    function setTimelock(uint256 newTimelockSeconds) external onlySelf {
        if (newTimelockSeconds < minimumTimelockSeconds || newTimelockSeconds > MAX_TIMELOCK_SECONDS) {
            revert InvalidConfig();
        }
        timelockSeconds = newTimelockSeconds;
        emit TimelockUpdatedV3(newTimelockSeconds);
    }

    /// @notice Starts a two-step guardian rotation. The proposed guardian
    ///         gains no authority until it accepts after the cooldown.
    function proposeGuardian(address newGuardian) external onlySelf {
        if (newGuardian == address(0) || isSigner[newGuardian]) revert InvalidConfig();
        pendingGuardian = newGuardian;
        uint64 acceptAfter = uint64(block.timestamp + guardianRotationDelay);
        pendingGuardianAcceptAfter = acceptAfter;
        emit GuardianRotationProposedV3(guardian, newGuardian, acceptAfter);
    }

    /// @notice Cancels the outstanding guardian rotation through governance.
    function cancelGuardianRotation() external onlySelf {
        address previousPendingGuardian = pendingGuardian;
        if (previousPendingGuardian == address(0)) revert NoPendingGuardian();
        delete pendingGuardian;
        delete pendingGuardianAcceptAfter;
        emit GuardianRotationCancelledV3(previousPendingGuardian);
    }

    /// @notice Adds a pause target only through an already-timelocked,
    ///         threshold-confirmed governance proposal.
    /// @dev Bootstrap begins with no operational targets. A later partner
    ///      activation must add each reviewed child bond before allowing any
    ///      value path. Targets are append-only; neither the guardian nor an
    ///      EOA can widen this allowlist.
    function addGuardianPauseTarget(address target) external onlySelf {
        if (
            target == address(0) || target.code.length == 0 || isGuardianPauseTarget[target]
                || guardianPauseTargetCount >= MAX_GUARDIAN_PAUSE_TARGETS
        ) revert InvalidConfig();
        isGuardianPauseTarget[target] = true;
        unchecked {
            ++guardianPauseTargetCount;
        }
        emit GuardianPauseTargetConfiguredV3(target);
    }

    /// @notice Commits an exact target/selector/value capability through the
    ///         same threshold and timelock as every other governance change.
    /// @dev Default is deny. Policy changes cannot authorize native value to
    ///      this governance contract itself.
    function setCallPolicy(
        address target,
        bytes4 selector,
        bool allowed,
        uint96 maxValue,
        bytes32 expectedRuntimeCodehash
    ) external onlySelf {
        if (
            target == address(0) || target.code.length == 0 || maxValue != 0 || !_isCanonicalExternalSelector(selector)
                || expectedRuntimeCodehash == bytes32(0) || target.codehash != expectedRuntimeCodehash
                || !constructorPinnedPolicyTarget[target] || canonicalRuntimeCodehash[target] != expectedRuntimeCodehash
                || !_targetMatchesRule(target, selector)
        ) revert InvalidConfig();
        callPolicy[target][selector] =
            CallPolicy({ allowed: allowed, maxValue: maxValue, runtimeCodehash: expectedRuntimeCodehash });
        unchecked {
            ++policyVersion;
        }
        policyCommitment = keccak256(
            abi.encode(
                policyCommitment,
                CANONICAL_POLICY_SHA256,
                policyVersion,
                target,
                selector,
                allowed,
                maxValue,
                expectedRuntimeCodehash
            )
        );
        emit CallPolicySetV3(target, selector, allowed, maxValue, expectedRuntimeCodehash, policyCommitment);
    }

    /// @notice Append-only exact identity pin for protected module/adapter
    ///         arguments that are not themselves the call target.
    function pinCanonicalAddress(address target, bytes32 expectedRuntimeCodehash) external onlySelf {
        if (
            target == address(0) || target.code.length == 0 || expectedRuntimeCodehash == bytes32(0)
                || target.codehash != expectedRuntimeCodehash || canonicalRuntimeCodehash[target] != bytes32(0)
        ) revert InvalidConfig();
        canonicalRuntimeCodehash[target] = expectedRuntimeCodehash;
        unchecked {
            ++policyVersion;
        }
        policyCommitment = keccak256(
            abi.encode(
                policyCommitment,
                CANONICAL_POLICY_SHA256,
                policyVersion,
                target,
                expectedRuntimeCodehash,
                "CANONICAL_ADDRESS"
            )
        );
        emit CanonicalAddressPinnedV3(target, expectedRuntimeCodehash, policyCommitment);
    }

    /// @notice Completes guardian rotation after the cooldown. Only the
    ///         proposed key can accept, preventing a mistyped key from gaining
    ///         emergency authority.
    function acceptGuardian() external {
        if (pendingGuardian == address(0)) revert NoPendingGuardian();
        if (msg.sender != pendingGuardian) revert NotPendingGuardian();
        if (block.timestamp < pendingGuardianAcceptAfter) revert GuardianRotationNotReady();

        address previousGuardian = guardian;
        guardian = pendingGuardian;
        delete pendingGuardian;
        delete pendingGuardianAcceptAfter;
        emit GuardianUpdatedV3(previousGuardian, guardian);
    }

    // ---------------------------------------------------------------------
    // Proposal flow
    // ---------------------------------------------------------------------

    function propose(address to, uint256 value, bytes calldata data) external onlySigner returns (uint256 id) {
        if (to == address(0)) revert InvalidProposal();
        if (data.length > MAX_PROPOSAL_DATA_LENGTH) revert ProposalTooLarge();
        bytes4 selector = _selector(data);
        if (!_isGovernanceCallAllowed(to, value, data)) revert PolicyDenied(to, selector, value);

        uint64 eta = uint64(block.timestamp + timelockSeconds);
        id = proposals.length;
        proposals.push(
            Proposal({
                to: to,
                value: value,
                data: data,
                proposedAt: uint64(block.timestamp),
                eta: eta,
                requiredConfirmations: threshold,
                configVersion: governanceConfigVersion,
                executed: false,
                cancelled: false,
                confirmations: 0
            })
        );
        proposalPolicyCommitment[id] = policyCommitment;
        emit ProposedV3(id, msg.sender, to, value, data, eta, threshold, governanceConfigVersion);
    }

    function confirm(uint256 id) external onlySigner {
        Proposal storage p = _proposal(id);
        if (p.executed) revert AlreadyExecuted();
        if (p.cancelled) revert Cancelled();
        if (p.configVersion != governanceConfigVersion) revert StaleProposal();
        if (proposalPolicyCommitment[id] != policyCommitment) revert StaleProposal();
        if (confirmed[id][msg.sender]) revert AlreadyConfirmed();

        confirmed[id][msg.sender] = true;
        unchecked {
            ++p.confirmations;
        }
        emit ConfirmedV3(id, msg.sender, p.confirmations);
    }

    function execute(uint256 id) external onlySigner returns (bytes memory ret) {
        Proposal storage p = _proposal(id);
        if (p.executed) revert AlreadyExecuted();
        if (p.cancelled) revert Cancelled();
        if (p.configVersion != governanceConfigVersion) revert StaleProposal();
        if (proposalPolicyCommitment[id] != policyCommitment) revert StaleProposal();
        if (p.confirmations < p.requiredConfirmations) revert ThresholdNotMet();
        if (block.timestamp < p.eta) revert TimelockNotElapsed();
        if (block.timestamp > uint256(p.eta) + PROPOSAL_LIFETIME) revert ProposalExpired();
        bytes4 selector = _selector(p.data);
        if (!_isGovernanceCallAllowed(p.to, p.value, p.data)) revert PolicyDenied(p.to, selector, p.value);

        p.executed = true;
        bool ok;
        (ok, ret) = p.to.call{ value: p.value }(p.data);
        if (!ok) revert CallFailed();
        emit ExecutedV3(id, ret);
    }

    /// @notice Confirms cancellation of a pending proposal. Cancellation takes
    ///         effect only after the proposal's snapped threshold confirms it.
    /// @dev Cancellation has no timelock because it cannot execute a target
    ///      call. It uses independent confirmations so execution approvals
    ///      cannot be repurposed as cancellation approvals.
    function cancel(uint256 id) external onlySigner {
        Proposal storage p = _proposal(id);
        if (p.executed) revert AlreadyExecuted();
        if (p.cancelled) revert Cancelled();
        if (p.configVersion != governanceConfigVersion) revert StaleProposal();
        if (cancellationConfirmed[id][msg.sender]) revert AlreadyCancellationConfirmed();

        cancellationConfirmed[id][msg.sender] = true;
        unchecked {
            ++cancellationConfirmations[id];
        }
        emit CancellationConfirmedV3(id, msg.sender, cancellationConfirmations[id]);

        if (cancellationConfirmations[id] >= p.requiredConfirmations) {
            p.cancelled = true;
            emit CancelledV3(id, msg.sender);
        }
    }

    /// @notice Guardian's sole authority: invoke pause() on the allowlist.
    ///         It cannot unpause, execute, or change any governance
    ///         configuration.
    function guardianPause(address target) external onlyGuardian {
        if (!isGuardianPauseTarget[target]) revert GuardianTargetNotAllowed();
        (bool ok,) = target.call(abi.encodeWithSignature("pause()"));
        if (!ok) revert GuardianPauseFailed();
        emit GuardianPausedV3(msg.sender, target);
    }

    /// @notice Governance holds authority, not protocol funds. Rejecting
    ///         direct native transfers keeps a read-only bootstrap from
    ///         acquiring an accidental value-acceptance surface.
    receive() external payable {
        revert NativeValueNotAccepted();
    }

    function proposalCount() external view returns (uint256) {
        return proposals.length;
    }

    function _proposal(uint256 id) private view returns (Proposal storage p) {
        if (id >= proposals.length) revert InvalidProposal();
        return proposals[id];
    }

    function _selector(bytes memory data) private pure returns (bytes4 selector) {
        if (data.length < 4) revert InvalidProposal();
        assembly ("memory-safe") {
            selector := mload(add(data, 0x20))
        }
    }

    function governancePolicyCommitment() external pure returns (bytes32) {
        return CANONICAL_POLICY_SHA256;
    }

    function isGovernanceCallAllowed(address target, uint256 value, bytes calldata data) external view returns (bool) {
        return _isGovernanceCallAllowed(target, value, data);
    }

    function _isGovernanceCallAllowed(address target, uint256 value, bytes memory data) private view returns (bool) {
        if (target == address(0) || value != 0 || data.length < 4 || data.length > MAX_PROPOSAL_DATA_LENGTH) {
            return false;
        }
        if (timelockSeconds < 1 days) return false;
        bytes4 selector = _selector(data);
        if (!_policyAllows(target, selector, value)) return false;
        return
            target == address(this) ? _validateSelfCall(selector, data) : _validateExternalCall(target, selector, data);
    }

    function _validateSelfCall(bytes4 selector, bytes memory data) private view returns (bool) {
        if (selector == this.cancelGuardianRotation.selector) return data.length == 4 && pendingGuardian != address(0);
        if (selector == this.setSigners.selector) return _validateSetSigners(data);
        if (selector == this.setCallPolicy.selector) {
            if (data.length != 164) return false;
            uint256 targetWord = _word(data, 0);
            uint256 selectorWord = _word(data, 1);
            uint256 allowedWord = _word(data, 2);
            uint256 maxValueWord = _word(data, 3);
            bytes32 expectedRuntimeCodehash = bytes32(_word(data, 4));
            address target = address(uint160(targetWord));
            bytes4 governedSelector = bytes4(uint32(selectorWord >> 224));
            return targetWord >> 160 == 0 && target != address(0) && target.code.length != 0
                && selectorWord & type(uint224).max == 0 && (allowedWord == 0 || allowedWord == 1) && maxValueWord == 0
                && expectedRuntimeCodehash != bytes32(0) && target.codehash == expectedRuntimeCodehash
                && constructorPinnedPolicyTarget[target] && canonicalRuntimeCodehash[target] == expectedRuntimeCodehash
                && _isCanonicalExternalSelector(governedSelector) && _targetMatchesRule(target, governedSelector);
        }
        if (selector == this.pinCanonicalAddress.selector) {
            if (data.length != 68) return false;
            uint256 targetWord = _word(data, 0);
            bytes32 expectedRuntimeCodehash = bytes32(_word(data, 1));
            address target = address(uint160(targetWord));
            return targetWord >> 160 == 0 && target != address(0) && target.code.length != 0
                && expectedRuntimeCodehash != bytes32(0) && target.codehash == expectedRuntimeCodehash
                && canonicalRuntimeCodehash[target] == bytes32(0);
        }
        if (data.length != 36) return false;
        uint256 argument = _word(data, 0);
        if (selector == this.setTimelock.selector) {
            return argument >= minimumTimelockSeconds && argument <= MAX_TIMELOCK_SECONDS;
        }
        if (argument >> 160 != 0 || argument == 0) return false;
        address account = address(uint160(argument));
        if (selector == this.addGuardianPauseTarget.selector) {
            return account.code.length != 0 && _isSupportedPauseTarget(account);
        }
        if (selector == this.proposeGuardian.selector) {
            return !isSigner[account] && account != guardian && account != pendingGuardian;
        }
        return false;
    }

    function _validateSetSigners(bytes memory data) private view returns (bool) {
        if (data.length < 260 || data.length > 1124 || _word(data, 0) != 64) return false;
        uint256 threshold_ = _word(data, 1);
        uint256 count = _word(data, 2);
        if (count < 5 || count > MAX_SIGNERS || threshold_ < 3 || threshold_ > count) return false;
        if (data.length != 100 + count * 32) return false;
        for (uint256 i; i < count; ++i) {
            uint256 signerWord = _word(data, 3 + i);
            if (signerWord >> 160 != 0 || signerWord == 0) return false;
            address signer = address(uint160(signerWord));
            if (signer == guardian || signer == pendingGuardian) return false;
            for (uint256 j; j < i; ++j) {
                if (signerWord == _word(data, 3 + j)) return false;
            }
        }
        return true;
    }

    function _validateExternalCall(address target, bytes4 selector, bytes memory data) private view returns (bool) {
        CallPolicy memory exactPolicy = callPolicy[target][selector];
        if (
            exactPolicy.runtimeCodehash == bytes32(0) || canonicalRuntimeCodehash[target] != exactPolicy.runtimeCodehash
                || target.codehash != exactPolicy.runtimeCodehash || !_targetMatchesRule(target, selector)
        ) return false;
        if (
            selector == 0x212bf316 || selector == 0xb1662d58 || selector == 0x8601ff8b || selector == 0x7db455ce
                || selector == 0x5c0204d9 || selector == 0x39800d46 || selector == 0xc4dac5fa
        ) {
            if (data.length != 68) return false;
        } else if (
            selector == 0x2061314c || selector == 0x3a902491 || selector == 0xe5a98603 || selector == 0x3a281eb8
                || selector == 0x8697c874
        ) {
            if (data.length != 36) return false;
        } else {
            return false;
        }

        uint256 a = _word(data, 0);
        uint256 b = data.length == 68 ? _word(data, 1) : 0;
        if (selector == 0x212bf316) return a != 0 && b != 0 && _capsWithinTargetMaxima(target, a, b);
        if (selector == 0xb1662d58) {
            if (!_canonicalAddressBool(a, b)) return false;
            return b == 0 || _isSupportedPilotModule(address(uint160(a)), target);
        }
        if (selector == 0x8601ff8b) {
            return _canonicalAddressBool(a, b) && _designPartnerMutationAllowed(target, address(uint160(a)), b == 1);
        }
        if (selector == 0x2061314c) return a <= 1 && (a == 0 || _pilotDeployReady(target));
        if (selector == 0x3a902491) return a <= 1;
        if (selector == 0x3a281eb8) return a <= 1 && (a == 0 || _safeFactoryReady(target));
        if (selector == 0x7db455ce) {
            if (!_canonicalAddressBool(a, b)) return false;
            return b == 0 || _isCanonicalMandateAdapter(address(uint160(a)), target);
        }
        if (selector == 0x5c0204d9) return _canonicalAddressBool(a, b);
        if (selector == 0xe5a98603) return a >= 3 && a <= 255 && a <= _readUint(target, "attesterCount()");
        if (selector == 0x39800d46) {
            return _canonicalAddress(a) && _canonicalAddress(b)
                && _factoryHashesMatch(target, address(uint160(a)), address(uint160(b)));
        }
        if (selector == 0xc4dac5fa) {
            return _canonicalAddress(a) && b == 0 && _designPartnerAllowed(target, address(uint160(a)));
        }
        if (selector == 0x8697c874) return _canonicalAddress(a) && _designPartnerAllowed(target, address(uint160(a)));
        return false;
    }

    function _isCanonicalExternalSelector(bytes4 selector) private pure returns (bool) {
        return selector == 0x212bf316 || selector == 0xb1662d58 || selector == 0x8601ff8b || selector == 0x2061314c
            || selector == 0x3a902491 || selector == 0x7db455ce || selector == 0x5c0204d9 || selector == 0xe5a98603
            || selector == 0x39800d46 || selector == 0x3a281eb8 || selector == 0xc4dac5fa || selector == 0x8697c874;
    }

    function _targetMatchesRule(address target, bytes4 selector) private view returns (bool) {
        if (
            selector == 0x212bf316 || selector == 0xb1662d58 || selector == 0x8601ff8b || selector == 0x2061314c
                || selector == 0x3a902491
        ) {
            return _readUint(target, "requiredChainId()") == block.chainid
                && _readBytes32(target, "GOVERNANCE_POLICY_MODULE_ID()")
                    == keccak256("Vaultfire.BaseV3.PilotController");
        }
        if (selector == 0x7db455ce) {
            return _readBytes32(target, "GOVERNANCE_POLICY_MODULE_ID()")
                    == keccak256("Vaultfire.BaseV3.MandateRegistry")
                && _readBytes32(target, "requiredExecutionAdapterCodehash()") != bytes32(0);
        }
        if (selector == 0xe5a98603) {
            return _readBytes32(target, "GOVERNANCE_POLICY_MODULE_ID()")
                    == keccak256("Vaultfire.BaseV3.ThresholdConsensus")
                && _readUint(target, "attesterCount()") != type(uint256).max;
        }
        if (selector == 0x39800d46 || selector == 0x3a281eb8 || selector == 0xc4dac5fa || selector == 0x8697c874) {
            return _readBytes32(target, "SUPPORTED_PILOT_MODULE_ID()") == keccak256("Vaultfire.BaseV3.SafeBondFactory");
        }
        if (selector == 0x5c0204d9) {
            bytes32 kind = _readBytes32(target, "GOVERNANCE_POLICY_MODULE_ID()");
            return _readAddress(target, "owner()") == address(this)
                && (kind == keccak256("Vaultfire.BaseV3.EvidenceRegistry")
                    || kind == keccak256("Vaultfire.BaseV3.ThresholdConsensus"));
        }
        return false;
    }

    function _canonicalAddress(uint256 word) private pure returns (bool) {
        return word != 0 && word >> 160 == 0;
    }

    function _canonicalAddressBool(uint256 addressWord, uint256 boolWord) private pure returns (bool) {
        return _canonicalAddress(addressWord) && boolWord <= 1;
    }

    function _isSupportedPauseTarget(address target) private view returns (bool) {
        bytes32 expected = canonicalRuntimeCodehash[target];
        return expected != bytes32(0) && target.codehash == expected && _readAddress(target, "owner()") == address(this);
    }

    function _isSupportedPilotModule(address module, address controller) private view returns (bool) {
        bytes32 expected = canonicalRuntimeCodehash[module];
        return expected != bytes32(0) && module.codehash == expected
            && _readBytes32(module, "supportedPilotModuleId()") == keccak256("Vaultfire.BaseV3.SafeBondFactory")
            && _readAddress(module, "pilotController()") == controller;
    }

    function _isCanonicalMandateAdapter(address adapter, address registry) private view returns (bool) {
        bytes32 expected = canonicalRuntimeCodehash[adapter];
        return expected != bytes32(0) && adapter.codehash == expected
            && _readAddress(adapter, "mandateRegistry()") == registry;
    }

    function _capsWithinTargetMaxima(address target, uint256 globalCap_, uint256 perPositionCap_)
        private
        view
        returns (bool)
    {
        uint256 maxGlobal = _readUint(target, "maxGlobalCap()");
        uint256 maxPosition = _readUint(target, "maxPerPositionCap()");
        return globalCap_ <= maxGlobal && perPositionCap_ <= maxPosition && perPositionCap_ <= globalCap_;
    }

    function _factoryHashesMatch(address target, address partnership, address accountability)
        private
        view
        returns (bool)
    {
        return partnership.codehash == _readBytes32(target, "expectedPartnershipFactoryRuntimeCodeHash()")
            && accountability.codehash == _readBytes32(target, "expectedAccountabilityFactoryRuntimeCodeHash()");
    }

    function _designPartnerAllowed(address factory, address partner) private view returns (bool) {
        address controller = _readAddress(factory, "pilotController()");
        if (controller == address(0)) return false;
        (bool ok, bytes memory result) =
            controller.staticcall(abi.encodeWithSignature("isDesignPartner(address)", partner));
        return ok && result.length == 32 && abi.decode(result, (bool));
    }

    function _pilotDeployReady(address controller) private view returns (bool) {
        return _readUint(controller, "moduleCount()") != 0 && _readUint(controller, "designPartnerCount()") == 1
            && _readUint(controller, "globalCap()") != 0 && _readUint(controller, "perPositionCap()") != 0;
    }

    function _designPartnerMutationAllowed(address controller, address partner, bool allowed)
        private
        view
        returns (bool)
    {
        (bool ok, bytes memory result) =
            controller.staticcall(abi.encodeWithSignature("isDesignPartner(address)", partner));
        if (!ok || result.length != 32) return false;
        bool currentlyAllowed = abi.decode(result, (bool));
        if (allowed) return currentlyAllowed || _readUint(controller, "designPartnerCount()") == 0;
        return currentlyAllowed;
    }

    function _safeFactoryReady(address factory) private view returns (bool) {
        return _readAddress(factory, "partnershipFactory()") != address(0)
            && _readAddress(factory, "accountabilityFactory()") != address(0)
            && _readBytes32(factory, "expectedAccountabilityBondInitCodeHash()")
                == _readBytes32(_readAddress(factory, "accountabilityFactory()"), "childInitCodeHash()");
    }

    function _readUint(address target, string memory signature) private view returns (uint256 result) {
        (bool ok, bytes memory data) = target.staticcall(abi.encodeWithSignature(signature));
        return ok && data.length == 32 ? abi.decode(data, (uint256)) : type(uint256).max;
    }

    function _readBytes32(address target, string memory signature) private view returns (bytes32 result) {
        (bool ok, bytes memory data) = target.staticcall(abi.encodeWithSignature(signature));
        return ok && data.length == 32 ? abi.decode(data, (bytes32)) : bytes32(0);
    }

    function _readAddress(address target, string memory signature) private view returns (address result) {
        (bool ok, bytes memory data) = target.staticcall(abi.encodeWithSignature(signature));
        return ok && data.length == 32 ? abi.decode(data, (address)) : address(0);
    }

    function _word(bytes memory data, uint256 index) private pure returns (uint256 value) {
        if (data.length < 36 + index * 32) return type(uint256).max;
        assembly ("memory-safe") {
            value := mload(add(add(data, 0x24), mul(index, 0x20)))
        }
    }

    function _policyAllows(address target, bytes4 selector, uint256 value) private view returns (bool) {
        if (target == address(this)) {
            return value == 0
                && (selector == this.setSigners.selector
                    || selector == this.setTimelock.selector
                    || selector == this.proposeGuardian.selector
                    || selector == this.cancelGuardianRotation.selector
                    || selector == this.addGuardianPauseTarget.selector
                    || selector == this.setCallPolicy.selector
                    || selector == this.pinCanonicalAddress.selector);
        }
        CallPolicy memory policy = callPolicy[target][selector];
        return policy.allowed && value <= policy.maxValue && policy.runtimeCodehash != bytes32(0)
            && canonicalRuntimeCodehash[target] == policy.runtimeCodehash && target.codehash == policy.runtimeCodehash;
    }

    function _validateSignerConfig(
        address[] memory candidateSigners,
        uint256 candidateThreshold,
        address currentGuardian,
        address candidateGuardian
    ) private pure {
        if (
            candidateSigners.length == 0 || candidateSigners.length > MAX_SIGNERS || candidateThreshold == 0
                || candidateThreshold > candidateSigners.length
        ) revert InvalidConfig();

        for (uint256 i; i < candidateSigners.length; ++i) {
            address signer = candidateSigners[i];
            if (signer == address(0) || signer == currentGuardian || signer == candidateGuardian) {
                revert InvalidConfig();
            }
            for (uint256 j; j < i; ++j) {
                if (signer == candidateSigners[j]) revert InvalidConfig();
            }
        }
    }

    function _setSigners(address[] memory newSigners, uint256 newThreshold) private {
        signers = newSigners;
        for (uint256 i; i < newSigners.length; ++i) {
            isSigner[newSigners[i]] = true;
        }
        threshold = newThreshold;
    }

    function _clearSigners() private {
        uint256 signerCount = signers.length;
        for (uint256 i; i < signerCount; ++i) {
            isSigner[signers[i]] = false;
        }
        delete signers;
    }
}
