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
}
}}
Submitted on: 2025-10-23 18:32:39
Comments
Log in to comment.
No comments yet.