WethOver4800DoublePokeResolver

Description:

Smart contract deployed on Ethereum with Factory features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

/// @notice Minimal interface to your PredictionAMM.
interface IPredictionMarketMinimal {
    function closeMarket(uint256 marketId) external;
    function resolve(uint256 marketId, bool outcome) external;
    function tradingOpen(uint256 marketId) external view returns (bool);
    function getMarket(uint256 marketId)
        external
        view
        returns (
            uint256 yesSupply,
            uint256 noSupply,
            address resolver,
            bool resolved,
            bool outcome,
            uint256 pot,
            uint256 payoutPerShare,
            string memory desc
        );
}

/// @notice Minimal interface to CheckTheChain (CTC).
interface ICheckTheChain {
    // Use the address overload to avoid relying on symbol registration.
    function checkPrice(address token)
        external
        view
        returns (uint256 price, string memory priceStr);
}

/**
 * @title WethOver4800DoublePokeResolver
 * @notice YES iff WETH price in USDC (6 decimals) from CheckTheChain is strictly > 4800.000000
 *         in TWO snapshots: first ≥ 24h after deploy, second ≥ minDelay after the first.
 *         Only the deployer (owner) may call `poke()`.
 *
 * - Source:    CheckTheChain (spot) at 0x0000000000cDC1F8d393415455E382c30FBc0a84
 * - Asset:     WETH at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
 * - Strike:    4_800_000_000 (4800.000000 USDC, 6 decimals)
 * - Anti-MEV:  requires two offside reads separated by time (minDelay), raising attack cost
 */
contract WethOver4800DoublePokeResolver {
    // ---- Constants / config ----
    ICheckTheChain public constant CTC = ICheckTheChain(0x0000000000cDC1F8d393415455E382c30FBc0a84);
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    uint256 public constant STRIKE_X6 = 4_800_000_000; // 4800.000000 USDC

    // ---- Immutable deps ----
    IPredictionMarketMinimal public immutable PM;
    address public immutable owner; // deployer
    uint256 public immutable earliestTs; // deployedAt + 24h
    uint256 public immutable minDelay; // seconds between snapshot#1 and #2

    // ---- State ----
    uint256 public marketId; // set via link()
    bool public observed1; // first snapshot done
    bool public observed2; // second snapshot done
    uint256 public price1X6; // first snapshot price
    uint256 public price2X6; // second snapshot price
    uint256 public t1; // timestamp of first snapshot
    uint256 public t2; // timestamp of second snapshot

    // ---- Events ----
    event Linked(uint256 indexed marketId);
    event Observed1(uint256 priceX6, string pretty, uint256 atTs, uint256 atBlock);
    event Observed2(uint256 priceX6, string pretty, uint256 atTs, uint256 atBlock);
    event Closed(uint256 indexed marketId, uint256 atTs);
    event Resolved(uint256 indexed marketId, bool yesOutcome);

    // ---- Errors ----
    error AlreadyLinked();
    error NotLinked();
    error WrongResolver();
    error TooEarlyForFirst();
    error TooEarlyForSecond();
    error MarketStillOpen();
    error NotOwner();

    constructor() {
        PM = IPredictionMarketMinimal(0x000000000088B4B43A69f8CDa34d93eD1d6f1431);
        owner = msg.sender;
        earliestTs = block.timestamp + 24 hours;
        minDelay = 180;
    }

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

    /// @notice One-time link; verifies this contract is the market's resolver.
    function link(uint256 _marketId) external {
        if (marketId != 0) revert AlreadyLinked();
        (,, address resolver,,,,,) = PM.getMarket(_marketId);
        if (resolver != address(this)) revert WrongResolver();
        marketId = _marketId;
        emit Linked(_marketId);
    }

    /// @notice Deployer-only: take snapshots and resolve when both are collected.
    /// Call #1 (≥ earliestTs): records price1.
    /// Call #2 (≥ t1 + minDelay): records price2, then closes (if allowed) and resolves.
    function poke() external onlyOwner {
        uint256 id = marketId;
        if (id == 0) revert NotLinked();

        // 1) First observation gate
        if (!observed1) {
            if (block.timestamp < earliestTs) revert TooEarlyForFirst();
            (uint256 px, string memory pretty) = CTC.checkPrice(WETH);
            price1X6 = px;
            observed1 = true;
            t1 = block.timestamp;
            emit Observed1(px, pretty, t1, block.number);
            return; // need a second call later
        }

        // 2) Second observation gate
        if (!observed2) {
            if (block.timestamp < t1 + minDelay) revert TooEarlyForSecond();
            (uint256 px2, string memory pretty2) = CTC.checkPrice(WETH);
            price2X6 = px2;
            observed2 = true;
            t2 = block.timestamp;
            emit Observed2(px2, pretty2, t2, block.number);
            // fallthrough to resolve in same tx
        }

        // 3) Try to close market if still open (works when canClose = true)
        if (PM.tradingOpen(id)) {
            try PM.closeMarket(id) {
                emit Closed(id, block.timestamp);
            } catch {}
        }

        // 4) If still open, can’t resolve yet; snapshots persist — call again later.
        if (PM.tradingOpen(id)) revert MarketStillOpen();

        // 5) Resolve: YES only if BOTH snapshots are strictly above strike.
        bool yes = (price1X6 > STRIKE_X6) && (price2X6 > STRIKE_X6);
        PM.resolve(id, yes);
        emit Resolved(id, yes);
    }

    /// @notice View helper for UIs.
    function preview()
        external
        view
        returns (
            uint256 nowTs,
            uint256 earliestAllowedTs,
            uint256 minDelaySeconds,
            bool isLinked,
            bool gotObs1,
            bool gotObs2,
            uint256 obs1PriceX6,
            uint256 obs2PriceX6,
            uint256 obs1Ts,
            uint256 obs2Ts,
            bool bothAboveStrikeNowIfLive
        )
    {
        nowTs = block.timestamp;
        earliestAllowedTs = earliestTs;
        minDelaySeconds = minDelay;
        isLinked = (marketId != 0);
        gotObs1 = observed1;
        gotObs2 = observed2;
        obs1PriceX6 = price1X6;
        obs2PriceX6 = price2X6;
        obs1Ts = t1;
        obs2Ts = t2;

        if (gotObs2) {
            bothAboveStrikeNowIfLive = (price1X6 > STRIKE_X6) && (price2X6 > STRIKE_X6);
        } else if (gotObs1) {
            (uint256 px,) = CTC.checkPrice(WETH);
            bothAboveStrikeNowIfLive = (price1X6 > STRIKE_X6) && (px > STRIKE_X6);
        } else {
            (uint256 px,) = CTC.checkPrice(WETH);
            bothAboveStrikeNowIfLive = (px > STRIKE_X6) && (px > STRIKE_X6); // same value twice
        }
    }
}
"
    }
  },
  "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:
Factory|addr:0x8528515759a58599219452b4c95bcbc4aa6baf6b|verified:true|block:23535961|tx:0x4c34973fa9a7018716e1a48c77620c46183606c17210e3259b33362d235bff27|first_check:1759994834

Submitted on: 2025-10-09 09:27:14

Comments

Log in to comment.

No comments yet.