EthWentUpResolver

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/// -----------------------------
/// Chainlink ETH/USD interface
/// -----------------------------
interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function description() external view returns (string memory);
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
    function getRoundData(uint80 _roundId)
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

/// -----------------------------
/// PAMM interface (minimal)
/// -----------------------------
interface IPAMM {
    struct PMTuning {
        uint32 lateRampStart; // seconds before close to start ramp (0 = off)
        uint16 lateRampMaxBps; // +bps at T
        uint16 extremeMaxBps; // +bps at extremes
    }

    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 setResolverFeeBps(uint16 bps) external;

    function resolve(uint256 marketId, bool outcome) external;

    function balanceOf(address owner, uint256 id) external view returns (uint256);

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

/// --------------------------------------------------------------------------
/// EthWentUpResolver
///  • YES if ETH/USD is strictly higher at the first Chainlink update
///    on/after the market’s scheduled resolve time than it was at market
///    creation time. Ties => NO.
///  • When a market is successfully resolved, pays a fixed tip to the caller
///    and immediately creates the next market (perpetual cadence).
///  • Owner can tune time knobs, seeds, PMTuning, per-resolve tip, and PAMM
///    resolver fee (fee paid from PAMM pot, not from this contract).
/// --------------------------------------------------------------------------
contract EthWentUpResolver {
    /* ────────────────────────────── constants ───────────────────────────── */

    IPAMM public constant PAMM = IPAMM(0x000000000071176401AdA1f2CD7748e28E173FCa);

    // ZAMM singleton used by PAMM to custody seeds/LP
    address constant ZAMM = 0x000000000000040470635EB91b7CE4D132D616eD;

    address constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
    address constant ZSTETH = 0x000000000077B216105413Dc45Dc6F6256577c7B;

    // Chainlink ETH/USD proxy (Ethereum mainnet)
    AggregatorV3Interface public constant FEED =
        AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);

    // For descriptions
    string public constant FEED_ENS = "eth-usd.data.eth";

    // Grid config for resolve times (snap to :00,:10,:20,...)
    uint256 public gridPeriod = 1 seconds;
    uint256 public gridOffset = 0;

    /* ───────────────────────────── owner/config ─────────────────────────── */

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

    address public owner;

    error Unauthorized();

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

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

    /// @dev Staking function into Lido wstETH for ETH inside this contract.
    function stakeETH(uint256 amount) public payable onlyOwner {
        assembly ("memory-safe") {
            pop(call(gas(), WSTETH, amount, codesize(), 0x00, codesize(), 0x00))
        }
    }

    /// @dev Governed exact-in swap to redeem yield from Lido staking.
    function swapExactWSTETHtoETH(address to, uint256 amount, uint256 minOut)
        public
        payable
        onlyOwner
    {
        EthWentUpResolver(payable(ZSTETH)).swapExactWSTETHtoETH(to, amount, minOut);
    }

    /// @dev Governed exact-out swap to redeem yield from Lido staking.
    function swapWSTETHtoExactETH(address to, uint256 exactOut, uint256 maxIn)
        public
        payable
        onlyOwner
    {
        EthWentUpResolver(payable(ZSTETH)).swapWSTETHtoExactETH(to, exactOut, maxIn);
    }

    // Soledge reentrancy guard:
    // (https://github.com/Vectorized/soledge/blob/main/src/utils/ReentrancyGuard.sol)

    error Reentrancy();

    uint256 constant REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268;

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

    uint256 public closeDelay = 5 minutes; // trading open window
    uint256 public resolveDelay = 10 minutes; // scheduled market resolution
    uint256 public maxResolveDelay = 0; // cap to limit cherry-picking
    uint256 public maxStale = 25 hours; // Chainlink liveness guard

    // Keeper incentive (fixed per successful resolve)
    uint256 public tipPerResolve = 0.00033 ether;

    // Initial symmetric seeds for YES/NO
    uint256 public seedYes = 10e18;
    uint256 public seedNo = 10e18;

    // PMTuning defaults (safe, modest)
    IPAMM.PMTuning public pmTuningDefaults =
        IPAMM.PMTuning({lateRampStart: 3 minutes, lateRampMaxBps: 100, extremeMaxBps: 50});

    // --- emergency safety valve ---
    uint256 public emergencyDelay = 90 minutes; // extra buffer after resolve window

    event EmergencyDelaySet(uint256 newDelay);
    event EmergencyResolved(uint256 indexed marketId, bool outcome, uint256 when);

    error EmergencyTooSoon();

    /* ────────────────────────────── market state ────────────────────────── */

    struct Epoch {
        uint72 closeAt;
        uint72 resolveAt;
        uint8 startDecimals;
        uint256 startPrice;
        uint256 startUpdatedAt;
        bool resolved;
    }

    // Active market we expect to resolve next
    uint256 public currentMarketId;

    // Per-market state
    mapping(uint256 => Epoch) public epochs;

    /* ─────────────────────────────── events ─────────────────────────────── */

    event MarketStarted(
        uint256 indexed marketId,
        uint72 closeAt,
        uint72 resolveAt,
        uint256 startPrice,
        uint8 startDecimals,
        uint256 startUpdatedAt,
        uint80 startRoundId,
        string description
    );

    event MarketResolved(
        uint256 indexed marketId,
        bool outcome,
        uint256 endPrice,
        uint8 endDecimals,
        uint256 endUpdatedAt,
        uint80 endRoundId,
        uint256 tipPaid
    );

    event Rolled(uint256 indexed fromMarket, uint256 indexed toMarket);

    event Funded(address indexed from, uint256 amount);
    event TipPerResolveSet(uint256 newTip);
    event TimeParamsSet(
        uint256 closeDelay, uint256 resolveDelay, uint256 maxResolveDelay, uint256 maxStale
    );
    event SeedsSet(uint256 seedYes, uint256 seedNo);
    event PMTuningSet(uint32 lateRampStart, uint16 lateRampMaxBps, uint16 extremeMaxBps);
    event PammResolverFeeSet(uint16 bps);

    event DeadMarketSkipped(uint256 indexed marketId);

    /* ─────────────────────────────── errors ─────────────────────────────── */

    error ActiveMarketLive();
    error NoActiveMarket();
    error AlreadyResolved();
    error ChainlinkAnswerZero();
    error ChainlinkStale();
    error ChainlinkTooSoon(); // latest.updatedAt < resolveAt
    error ResolveWindowExceeded(); // latest.updatedAt > resolveAt + maxResolveDelay
    error ChainlinkInvariant(); // answeredInRound < roundId
    error BadParams();
    error TipPaymentFailed();

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

    constructor() payable {
        IERC20(WSTETH).approve(ZSTETH, type(uint256).max);
        emit OwnershipTransferred(address(0), owner = msg.sender);
    }

    /* ─────────────────────── public funding (tips) ──────────────────────── */

    /// @notice Fund the tip pool used to pay `tipPerResolve` to the first
    ///         successful resolver caller for each market.
    function fundTips() public payable {
        emit Funded(msg.sender, msg.value);
    }

    /// Back-compat alias (same behavior as fundTips)
    function tipResolve() public payable {
        emit Funded(msg.sender, msg.value);
    }

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

    /* ──────────────────── owner configuration knobs ─────────────────────── */

    function setTipPerResolve(uint256 newTip) public payable onlyOwner {
        tipPerResolve = newTip;
        emit TipPerResolveSet(newTip);
    }

    function setEmergencyDelay(uint256 d) public payable onlyOwner {
        if (d < gridPeriod || d > 7 days) revert BadParams();
        emergencyDelay = d;
        emit EmergencyDelaySet(d);
    }

    /// @param _closeDelay  seconds from market creation to trading close
    /// @param _resolveDelay seconds from market creation to scheduled resolution (must exceed closeDelay)
    /// @param _maxResolveDelay max seconds after resolveAt we still accept the first post-resolve update (0 = disable cap)
    /// @param _maxStale  Chainlink liveness guard
    function setTimeParams(
        uint256 _closeDelay,
        uint256 _resolveDelay,
        uint256 _maxResolveDelay,
        uint256 _maxStale
    ) public payable onlyOwner {
        // keep PM tuning consistent with the new close window
        if (pmTuningDefaults.lateRampStart != 0 && pmTuningDefaults.lateRampStart > _closeDelay) {
            revert BadParams();
        }

        // avoid bricking: must allow some staleness window (> 0)
        if (_maxStale == 0) revert BadParams();

        // avoid uint72 truncation when computing closeAt/resolveAt later
        uint256 nowTs = block.timestamp;
        unchecked {
            if (_closeDelay > type(uint72).max - nowTs) revert BadParams();
            if (_resolveDelay > type(uint72).max - nowTs) revert BadParams();
        }

        closeDelay = _closeDelay;
        resolveDelay = _resolveDelay;
        maxResolveDelay = _maxResolveDelay; // 0 = disable cap (OK)
        maxStale = _maxStale;

        emit TimeParamsSet(_closeDelay, _resolveDelay, _maxResolveDelay, _maxStale);
    }

    event GridParamsSet(uint256 period, uint256 offset);

    function setGridParams(uint256 period, uint256 offset) public payable onlyOwner {
        // Basic sanity
        if (period == 0 || period > 1 days) revert BadParams();

        // Keep offset strictly less than period
        uint256 off = offset % period;

        gridPeriod = period;
        gridOffset = off;

        emit GridParamsSet(period, off);
    }

    function setSeeds(uint256 _seedYes, uint256 _seedNo) public payable onlyOwner {
        if ((_seedYes == 0) != (_seedNo == 0)) revert BadParams(); // both zero or both non-zero
        seedYes = _seedYes;
        seedNo = _seedNo;
        emit SeedsSet(_seedYes, _seedNo);
    }

    function setPMTuning(IPAMM.PMTuning calldata t) public payable onlyOwner {
        // Respect PAMM caps/assumptions
        if (t.lateRampMaxBps > 2_000 || t.extremeMaxBps > 2_000) revert BadParams();
        if (t.lateRampStart != 0 && t.lateRampStart > closeDelay) revert BadParams();
        pmTuningDefaults = t;
        emit PMTuningSet(t.lateRampStart, t.lateRampMaxBps, t.extremeMaxBps);
    }

    /// @notice Sets PAMM resolver fee (paid from market pot when PAMM resolves).
    ///         This sets the fee for THIS resolver address in PAMM.
    function setPammResolverFeeBps(uint16 bps) public payable onlyOwner {
        // PAMM enforces bps <= 1000 (10%) internally; we can be lenient here.
        PAMM.setResolverFeeBps(bps);
        emit PammResolverFeeSet(bps);
    }

    /* ───────────────────── lifecycle: start / resolve / roll ───────────── */

    /// @notice Bootstrap the first market (only once, or after a gap).
    function startNewMarket() public payable onlyOwner {
        uint256 _currentMarketId = currentMarketId;
        // Prevent accidental override if the active market hasn’t resolved yet.
        if (_currentMarketId != 0 && !epochs[_currentMarketId].resolved) revert ActiveMarketLive();
        _startNextMarket();
    }

    /// @notice Resolves the current market if ready, pays the keeper tip,
    ///         and immediately starts the next market.
    function resolve() public payable nonReentrant {
        uint256 mId = currentMarketId;
        if (mId == 0) revert NoActiveMarket();

        Epoch storage e = epochs[mId];
        if (e.resolved) revert AlreadyResolved();

        uint256 nextId;
        uint256 paid;

        // ─────────────────────────────────────────────────────────────
        // DEAD-MARKET FAST PATH (skip if no participants, pot == 0)
        // Safe to run once scheduled resolve time has arrived.
        // ─────────────────────────────────────────────────────────────
        if (block.timestamp >= e.resolveAt) {
            (uint256 yesSupply, uint256 noSupply,,,,,,,,,,,,) = PAMM.getMarket(mId);

            uint256 yesId = mId;
            uint256 noId = _noId(mId);

            uint256 yesCirc =
                _circ(yesSupply, PAMM.balanceOf(address(PAMM), yesId), PAMM.balanceOf(ZAMM, yesId));
            uint256 noCirc =
                _circ(noSupply, PAMM.balanceOf(address(PAMM), noId), PAMM.balanceOf(ZAMM, noId));

            if (yesCirc == 0 && noCirc == 0) {
                e.resolved = true;
                if (tipPerResolve != 0) {
                    paid = address(this).balance < tipPerResolve
                        ? address(this).balance
                        : tipPerResolve;
                    if (paid != 0) {
                        (bool ok,) = msg.sender.call{value: paid}("");
                        if (!ok) revert TipPaymentFailed();
                    }
                }
                emit DeadMarketSkipped(mId);
                nextId = _startNextMarket();
                emit Rolled(mId, nextId);
                return;
            }
        }

        // ─────────────────────────────────────────────────────────────
        // NORMAL RESOLUTION PATH (there are participants or a pot)
        // ─────────────────────────────────────────────────────────────

        // 1) Get latest to ensure feed is live and we have at least one post-scheduled observation
        (uint80 rid, int256 latestAns,, uint256 latestUpd, uint80 latestAir) =
            FEED.latestRoundData();
        if (latestAns <= 0) revert ChainlinkAnswerZero();
        if (latestUpd == 0 || latestUpd + maxStale < block.timestamp) revert ChainlinkStale();
        if (latestAir < rid) revert ChainlinkInvariant();
        if (latestUpd < e.resolveAt) revert ChainlinkTooSoon(); // nothing after resolveAt yet

        // 2) Walk back to the FIRST round with updatedAt >= resolveAt (phase-aware)
        uint80 firstRid = rid;
        while (true) {
            (uint80 prev, bool ok) = _prevRoundId(firstRid);
            if (!ok) break;
            try FEED.getRoundData(prev) returns (
                uint80, int256, uint256, uint256 prevUpd, uint80 prevAir
            ) {
                if (prevAir < prev) revert ChainlinkInvariant();
                if (prevUpd == 0 || prevUpd < e.resolveAt) break; // crossed the boundary
                firstRid = prev; // still on/after resolveAt → keep walking back
            } catch {
                // invalid/missing round id (phase edge or gap); stop here and use current firstRid
                break;
            }
        }

        // 3) Resolve using that FIRST post-scheduled round
        (/*roundId*/, latestAns,/*startedAt*/, latestUpd, latestAir) = FEED.getRoundData(firstRid);
        if (latestAns <= 0) revert ChainlinkAnswerZero();
        if (latestAir < firstRid) revert ChainlinkInvariant();
        if (maxResolveDelay != 0 && latestUpd > uint256(e.resolveAt) + maxResolveDelay) {
            revert ResolveWindowExceeded();
        }

        uint8 endDec = FEED.decimals();
        (uint256 sp, uint256 ep) =
            _normalizedPair(e.startPrice, e.startDecimals, uint256(latestAns), endDec);
        bool outcome = ep > sp; // ties => NO

        PAMM.resolve(mId, outcome); // PAMM enforces close
        e.resolved = true;

        uint256 tip = tipPerResolve;
        if (tip != 0) {
            paid = address(this).balance < tip ? address(this).balance : tip;
            if (paid != 0) {
                (bool ok,) = msg.sender.call{value: paid}("");
                if (!ok) revert TipPaymentFailed();
            }
        }

        emit MarketResolved(mId, outcome, uint256(latestAns), endDec, latestUpd, firstRid, paid);

        nextId = _startNextMarket();
        emit Rolled(mId, nextId);
    }

    function emergencyResolve(bool preferYes) public payable onlyOwner nonReentrant {
        uint256 mId = currentMarketId;
        if (mId == 0) revert NoActiveMarket();
        Epoch storage e = epochs[mId];
        if (e.resolved) revert AlreadyResolved();

        // Require we are well past the normal resolution window:
        // base = resolveAt + (maxResolveDelay if set)
        uint256 base = uint256(e.resolveAt) + (maxResolveDelay == 0 ? 0 : maxResolveDelay);
        if (block.timestamp < base + emergencyDelay) revert EmergencyTooSoon();

        // Snapshot market state
        (uint256 yesSupply, uint256 noSupply,,,,,,,,,,,,) = PAMM.getMarket(mId);

        uint256 yesId = mId;
        uint256 noId = _noId(mId);

        // Circulating = total - PM - ZAMM
        uint256 yesCirc =
            _circ(yesSupply, PAMM.balanceOf(address(PAMM), yesId), PAMM.balanceOf(ZAMM, yesId));
        uint256 noCirc =
            _circ(noSupply, PAMM.balanceOf(address(PAMM), noId), PAMM.balanceOf(ZAMM, noId));

        uint256 nextId;

        // Dead-market safe skip (matches your normal fast path)
        if (yesCirc == 0 && noCirc == 0) {
            e.resolved = true;
            emit DeadMarketSkipped(mId);
            nextId = _startNextMarket();
            emit Rolled(mId, nextId);
            return;
        }

        // Choose outcome with PAMM-style auto-flip so winners exist
        bool outcome = preferYes;
        if (outcome && yesCirc == 0 && noCirc > 0) outcome = false;
        if (!outcome && noCirc == 0 && yesCirc > 0) outcome = true;

        // Require winners to exist (handles edge cases)
        uint256 winners = outcome ? yesCirc : noCirc;
        if (winners == 0) revert BadParams();

        // Resolve in PAMM (close already passed long ago)
        PAMM.resolve(mId, outcome);

        // Mark + (optionally) tip + roll
        e.resolved = true;

        uint256 paid;
        if (tipPerResolve != 0) {
            paid = address(this).balance < tipPerResolve ? address(this).balance : tipPerResolve;
            if (paid != 0) {
                (bool ok,) = msg.sender.call{value: paid}("");
                if (!ok) revert TipPaymentFailed();
            }
        }

        emit EmergencyResolved(mId, outcome, block.timestamp);

        nextId = _startNextMarket();
        emit Rolled(mId, nextId);
    }

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

    function currentTimes()
        public
        view
        returns (uint256 marketId, uint72 closeAt, uint72 resolveAt, bool isResolved)
    {
        marketId = currentMarketId;
        if (marketId != 0) {
            Epoch storage e = epochs[marketId];
            closeAt = e.closeAt;
            resolveAt = e.resolveAt;
            isResolved = e.resolved;
        }
    }

    function feedDescription() public view returns (string memory) {
        return FEED.description();
    }

    // ── Helper so keepers know what to do without trial calls ────
    function canResolveNow() public view returns (bool ready, bool shouldSkip) {
        uint256 mId = currentMarketId;
        if (mId == 0) return (false, false);
        Epoch storage e = epochs[mId];
        if (e.resolved) return (false, false);
        if (block.timestamp < e.resolveAt) return (false, false);

        (uint256 yesSupply, uint256 noSupply,,,,,,,,,,,,) = PAMM.getMarket(mId);

        uint256 yesId = mId;
        uint256 noId = _noId(mId);
        uint256 yesCirc =
            _circ(yesSupply, PAMM.balanceOf(address(PAMM), yesId), PAMM.balanceOf(ZAMM, yesId));
        uint256 noCirc =
            _circ(noSupply, PAMM.balanceOf(address(PAMM), noId), PAMM.balanceOf(ZAMM, noId));

        // skip if truly dead (no circ + no pot), otherwise "ready" = try normal resolution
        if (yesCirc == 0 && noCirc == 0) return (true, true);

        // Chainlink gate: we need latest update on/after resolveAt and not too stale.
        (uint80 rid, int256 ans,, uint256 upd, uint80 air) = FEED.latestRoundData();
        if (ans <= 0) return (false, false);
        if (air < rid) return (false, false);
        if (upd == 0 || upd + maxStale < block.timestamp) return (false, false);
        if (upd < e.resolveAt) return (false, false);
        if (maxResolveDelay != 0 && upd > uint256(e.resolveAt) + maxResolveDelay) {
            return (false, false);
        }

        return (true, false);
    }

    /* ─────────────────────────── internals ─────────────────────────────── */

    // Recreate PAMM's NO-id hashing (matches PAMM.getNoId)
    function _noId(uint256 marketId) internal pure returns (uint256) {
        return uint256(keccak256(abi.encodePacked("PMARKET:NO", marketId)));
    }

    // Compute circulating = total - PM - ZAMM (unchecked to avoid underflow reverts)
    function _circ(uint256 total, uint256 pm, uint256 zamm) internal pure returns (uint256 c) {
        unchecked {
            c = total - pm - zamm;
        }
    }

    function _prevRoundId(uint80 rid) internal pure returns (uint80 pr, bool ok) {
        uint16 phase = uint16(rid >> 64);
        uint64 r = uint64(rid);
        if (r > 1) {
            pr = uint80((uint256(phase) << 64) | uint256(r - 1));
            ok = true;
        } else if (phase > 0) {
            pr = uint80((uint256(phase - 1) << 64) | type(uint64).max);
            ok = true;
        }
    }

    function _startNextMarket() internal returns (uint256 newMarketId) {
        // 1) Sample Chainlink for the "start" observation of the new epoch
        (uint80 rid, int256 ans,, uint256 upd, uint80 air) = FEED.latestRoundData();
        if (ans <= 0) revert ChainlinkAnswerZero();
        if (upd == 0 || upd + maxStale < block.timestamp) revert ChainlinkStale();
        if (air < rid) revert ChainlinkInvariant();

        uint8 sDec = FEED.decimals();

        // Sanity: resolveDelay must exceed closeDelay (also enforced in setTimeParams)
        if (resolveDelay <= closeDelay) revert BadParams();

        // 2) Schedule — snap resolveAt to the next grid tick >= now + resolveDelay.
        uint256 p = gridPeriod;
        uint256 off = gridOffset % p;
        uint256 base = block.timestamp + resolveDelay;

        // Round up to next grid boundary with offset
        uint72 resolveAt = uint72(((base + p - 1 - off) / p) * p + off);

        // Quiet period: closeAt = resolveAt - closeDelay
        uint72 closeAt = uint72(resolveAt - closeDelay);

        // If rounding squeezed closeAt into the past, push one grid forward
        if (closeAt <= block.timestamp) {
            resolveAt = uint72(uint256(resolveAt) + p);
            closeAt = uint72(resolveAt - closeDelay);
        }

        // 3) Programmatic description (include resolveAt for unique marketId)
        string memory desc = string(
            abi.encodePacked(
                "ETH price went up vs USD? Resolved by Chainlink (",
                FEED_ENS,
                ") at unix=",
                _u2s(resolveAt),
                " | YES if price > creation snapshot."
            )
        );

        // 4) Create market on PAMM with defaults
        (
            newMarketId, /* noId */
        ) = PAMM.createMarketWithPMTuning(
            desc,
            address(this),
            closeAt,
            /* canClose= */
            false,
            seedYes,
            seedNo,
            pmTuningDefaults
        );

        // 5) Persist epoch data
        Epoch storage e = epochs[newMarketId];
        e.closeAt = closeAt;
        e.resolveAt = resolveAt;
        e.startDecimals = sDec;
        e.startPrice = uint256(ans);
        e.startUpdatedAt = upd;
        e.resolved = false;

        // 6) Set market
        currentMarketId = newMarketId;

        emit MarketStarted(newMarketId, closeAt, resolveAt, uint256(ans), sDec, upd, rid, desc);
    }

    function _normalizedPair(uint256 aVal, uint8 aDec, uint256 bVal, uint8 bDec)
        internal
        pure
        returns (uint256 a, uint256 b)
    {
        if (aDec == bDec) return (aVal, bVal);
        if (aDec < bDec) {
            uint256 factor = 10 ** (bDec - aDec);
            return (aVal * factor, bVal);
        } else {
            uint256 factor = 10 ** (aDec - bDec);
            return (aVal, bVal * factor);
        }
    }

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

/// @dev Minimal ERC20 token interface.
interface IERC20 {
    function approve(address to, uint256 amount) external returns (bool);
}
"
    }
  },
  "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:
Proxy, Swap, Staking, Yield, Upgradeable, Factory, Oracle|addr:0x8a9a5736e573098c9756a0d28035fa4aeacebaf6|verified:true|block:23631298|tx:0xe4eae1347eab37f7b086fd5af32fe43d3272b32099ffb7e20e7553f038c9184b|first_check:1761237157

Submitted on: 2025-10-23 18:32:39

Comments

Log in to comment.

No comments yet.