NounsPassVotingResolver

Description:

Governance contract for decentralized decision-making.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/NounsPassVotingResolver.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/* ──────────────────────────────────────────────────────────────────────────
   Nouns “Pass Voting?” resolver for PAMM (YES/NO)
   • Governor: 0x6f3E6272A167e8AcCb32072d08E0957F9c79223d (NounsDAOLogicV4)
   • PAMM:     0x000000000071176401AdA1f2CD7748e28E173FCa
   • One market per Nouns proposal:
        YES if the proposal *passed voting*:
          state ∈ {Succeeded, Queued, Executed, Expired}
          (checked right after endBlock when no objection, or at objection end, or at/after evalBlock)
        NO if {Defeated, Vetoed}.
     Other states are non-terminal and don’t resolve.
   • Auto-sync across many proposals:
       - syncNewProposals(max)    → discover/adopt/create
       - syncOne(id) / poke(id)   → keep eval fresh & resolve ASAP
   • Adopt-or-create: if a market with the same description/resolver already
     exists (even if created by someone else), we adopt when closable or time-closed; 
     otherwise create our own.
   • Objection refresh: updates evalBlock/closeAt once the DAO sets it.
   • Emergency resolve: owner-only after a buffer AND only when Nouns state is
     terminal (YES/NO). Will skip dead markets.
   • Pausable creation/resolution, withdraw tips, ERC20 sweep.
   ───────────────────────────────────────────────────────────────────────── */

interface INounsGovernor {
    enum ProposalState {
        Pending, // 0
        Active, // 1
        Canceled, // 2
        Defeated, // 3
        Succeeded, // 4
        Queued, // 5
        Expired, // 6
        Executed, // 7
        Vetoed, // 8
        ObjectionPeriod, // 9 (added in V3/V4)
        Updatable // 10 (added in V3/V4)

    }

    struct ProposalCondensedV3 {
        uint256 id;
        address proposer;
        uint256 proposalThreshold;
        uint256 quorumVotes;
        uint256 eta;
        uint256 startBlock;
        uint256 endBlock;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 abstainVotes;
        bool canceled;
        bool vetoed;
        bool executed;
        uint256 totalSupply;
        uint256 creationBlock;
        address[] signers;
        uint256 updatePeriodEndBlock;
        uint256 objectionPeriodEndBlock;
        bool executeOnTimelockV1;
    }

    function proposalCount() external view returns (uint256);
    function proposalsV3(uint256 id) external view returns (ProposalCondensedV3 memory);
    function state(uint256 id) external view returns (ProposalState);
    function objectionPeriodDurationInBlocks() external view returns (uint256);
}

interface IPAMM {
    struct PMTuning {
        uint32 lateRampStart; // seconds before close to start ramp (0 = off)
        uint16 lateRampMaxBps; // +bps at/after close (EV charge)
        uint16 extremeMaxBps; // +bps near p≈0/1
    }

    function createMarketWithPMTuning(
        string calldata description,
        address resolver,
        uint72 close,
        bool canClose,
        uint256 seedYes,
        uint256 seedNo,
        PMTuning calldata t
    ) external returns (uint256 marketId, uint256 noId);

    function closeMarket(uint256 marketId) external;
    function resolve(uint256 marketId, bool outcome) external;
}

interface IPAMMExtra is IPAMM {
    function getMarket(uint256 marketId)
        external
        view
        returns (
            uint256 yesSupply,
            uint256 noSupply,
            address resolver,
            bool resolved,
            bool outcome,
            uint256 pot,
            uint256 payoutPerShare,
            string memory desc,
            uint72 closeTs,
            bool canClose,
            uint256 rYes,
            uint256 rNo,
            uint256 pYes_num,
            uint256 pYes_den
        );

    function balanceOf(address owner, uint256 id) external view returns (uint256);
    function getNoId(uint256 marketId) external pure returns (uint256);
    function impliedYesProb(uint256 marketId) external view returns (uint256 num, uint256 den);
}

contract NounsPassVotingResolver {
    /* ───────────────────────── constants / addrs ───────────────────────── */

    INounsGovernor public constant GOV = INounsGovernor(0x6f3E6272A167e8AcCb32072d08E0957F9c79223d);
    IPAMMExtra public constant PAMM = IPAMMExtra(0x000000000071176401AdA1f2CD7748e28E173FCa);

    // ZAMM address excluded from “circulating” (matches PAMM’s).
    address public constant ZAMM = 0x000000000000040470635EB91b7CE4D132D616eD;

    // For estimating PAMM close timestamp; resolution is block-gated.
    uint256 public secsPerBlock = 12;

    /* ───────────────────────── owner / admin ───────────────────────────── */

    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "OWNER");
        _;
    }

    event OwnershipTransferred(address indexed from, address indexed to);

    function transferOwnership(address to) public payable onlyOwner {
        emit OwnershipTransferred(msg.sender, owner = to);
    }

    /* ───────────────────────── tips / liveness ─────────────────────────── */

    uint256 public tipPerAction = 0.001 ether;

    event Funded(address indexed from, uint256 amount);
    event TipPaid(address indexed to, uint256 amount);

    receive() external payable {
        emit Funded(msg.sender, msg.value);
    }

    function fundTips() public payable {
        emit Funded(msg.sender, msg.value);
    }

    function setTipPerAction(uint256 tip) public payable onlyOwner {
        tipPerAction = tip;
    }

    function setSecsPerBlock(uint256 s) public payable onlyOwner {
        secsPerBlock = s;
    }

    // Owner may withdraw leftover ETH used for tips.
    function withdrawTips(address payable to, uint256 amount) public payable onlyOwner {
        if (amount == 0 || amount > address(this).balance) amount = address(this).balance;
        (bool ok,) = to.call{value: amount}("");
        require(ok, "WITHDRAW_FAIL");
    }

    // Owner can sweep arbitrary ERC20s accidentally sent here.
    function sweepERC20(address token, address to, uint256 amount) public payable onlyOwner {
        (bool ok, bytes memory d) =
            token.call(abi.encodeWithSignature("transfer(address,uint256)", to, amount));
        require(ok && (d.length == 0 || abi.decode(d, (bool))), "SWEEP_FAIL");
    }

    /* ───────────────────────── PM tuning / seeds ───────────────────────── */

    IPAMM.PMTuning public pmDefaults = IPAMM.PMTuning({
        lateRampStart: 15 minutes,
        lateRampMaxBps: 100, // +1.00% EV charge at/after close
        extremeMaxBps: 50 // +0.50% near extremes
    });

    uint256 public seedYes = 1e18; // initial symmetric seeds
    uint256 public seedNo = 1e18;

    uint256 public adoptMaxCloseDrift = 2 days;

    function setPMTuning(IPAMM.PMTuning calldata t) public payable onlyOwner {
        require(t.lateRampMaxBps <= 2000 && t.extremeMaxBps <= 2000, "TUNING_CAP");
        pmDefaults = t;
    }

    function setSeeds(uint256 _yes, uint256 _no) public payable onlyOwner {
        require((_yes == 0) == (_no == 0), "SEEDS_BOTH_ZERO_OR_BOTH_NONZERO");
        seedYes = _yes;
        seedNo = _no;
    }

    function setAdoptMaxCloseDrift(uint256 _adoptMaxCloseDrift) public payable onlyOwner {
        adoptMaxCloseDrift = _adoptMaxCloseDrift;
    }

    /* ───────────────────────── markets / storage ───────────────────────── */

    struct MarketInfo {
        uint256 marketId;
        uint64 evalBlock; // target evaluation block
        uint72 closeAt; // PAMM close timestamp estimate (for early-close)
        bool created;
        bool settled;
        bool deadLogged;
    }

    uint256 public lastScannedProposalId;
    mapping(uint256 proposalId => MarketInfo) public marketOf;

    event MarketCreated(
        uint256 indexed proposalId, uint256 marketId, uint64 evalBlock, uint72 closeAt
    );
    event MarketAdopted(
        uint256 indexed proposalId, uint256 marketId, uint64 evalBlock, uint72 closeAt
    );
    event MarketResolved(uint256 indexed proposalId, uint256 marketId, bool yes);
    event DeadMarketSkipped(uint256 indexed proposalId, uint256 marketId);
    event EvalUpdated(uint256 indexed proposalId, uint64 newEvalBlock, uint72 newCloseAt);

    /* ───────────────────────── emergency backstop ──────────────────────── */

    // Buffer (blocks) beyond evalBlock after which owner can emergency-resolve,
    // but ONLY if Nouns state is terminal YES/NO.
    uint256 public emergencyDelayBlocks = 7200; // ~1 day

    event EmergencyResolved(uint256 indexed proposalId, uint256 marketId, bool outcome);

    function setEmergencyDelayBlocks(uint256 d) public payable onlyOwner {
        require(d >= 300 && d <= 200_000, "BAD_EMERGENCY_DELAY"); // ~1h .. ~4w
        emergencyDelayBlocks = d;
    }

    function _deadSkip(MarketInfo storage m, uint256 proposalId) internal {
        if (!m.deadLogged) {
            m.deadLogged = true;
            emit DeadMarketSkipped(proposalId, m.marketId);
        }
    }

    /* ───────────────────────── switches ────────────────────────────────── */

    bool public creationPaused;
    bool public resolutionPaused;

    function setCreationPaused(bool p) public payable onlyOwner {
        creationPaused = p;
    }

    function setResolutionPaused(bool p) public payable onlyOwner {
        resolutionPaused = p;
    }

    /* ───────────────────────── reentrancy guard ────────────────────────── */

    error Reentrancy();

    uint256 constant REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268;

    modifier nonReentrant() {
        assembly ("memory-safe") {
            if tload(REENTRANCY_GUARD_SLOT) {
                mstore(0x00, 0xab143c06)
                revert(0x1c, 0x04)
            }
            tstore(REENTRANCY_GUARD_SLOT, address())
        }
        _;
        assembly ("memory-safe") {
            tstore(REENTRANCY_GUARD_SLOT, 0)
        }
    }

    /* ───────────────────────── constructor ─────────────────────────────── */

    constructor() payable {
        emit OwnershipTransferred(address(0), owner = msg.sender);
    }

    /* ───────────────────────── public sync API ─────────────────────────── */

    /// @notice Discover/adopt/create markets for newly-created proposals up to `maxScan`.
    function syncNewProposals(uint256 maxScan) public payable nonReentrant {
        if (creationPaused) return;

        uint256 latest = GOV.proposalCount();
        uint256 hi = lastScannedProposalId + maxScan;
        if (hi > latest) hi = latest;

        for (uint256 id = lastScannedProposalId + 1; id <= hi; ++id) {
            _maybeCreateOrAdopt(id);
        }
        lastScannedProposalId = hi;
    }

    /// @notice Idempotent per-proposal sync: adopt/create if needed, then try to resolve if ready.
    function syncOne(uint256 proposalId) public payable nonReentrant {
        if (!creationPaused) {
            _maybeCreateOrAdopt(proposalId);
        }
        _refreshEval(proposalId); // always refresh eval/close
        if (!resolutionPaused) {
            _maybeResolve(proposalId, true);
        }
    }

    /// @notice Fast path: attempt to early-close & resolve now if possible.
    function poke(uint256 proposalId) public payable nonReentrant {
        require(!resolutionPaused, "RESOLUTION_PAUSED");
        _refreshEval(proposalId);
        bool ok = _maybeResolve(proposalId, true);
        require(ok, "NOTHING_TO_DO");
    }

    error NotReady();

    function emergencyResolve(uint256 proposalId) public payable onlyOwner nonReentrant {
        require(!resolutionPaused, "RESOLUTION_PAUSED");
        MarketInfo storage m = marketOf[proposalId];
        require(m.created && !m.settled, "NO_MARKET_OR_DONE");
        require(block.number >= uint256(m.evalBlock) + emergencyDelayBlocks, "TOO_SOON");

        INounsGovernor.ProposalState s = GOV.state(proposalId);
        bool yesTerminal = _isYesTerminalState(s);
        bool noTerminal = _isNoTerminalState(s);
        require(yesTerminal || noTerminal, "NON_TERMINAL");

        // Start with the DAO-indicated terminal side.
        bool outcome = yesTerminal; // true = YES, false = NO

        // If chosen side has no winners, only skip if BOTH sides have no winners.
        if (!_hasWinners(m.marketId, outcome)) {
            if (!_hasWinners(m.marketId, !outcome)) {
                _deadSkip(m, proposalId);
                return;
            }
            // Auto-flip to the side that does have circulating winners.
            outcome = !outcome;
        }

        if (!_closeIfNeeded(m)) revert NotReady();

        PAMM.resolve(m.marketId, outcome);
        m.settled = true;
        emit EmergencyResolved(proposalId, m.marketId, outcome);
    }

    /* ───────────────────────── core logic ──────────────────────────────── */

    function _maybeCreateOrAdopt(uint256 proposalId) internal {
        MarketInfo storage m = marketOf[proposalId];
        if (m.created) return;

        INounsGovernor.ProposalCondensedV3 memory p = GOV.proposalsV3(proposalId);
        uint256 objectionDur = GOV.objectionPeriodDurationInBlocks();

        // current eval (with objection if set):
        uint256 evalBlk =
            p.objectionPeriodEndBlock != 0 ? p.objectionPeriodEndBlock : p.endBlock + objectionDur;
        uint72 closeTsEstimate = _estimateClose(uint64(evalBlk));

        // two candidate descriptions: current + “no-objection” fallback:
        string memory descNow =
            _descPassVoting(proposalId, p.endBlock, p.objectionPeriodEndBlock, evalBlk);
        string memory descNoObj =
            _descPassVoting(proposalId, p.endBlock, 0, p.endBlock + objectionDur);

        // --- try adopt (current) ---
        uint256 midNow = uint256(keccak256(abi.encodePacked("PMARKET:YES", descNow, address(this))));
        (,, address resNow,,,,,, uint72 closeNow, bool canCloseNow,,,,) = PAMM.getMarket(midNow);
        bool adoptNow = (resNow != address(0));
        if (adoptNow) {
            // Require either early-closable OR already time-closed:
            if (!canCloseNow) {
                adoptNow = (closeNow != 0 && block.timestamp >= closeNow);
            }
            // Refuse adoption if close is griefy-far in the future vs our estimate:
            if (adoptNow && closeNow != 0 && closeNow > closeTsEstimate) {
                if (uint256(closeNow) - uint256(closeTsEstimate) > adoptMaxCloseDrift) {
                    adoptNow = false;
                }
            }
        }
        bool refusedCurrentAdoption = (resNow != address(0) && !adoptNow);

        if (adoptNow) {
            m.marketId = midNow;
            m.evalBlock = uint64(evalBlk);
            m.closeAt = (closeNow != 0) ? closeNow : closeTsEstimate;
            m.created = true;
            emit MarketAdopted(proposalId, midNow, uint64(evalBlk), m.closeAt);
            _payTip(msg.sender);
            return;
        }

        // --- try adopt (no-objection) ---
        uint256 midNoObj =
            uint256(keccak256(abi.encodePacked("PMARKET:YES", descNoObj, address(this))));
        (,, address resNoObj,,,,,, uint72 closeNoObj, bool canCloseNoObj,,,,) =
            PAMM.getMarket(midNoObj);

        bool adoptNoObj = (resNoObj != address(0));
        if (adoptNoObj) {
            // Require either early-closable or already time-closed:
            if (!canCloseNoObj) {
                adoptNoObj = (closeNoObj != 0 && block.timestamp >= closeNoObj);
            }
            // Refuse adoption if close is griefy-far in the future vs our estimate:
            if (adoptNoObj && closeNoObj != 0 && closeNoObj > closeTsEstimate) {
                if (uint256(closeNoObj) - uint256(closeTsEstimate) > adoptMaxCloseDrift) {
                    adoptNoObj = false;
                }
            }
        }
        bool refusedNoObjAdoption = (resNoObj != address(0) && !adoptNoObj);

        if (adoptNoObj) {
            m.marketId = midNoObj;
            m.evalBlock = uint64(evalBlk);
            m.closeAt = (closeNoObj != 0) ? closeNoObj : closeTsEstimate;
            m.created = true;
            emit MarketAdopted(proposalId, midNoObj, uint64(evalBlk), m.closeAt);
            _payTip(msg.sender);
            return;
        }

        // --- otherwise create using (possibly suffixed) desc to avoid ID collision if we refused adoption ---
        string memory descCreate = descNow;
        if (refusedCurrentAdoption || refusedNoObjAdoption) {
            descCreate = string(abi.encodePacked(descNow, " ;resolver=closable-v1"));
        }

        (uint256 newMarketId,) = PAMM.createMarketWithPMTuning(
            descCreate, address(this), closeTsEstimate, true, seedYes, seedNo, pmDefaults
        );

        m.marketId = newMarketId;
        m.evalBlock = uint64(evalBlk);
        m.closeAt = closeTsEstimate;
        m.created = true;

        emit MarketCreated(proposalId, newMarketId, uint64(evalBlk), closeTsEstimate);
        _payTip(msg.sender);
    }

    /// Keep evalBlock/current closeAt fresh if objection is set after creation.
    function _refreshEval(uint256 proposalId) internal {
        MarketInfo storage m = marketOf[proposalId];
        if (!m.created || m.settled) return;

        INounsGovernor.ProposalCondensedV3 memory p = GOV.proposalsV3(proposalId);
        if (p.objectionPeriodEndBlock != 0 && uint64(p.objectionPeriodEndBlock) != m.evalBlock) {
            m.evalBlock = uint64(p.objectionPeriodEndBlock);
            m.closeAt = _estimateClose(m.evalBlock);
            emit EvalUpdated(proposalId, m.evalBlock, m.closeAt);
        }
    }

    /// Try to resolve; returns true if it resolved or skipped (dead-market).
    function _maybeResolve(uint256 proposalId, bool payTip) internal returns (bool) {
        MarketInfo storage m = marketOf[proposalId];
        if (!m.created || m.settled) return false;

        INounsGovernor.ProposalState s = GOV.state(proposalId);
        INounsGovernor.ProposalCondensedV3 memory p = GOV.proposalsV3(proposalId);

        // --- CANCELED: resolve by price at/after eval (not automatic NO) ---
        if (s == INounsGovernor.ProposalState.Canceled) {
            if (block.number < m.evalBlock) return false;
            bool ready = _closeIfNeeded(m);
            if (!ready) return false; // wait until time-based close

            // decide by current implied price:
            (uint256 pYesBps, bool ok) = _impliedYesBps(m.marketId);
            bool yesWins;
            if (!ok) {
                (yesWins, ok) = _circulatingSideIsYes(m.marketId);
                if (!ok) yesWins = true; // tie defaults to YES
            } else if (pYesBps == 5000) {
                (bool cYes, bool decided) = _circulatingSideIsYes(m.marketId);
                yesWins = decided ? cYes : true;
            } else {
                yesWins = (pYesBps > 5000);
            }

            if (!_hasWinners(m.marketId, yesWins)) {
                bool opp = _hasWinners(m.marketId, !yesWins);
                if (!opp) {
                    _deadSkip(m, proposalId);
                    return false;
                }
                yesWins = !yesWins;
            }

            PAMM.resolve(m.marketId, yesWins);
            m.settled = true;
            emit MarketResolved(proposalId, m.marketId, yesWins);
            if (payTip) _payTip(msg.sender);
            return true;
        }

        // Early NO for veto at any time:
        if (_isNoImmediatelyResolvable(s)) {
            bool outcome = false; // veto → NO
            if (!_hasWinners(m.marketId, outcome)) {
                if (!_hasWinners(m.marketId, !outcome)) {
                    _deadSkip(m, proposalId);
                    return false;
                }
                outcome = !outcome; // let PAMM flip to the winning side
            }
            if (!_closeIfNeeded(m)) return false;
            PAMM.resolve(m.marketId, outcome);
            m.settled = true;
            emit MarketResolved(proposalId, m.marketId, outcome);
            if (payTip) _payTip(msg.sender);
            return true;
        }

        // After end with no objection set: quick terminal check:
        if (block.number >= p.endBlock && p.objectionPeriodEndBlock == 0) {
            if (_isYesTerminalState(s)) {
                bool outcome = true;
                if (!_hasWinners(m.marketId, outcome)) {
                    if (!_hasWinners(m.marketId, !outcome)) {
                        _deadSkip(m, proposalId);
                        return false;
                    }
                    outcome = !outcome;
                }
                if (!_closeIfNeeded(m)) return false;
                PAMM.resolve(m.marketId, outcome);
                m.settled = true;
                emit MarketResolved(proposalId, m.marketId, outcome);
                if (payTip) _payTip(msg.sender);
                return true;
            } else if (s == INounsGovernor.ProposalState.Defeated) {
                bool outcome = false;
                if (!_hasWinners(m.marketId, outcome)) {
                    if (!_hasWinners(m.marketId, !outcome)) {
                        _deadSkip(m, proposalId);
                        return false;
                    }
                    outcome = !outcome;
                }
                if (!_closeIfNeeded(m)) return false;
                PAMM.resolve(m.marketId, outcome);
                m.settled = true;
                emit MarketResolved(proposalId, m.marketId, outcome);
                if (payTip) _payTip(msg.sender);
                return true;
            }
        }

        // Gate by evalBlock (covers objection window or padded end):
        if (block.number >= m.evalBlock) {
            bool yesTerm = _isYesTerminalState(s);
            bool noTerm = _isNoTerminalState(s);

            if (yesTerm || noTerm) {
                bool outcome = yesTerm; // true = YES, false = NO
                if (!_hasWinners(m.marketId, outcome)) {
                    if (!_hasWinners(m.marketId, !outcome)) {
                        _deadSkip(m, proposalId);
                        return false;
                    }
                    outcome = !outcome;
                }
                if (!_closeIfNeeded(m)) return false;
                PAMM.resolve(m.marketId, outcome);
                m.settled = true;
                emit MarketResolved(proposalId, m.marketId, outcome);
                if (payTip) _payTip(msg.sender);
                return true;
            }
        }

        return false;
    }

    function _readyToResolveView(uint256 marketId, uint72 estCloseAt)
        internal
        view
        returns (bool)
    {
        (,,,,,,,, uint72 closeTs, bool canClose,,,,) = PAMM.getMarket(marketId);
        uint72 ref = closeTs != 0 ? closeTs : estCloseAt;
        return (block.timestamp >= ref) || canClose;
    }

    function _closeIfNeeded(MarketInfo storage m) internal returns (bool ready) {
        // Sync on-chain close params:
        (,,,,,,,, uint72 closeTs, bool canClose,,,,) = PAMM.getMarket(m.marketId);
        if (closeTs != 0) m.closeAt = closeTs;

        // Already closed by time:
        if (block.timestamp >= m.closeAt) return true;

        // If not closable early, we are not ready yet:
        if (!canClose) return false;

        // Try to early-close; if it fails, treat as not-ready:
        try PAMM.closeMarket(m.marketId) {
            return true;
        } catch {
            return false;
        }
    }

    function _estimateClose(uint64 evalBlock) internal view returns (uint72) {
        uint256 bn = block.number;
        if (evalBlock <= bn) return uint72(block.timestamp + 1); // next second
        unchecked {
            uint256 delta = evalBlock - bn;
            uint256 est = block.timestamp + delta * secsPerBlock;
            return uint72(est > type(uint72).max ? type(uint72).max : est);
        }
    }

    /* ───────────────────────── helpers / views ─────────────────────────── */

    function canResolveNow(uint256 proposalId)
        public
        view
        returns (bool ready, bool deadMarket, bool yesWins)
    {
        MarketInfo storage m = marketOf[proposalId];
        if (!m.created || m.settled) return (false, false, false);

        INounsGovernor.ProposalState s = GOV.state(proposalId);
        INounsGovernor.ProposalCondensedV3 memory p = GOV.proposalsV3(proposalId);

        if (_isNoImmediatelyResolvable(s)) {
            if (!_readyToResolveView(m.marketId, m.closeAt)) return (false, false, false);
            bool dead = !_hasWinnersView(m.marketId, false) && !_hasWinnersView(m.marketId, true);
            return (true, dead, false);
        }

        if (s == INounsGovernor.ProposalState.Canceled) {
            if (block.number < m.evalBlock) return (false, false, false);
            (,,,,,,,, uint72 closeTs, bool canClose,,,,) = PAMM.getMarket(m.marketId);
            uint72 ref = closeTs != 0 ? closeTs : m.closeAt;
            bool readyNow = (block.timestamp >= ref) || canClose; // can close immediately or already closed by time
            if (!readyNow) return (false, false, false);

            (uint256 bps, bool ok) = _impliedYesBps(m.marketId);
            bool y;
            if (!ok) {
                (y, ok) = _circulatingSideIsYes(m.marketId);
                if (!ok) y = true;
            } else if (bps == 5000) {
                (bool c, bool d) = _circulatingSideIsYes(m.marketId);
                y = d ? c : true;
            } else {
                y = (bps > 5000);
            }
            bool dead = !_hasWinnersView(m.marketId, y) && !_hasWinnersView(m.marketId, !y);
            return (true, dead, y);
        }

        bool afterEndNoObj = (block.number >= p.endBlock && p.objectionPeriodEndBlock == 0);
        bool afterEval = (block.number >= m.evalBlock);

        if (afterEndNoObj || afterEval) {
            bool yes = _isYesTerminalState(s);
            bool no = _isNoTerminalState(s);
            if (yes || no) {
                if (!_readyToResolveView(m.marketId, m.closeAt)) return (false, false, false);
                bool dead = !_hasWinnersView(m.marketId, yes) && !_hasWinnersView(m.marketId, !yes);
                return (true, dead, yes);
            }
        }
        return (false, false, false);
    }

    function marketIdFor(uint256 proposalId) public view returns (uint256 id, bool created) {
        MarketInfo storage m = marketOf[proposalId];
        return (m.marketId, m.created);
    }

    function _isYesTerminalState(INounsGovernor.ProposalState s) internal pure returns (bool) {
        return (
            s == INounsGovernor.ProposalState.Succeeded || s == INounsGovernor.ProposalState.Queued
                || s == INounsGovernor.ProposalState.Executed
                || s == INounsGovernor.ProposalState.Expired
        );
    }

    function _isNoTerminalState(INounsGovernor.ProposalState s) internal pure returns (bool) {
        return
            (s == INounsGovernor.ProposalState.Defeated || s == INounsGovernor.ProposalState.Vetoed);
    }

    function _isNoImmediatelyResolvable(INounsGovernor.ProposalState s)
        internal
        pure
        returns (bool)
    {
        return s == INounsGovernor.ProposalState.Vetoed;
    }

    function _impliedYesBps(uint256 marketId) internal view returns (uint256 bps, bool ok) {
        (,,,,,,,,,,,, uint256 pYes_num, uint256 pYes_den) = PAMM.getMarket(marketId);
        if (pYes_den == 0) return (0, false);
        return ((pYes_num * 10_000) / pYes_den, true);
    }

    function _circulatingSideIsYes(uint256 marketId)
        internal
        view
        returns (bool yesWins, bool decided)
    {
        (uint256 yesSupply, uint256 noSupply,,,,,,,,,,,,) = PAMM.getMarket(marketId);
        uint256 nid = _noId(marketId);
        uint256 yesCirc =
            yesSupply - PAMM.balanceOf(address(PAMM), marketId) - PAMM.balanceOf(ZAMM, marketId);
        uint256 noCirc = noSupply - PAMM.balanceOf(address(PAMM), nid) - PAMM.balanceOf(ZAMM, nid);
        if (yesCirc == noCirc) return (false, false);
        return (yesCirc > noCirc, true);
    }

    function _hasWinners(uint256 marketId, bool yesWins) internal view returns (bool) {
        (uint256 yesSupply, uint256 noSupply,,,,,,,,,,,,) = PAMM.getMarket(marketId);

        uint256 yesCirc = yesSupply - PAMM.balanceOf(address(PAMM), marketId) // exclude PM
            - PAMM.balanceOf(ZAMM, marketId); // exclude ZAMM

        uint256 nid = _noId(marketId);
        uint256 noCirc = noSupply - PAMM.balanceOf(address(PAMM), nid) - PAMM.balanceOf(ZAMM, nid);

        return yesWins ? (yesCirc != 0) : (noCirc != 0);
    }

    function _hasWinnersView(uint256 marketId, bool yesWins) internal view returns (bool) {
        return _hasWinners(marketId, yesWins);
    }

    function _noId(uint256 marketId) internal pure returns (uint256) {
        // Match PAMM.getNoId:
        return uint256(keccak256(abi.encodePacked("PMARKET:NO", marketId)));
    }

    function _descPassVoting(
        uint256 pid,
        uint256 endBlock,
        uint256 objectionEndBlock,
        uint256 evalBlock
    ) internal pure returns (string memory) {
        return string(
            abi.encodePacked(
                "Nouns #",
                _u2s(pid),
                " - Pass Voting? YES if Succeeded/Queued/Executed/Expired at/after voting end (or objection end). ",
                "If Canceled: settle to side with >=50% implied YES probability when the market is closed; ",
                "50/50 ties break by circulating supply, else YES. ",
                "endBlock=",
                _u2s(endBlock),
                ", objectionEndBlock=",
                _u2s(objectionEndBlock),
                ", evalBlock=",
                _u2s(evalBlock)
            )
        );
    }

    function _payTip(address to) internal {
        uint256 tip = tipPerAction;
        if (tip == 0) return;
        uint256 bal = address(this).balance;
        if (bal == 0) return;
        if (tip > bal) tip = bal;
        (bool ok,) = to.call{value: tip}("");
        emit TipPaid(to, ok ? tip : 0);
    }

    function _u2s(uint256 x) internal pure returns (string memory s) {
        if (x == 0) return "0";
        uint256 j = x;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory b = new bytes(len);
        uint256 k = len;
        while (x != 0) {
            k--;
            b[k] = bytes1(uint8(48 + x % 10));
            x /= 10;
        }
        s = string(b);
    }
}
"
    }
  },
  "settings": {
    "remappings": [
      "@solady/=lib/solady/",
      "@soledge/=lib/soledge/",
      "@forge/=lib/forge-std/src/",
      "forge-std/=lib/forge-std/src/",
      "solady/=lib/solady/src/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 9999999
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": true
  }
}}

Tags:
Governance, Voting, Factory|addr:0x1637047f090d6b38d81da0c589d4d8b9d3c7f32e|verified:true|block:23570685|tx:0x485e2943e5a47626074f71d4c9810eb9e9d73899a817ee1d482cddc336fa985a|first_check:1760426277

Submitted on: 2025-10-14 09:17:58

Comments

Log in to comment.

No comments yet.