Description:
Smart contract deployed on Ethereum with Factory, Oracle features.
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.20;
/// @notice Minimal interface for Chainlink AggregatorV3
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
);
}
/// @notice Minimal surface of the PAMM we call
interface 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 resolve(uint256 marketId, bool outcome) external;
}
/// @title EthWentUpResolver
/// @notice YES if ETH/USD is strictly higher at the first Chainlink update on/after
/// (deploy + 30 minutes) than it was at deployment. Ties => NO.
/// Includes a simple tip pot paid to the first successful resolver caller.
contract EthWentUpResolver {
/* ───────── constants (PoC defaults) ───────── */
// PAMM singleton
IPAMM public constant PAMM = IPAMM(0x000000000071176401AdA1f2CD7748e28E173FCa);
// Chainlink ETH/USD proxy (Ethereum mainnet)
AggregatorV3Interface public constant FEED =
AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// Liveness / timing knobs
uint256 public constant MAX_STALE = 25 hours; // tolerate daily heartbeats
uint256 public constant MAX_RESOLVE_DELAY = 6 hours; // limit cherry-pick window
/* ───────── immutable runtime values ───────── */
address public immutable OWNER;
uint256 public immutable DEPLOYED_AT;
uint256 public immutable RESOLVE_AT; // DEPLOYED_AT + 30 minutes
/* ───────── start observation ───────── */
uint8 public immutable startDecimals;
uint80 public startRoundId;
uint256 public startPrice; // raw feed answer (scale = startDecimals)
uint256 public startUpdatedAt; // unix seconds
/* ───────── end observation (filled at resolve) ───────── */
uint8 public endDecimals;
uint80 public endRoundId;
uint256 public endPrice; // raw feed answer (scale = endDecimals)
uint256 public endUpdatedAt;
/* ───────── PAMM wiring ───────── */
uint256 public marketId; // 0 until linked
bool public resolved;
/* ───────── events ───────── */
event Linked(uint256 indexed marketId);
event Resolved(
uint256 indexed marketId,
bool outcome,
uint256 startPrice,
uint8 startDecimals,
uint256 startUpdatedAt,
uint256 endPrice,
uint8 endDecimals,
uint256 endUpdatedAt,
uint256 tipPaid
);
event Tipped(address indexed from, uint256 amount);
/* ───────── errors ───────── */
error OnlyOwner();
error AlreadyLinked();
error NotLinked();
error TooEarly();
error AlreadyResolved();
error FeedAnswerZero();
error FeedStale();
error FeedTooSoon(); // no post-RESOLVE_AT update yet
error ResolveWindowExceeded(); // end update too far after RESOLVE_AT
error StaleInvariant(); // answeredInRound < roundId
error InvalidResolverInMarket();
error NoTip();
error TipPaymentFailed();
/* ───────── constructor (no args) ───────── */
constructor() payable {
OWNER = msg.sender;
DEPLOYED_AT = block.timestamp;
RESOLVE_AT = DEPLOYED_AT + 30 minutes;
startDecimals = FEED.decimals();
(uint80 _rid, int256 _ans,, uint256 _upd, uint80 _air) = FEED.latestRoundData();
if (_ans <= 0) revert FeedAnswerZero();
if (_upd == 0 || _upd + MAX_STALE < block.timestamp) revert FeedStale();
if (_air < _rid) revert StaleInvariant();
startRoundId = _rid;
startPrice = uint256(_ans);
startUpdatedAt = _upd;
}
/* ───────── wiring ───────── */
/// @notice One-time link to a PAMM market that already sets this contract as resolver.
function link(uint256 _marketId) public payable {
if (msg.sender != OWNER) revert OnlyOwner();
if (marketId != 0) revert AlreadyLinked();
(,, address res,,,,,,,,,,,) = PAMM.getMarket(_marketId);
if (res != address(this)) revert InvalidResolverInMarket();
marketId = _marketId;
emit Linked(_marketId);
}
/* ───────── incentives ───────── */
/// @notice Add ETH tips to pay the first successful resolve() caller.
function tipResolve() public payable {
if (msg.value == 0) revert NoTip();
emit Tipped(msg.sender, msg.value);
}
/* ───────── resolution ───────── */
/// @notice Anyone can call once:
/// - block.timestamp >= RESOLVE_AT,
/// - the feed has posted an update at/after RESOLVE_AT,
/// - and within MAX_RESOLVE_DELAY (limits cherry-picking).
/// On success, pays all accumulated tips to the caller.
function resolve() public payable {
if (marketId == 0) revert NotLinked();
if (resolved) revert AlreadyResolved();
if (block.timestamp < RESOLVE_AT) revert TooEarly();
(uint80 _rid, int256 _ans,, uint256 _upd, uint80 _air) = FEED.latestRoundData();
if (_ans <= 0) revert FeedAnswerZero();
if (_upd == 0 || _upd + MAX_STALE < block.timestamp) revert FeedStale();
if (_air < _rid) revert StaleInvariant();
// Require the first post-target observation (prevents pre-RESOLVE_AT comparisons)
if (_upd < RESOLVE_AT) revert FeedTooSoon();
// Optional upper bound to reduce "waiting for a nicer candle"
if (_upd > RESOLVE_AT + MAX_RESOLVE_DELAY) revert ResolveWindowExceeded();
endDecimals = FEED.decimals();
endRoundId = _rid;
endPrice = uint256(_ans);
endUpdatedAt = _upd;
// Compare with normalization to handle any future decimals change
(uint256 sp, uint256 ep) = _normalizedPair(startPrice, startDecimals, endPrice, endDecimals);
bool outcome = ep > sp; // ties → NO
// PAMM enforces close-time; if called too early wrt market close, this reverts.
PAMM.resolve(marketId, outcome);
resolved = true;
// Pay tips to the first successful resolver
uint256 reward = address(this).balance;
if (reward != 0) {
(bool ok,) = msg.sender.call{value: reward}("");
if (!ok) revert TipPaymentFailed();
}
emit Resolved(
marketId,
outcome,
startPrice,
startDecimals,
startUpdatedAt,
endPrice,
endDecimals,
endUpdatedAt,
reward
);
}
/* ───────── views ───────── */
function secondsUntilResolve() public view returns (uint256) {
return block.timestamp >= RESOLVE_AT ? 0 : (RESOLVE_AT - block.timestamp);
}
function feedDescription() public view returns (string memory) {
return FEED.description();
}
/* ───────── internals ───────── */
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);
}
}
// Accept stray ETH (e.g., self-destruct sends).
receive() external payable {}
}
"
}
},
"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
}
}}
Submitted on: 2025-10-12 12:56:10
Comments
Log in to comment.
No comments yet.