Address
0xfffffd64effb1f789bfdbb94db682bc764e4ffffCurrent Holdings
$0.00
TXs sent
not counted
First Active
2025-10-29
block 24,880,975
Last Active
322 days ago
block 24,881,485
Funded By
not identified
Net worth historyi
No net-worth snapshots recorded yet
exact matchMoonsolc 0.8.24+commit.e11b9ed9runtime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/*
┌────────────────────────────────────────────────────────────────────────┐
│ │
│ ███ ███ ██████ ██████ ███ ██ │
│ ████ ████ ██ ██ ██ ██ ████ ██ │
│ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ │
│ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ │
│ ██ ██ ██████ ██████ ██ ████ │
│ │
│ │
│ MOON is the heart of a fully decentralized discovery system. It │
│ converts swap activity into reflections for SUN holders while │
│ benefiting every project that pairs with it. │
│ │
│ How it works │
│ • All swaps through official PulseX V2 pairs are taxed 3%. │
│ • MOON uses the tax to buy back the paired token and distributes it │
│ to SUN holders automatically. │
│ • Wallet transfers, liquidity mints/burns as well as swaps on other │
│ DEXs are untaxed. │
│ │
│ Add your token │
│ • Create a V2 liquidity pair with your token and MOON on PulseX: │
│ → Add your token first. │
│ → Add MOON second. (PulseX UI: MOON on the bottom) │
│ • This lets you add/remove liquidity tax free and plugs your │
│ token into the reflection system automatically. │
│ │
│ Why pair with MOON? │
│ • Organic exposure: Natural arbitrage connects your token directly │
│ with SUN holders, building awareness without paid promotion. │
│ • Constant buy pressure: whether users buy or sell through the pair, │
│ the tax is always used to buy your token. │
│ • Enhanced LP rewards: a built-in “pair bonus” leaves part of │
│ swap-back yield in the pool, boosting LP take on top of V2 fees. │
│ │
└────────────────────────────────────────────────────────────────────────┘
*/
/*──────────────────────────── INTERFACES ─────────────────────────────*/
/// @dev Minimal ERC-20 subset used by this contract.
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/// @dev Uniswap V2 Pair subset used by the contract’s swap-backs.
interface IUniswapV2Pair {
function swap(
uint amount0Out,
uint amount1Out,
address to,
bytes calldata data
) external;
}
/// @notice Vault payout used in _emitRay.
interface IVault {
function releaseToMember(address ray, uint256 amount, address member) external;
}
/// @notice All other external calls use precomputed selectors
/*──────────────────────── FLOAT‑96 CODEC (88+8) ────────────────────────*/
/// @title Uint256 <-> Float96 (88-bit mantissa + 8-bit exponent)
/// @notice Compact, lossy encoding for non-negative uint256 values.
/// @dev Layout: packed[95:88]=exponent, packed[87:0]=mantissa.
/// Decodes as (mantissa << exponent). Rounding = nearest, ties-to-even.
library Uint256Float96 {
uint256 private constant MANTISSA_BITS = 88;
uint256 private constant EXPONENT_BITS = 8; // reserved for clarity
uint256 private constant MANTISSA_MASK = (uint256(1) << MANTISSA_BITS) - 1;
uint8 private constant MAX_EXPONENT = 168; // 256 - 88
error BadPackedExponent(uint8 e);
/// @notice Encode a uint256 into a uint96 float-like value.
/// @dev Exact for values < 2^88 (exponent=0). Larger values are rounded
/// to nearest, ties-to-even; may saturate if exponent would overflow.
function encode(uint256 x) internal pure returns (uint96 packed) {
if (x == 0) return 0;
// Find msb(x). After this block: bitLen = floor(log2(x)) + 1.
uint256 msb;
unchecked {
uint256 y = x;
if (y >= 1 << 128) { y >>= 128; msb += 128; }
if (y >= 1 << 64) { y >>= 64; msb += 64; }
if (y >= 1 << 32) { y >>= 32; msb += 32; }
if (y >= 1 << 16) { y >>= 16; msb += 16; }
if (y >= 1 << 8) { y >>= 8; msb += 8; }
if (y >= 1 << 4) { y >>= 4; msb += 4; }
if (y >= 1 << 2) { y >>= 2; msb += 2; }
if (y >= 1 << 1) { msb += 1; }
}
uint256 bitLen = msb + 1;
// Fits entirely in mantissa -> exponent=0, exact store.
if (bitLen <= MANTISSA_BITS) {
return uint96(x);
}
// Normalize into 88-bit mantissa and an exponent in [1..168].
uint256 shift = bitLen - MANTISSA_BITS;
uint256 mant = x >> shift;
// Round to nearest, ties-to-even.
{
uint256 remMask = (uint256(1) << shift) - 1;
uint256 rem = x & remMask;
uint256 half = uint256(1) << (shift - 1);
bool roundUp = (rem > half) || (rem == half && (mant & 1) == 1);
if (roundUp) {
unchecked { mant += 1; }
// If rounding overflowed mantissa to 2^88, renormalize.
if (mant == (uint256(1) << MANTISSA_BITS)) {
mant >>= 1;
shift += 1;
}
}
}
// Saturate if exponent would exceed representable range.
if (shift > MAX_EXPONENT) {
shift = MAX_EXPONENT;
mant = MANTISSA_MASK;
}
// Pack: [ exponent | mantissa ].
return (uint96(uint8(shift)) << uint96(MANTISSA_BITS)) | uint96(mant & MANTISSA_MASK);
}
/// @notice Decode a packed uint96 back into a uint256.
/// @dev Reverts if exponent field is malformed (> MAX_EXPONENT).
function decode(uint96 packed) internal pure returns (uint256 x) {
uint8 exponent = uint8(packed >> MANTISSA_BITS);
if (exponent > MAX_EXPONENT) revert BadPackedExponent(exponent);
uint256 mantissa = uint256(packed) & MANTISSA_MASK;
return mantissa << exponent;
}
}
/*──────────────────────────────── MOON ───────────────────────────────*/
/// @title Moon (MOON)
/// @notice ERC‑20 token with integrated pair‑aware taxation and SUN reflections.
contract Moon is IERC20 {
/*//////////////////////////////////////////////////////////////////////
METADATA
//////////////////////////////////////////////////////////////////////*/
/// @notice Token name.
string public constant name = "Moon";
/// @notice Token symbol.
string public constant symbol = "MOON";
/// @notice Token decimals (fixed at 18).
uint8 public constant decimals = 18;
/*//////////////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////////////*/
/// @dev Fired once when a new ray (paired token) is discovered & cached.
event RayInitialized(address indexed ray);
/*//////////////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////////////*/
/// @notice Total SUN supply credited at deploy (1e18‑scaled).
uint256 public immutable totalSunSupply;
/// @notice Verified Uniswap‑style V2 factory used for pair recognition.
address public immutable factory;
/// @notice SUN ERC‑20 contract linked for distributions.
address public immutable sun;
/// @notice Vault that custodies Rays.
address public immutable vault;
/// @notice Uniswap‑style fee in basis points (e.g., 30 for 0.30%).
uint24 public immutable feeBps;
/// @notice Multiplier used in x*y=k fee math: (FEE_DENOMINATOR - feeBps).
/// @dev Example: 9_970 for a 0.30% fee.
uint24 public immutable feeMul;
/*//////////////////////////////////////////////////////////////////////
CONSTANTS
//////////////////////////////////////////////////////////////////////*/
/// @dev Basis points denominator for AMM fee math (10_000 = 100%).
uint24 public constant FEE_DENOMINATOR = 10_000;
/// @notice Swap tax rate (3%).
uint256 public constant SWAP_TAX = 3e16;
/// @notice Percentage of swap-back left in the pair as an LP bonus (25%).
uint256 public constant PAIR_BONUS = 25e16;
/// @notice Appreciation tax applied to positive value deltas (20%).
uint256 public constant APPRECIATION_TAX = 20e16;
/// @notice Gas stipend for the radiation loop.
uint256 public constant RADIATE_GAS_LIMIT = 220_000;
/// @notice Gas stipend for swapBack.
uint256 public constant SWAPBACK_CALL_GAS = 350_000;
/// @notice Minimum pending MOON before a swap-back is attempted (1 MOON).
uint256 public constant MIN_SWAP_BACK = 1e18;
/// @notice Minimum value emitted per payout (1e-9 MOON).
uint256 public constant MIN_EMIT = 1e9;
/// @notice Minimum SUN tokens for automatic payout (1 SUN)
/// Members below this threshold can still call collect()
uint256 public constant MIN_SUN_RADIATE = 1e18;
/// @notice Maximum distinct tokens to attempt in a single payout iteration.
uint256 public constant MAX_PAYOUT_TOKENS = 6;
/// @notice Index scaling factor (1e32) — “index decimals”.
uint256 public constant INDEX_DECIMALS = 1e32;
/// @notice Conventional burn sink address (irretrievable).
/// @dev Tokens sent here are considered permanently destroyed.
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
/*//////////////////////////////////////////////////////////////////////
ERC-20 STORAGE
//////////////////////////////////////////////////////////////////////*/
/// @notice Current total supply of MOON tokens in existence.
/// @dev This value is initialized at deployment and monotonically decreases
/// as tokens are burned. There is no mechanism to increase supply.
uint256 public override totalSupply;
/// @dev MOON balances.
mapping(address => uint256) internal balances;
/// @dev ERC‑20 allowances: owner => (spender => amount).
mapping(address => mapping(address => uint256)) internal allowances;
/*//////////////////////////////////////////////////////////////////////
CLASSIFICATION BOOKKEEPING
//////////////////////////////////////////////////////////////////////*/
/*
* moonClass:
* 0 = unknown (not yet probed)
* 1 = regular holder or non‑verified contract
* 2 = verified V2 pair that contains MOON (taxed)
*/
mapping(address => uint8) internal moonClass;
/*//////////////////////////////////////////////////////////////////////
SUN MEMBER STATE
//////////////////////////////////////////////////////////////////////*/
/// @dev Member accounting snapshot (first four fields are persisted).
struct Member {
// persisted fields
uint128 pastIndex; // index snapshot (1e32‑scaled)
uint112 uncollected; // uncollected value (MOON‑value terms)
uint8 class; // 0 unknown | 1 eligible | 2 ineligible
uint8 status; // 0 not active | 1 queued for removal | 2 active
// transient helpers (NEVER written to storage)
address self; // member address in memory
uint256 balance; // SUN balance (queried lazily)
}
/// @dev Member records (only the first four fields are read/written).
mapping(address => Member) internal members;
/*//////////////////////////////////////////////////////////////////////
RAY TOKEN STATE
//////////////////////////////////////////////////////////////////////*/
/// @dev Ray = per‑token accounting for a reflection asset paired with MOON.
struct Ray {
// slot 0
address pair; // verified V2 pair that contains MOON & this ray
// slot 1 (tightly packed to 256 bits)
uint112 reserve; // current Vault balance credited as “value bearing”
uint112 pending; // pending MOON earmarked for swap‑back
uint8 swapStatus; // 0 not listed | 2 in swapQueue
uint8 brightStatus; // 0 not listed | 1 pending removal | 2 in brightRays
bool thisZero; // true if MOON is token0 in the pair
// slot 2 (tightly packed to 256 bits)
uint96 quote96; // MOON per token, 1e38‑scaled (compressed)
uint96 candidateQuote96; // intra‑block max quote (compressed)
uint64 updateBlock; // last quote update block
// transient helpers (NEVER written to storage)
address self; // this ray address (for convenience)
uint256 ra; // pair’s ray reserve (pre‑event)
uint256 rm; // pair’s MOON reserve (pre‑event)
uint256 quote; // decoded committed quote (1e38‑scaled)
uint256 candidateQuote; // decoded candidate quote (1e38‑scaled)
uint256 cursor; // place in brightRays list during emitRay
}
/// @dev Per‑token accounting. Public getter exposes persisted fields.
mapping(address => Ray) internal rays;
/*//////////////////////////////////////////////////////////////////////
PAIR STATE
//////////////////////////////////////////////////////////////////////*/
/// @dev Per-pair bookkeeping.
struct Pair {
address ray; // Ray associated with that pair
bool swapLock; // prevents swaps during untaxed mints/burns
}
/// @notice Registered pairs containing MOON, keyed by pair address.
mapping(address => Pair) public pairs;
/*//////////////////////////////////////////////////////////////////////
GLOBAL AGGREGATES (PACKED)
//////////////////////////////////////////////////////////////////////*/
/// @dev [hi 128] = active SUN tokens | [lo 128] = index (1e32‑scaled).
uint256 private packedIndexAndActive;
/// @dev [hi 128] = oath (MOON‑value) | [lo 128] = pool (MOON‑value).
uint256 private packedPoolAndOath;
/// @dev Ephemeral “global” bundle to pass between helpers.
struct GlobalMemory {
uint128 pool; // total value currently held by vault
uint128 oath; // total value owed to active SUN members
uint128 index; // cumulative owed value/share (1e32‑scaled)
uint128 active; // total SUN tokens held by active SUN members
}
/*//////////////////////////////////////////////////////////////////////
DYNAMIC LISTS & CURSORS
//////////////////////////////////////////////////////////////////////*/
/// @dev Tokens whose pairs should be swapped through (pending ≥ MIN_SWAP_BACK).
address[] private swapQueue;
/// @dev Rays ready to be distributed to SUN members.
address[] private brightRays;
/// @dev Active SUN members (round‑robin radiation processor).
address[] private activeMembers;
/// @dev Cursor for `activeMembers` round‑robin.
uint256 public activeMembersCursor;
/*//////////////////////////////////////////////////////////////////////////
PRECOMPUTED SELECTORS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Precomputed selectors for gas savings.
bytes4 constant SEL_BALANCEOF = bytes4(keccak256("balanceOf(address)")); // ERC-20 style balanceOf(owner)
bytes4 constant SEL_V1_tokenAddr = bytes4(keccak256("tokenAddress()")); // Uniswap V1: tokenAddress()
bytes4 constant SEL_TOKEN0 = bytes4(keccak256("token0()")); // token0()
bytes4 constant SEL_TOKEN1 = bytes4(keccak256("token1()")); // token1()
bytes4 constant SEL_BALANCEOF6909 = bytes4(keccak256("balanceOf(address,uint256)")); // ERC‑6909 style balanceOf(owner,id)
bytes4 constant SEL_GETRESERVES = bytes4(keccak256("getReserves()")); // Uniswap V2: getReserves()
bytes4 constant SEL_GETPAIR = bytes4(keccak256("getPair(address,address)")); // Ask factory for pair
/*//////////////////////////////////////////////////////////////////////
REENTRANCY GUARDS
//////////////////////////////////////////////////////////////////////*/
/// @dev Reentrancy guard state: 1 = unlocked, 2 = locked. Never write zero (gas savings)
uint256 private constant _UNLOCKED = 1;
uint256 private constant _LOCKED = 2;
/// @dev Storage slot for the guard (initialized to unlocked).
uint256 private _reentrancyStatus = _UNLOCKED;
/// @notice Reentrancy guard using a dedicated storage slot (sload/sstore).
/// @dev
/// - Uses non-zero states (1↔2) to avoid clearing slot.
/// - Contract-wide single-entry guard: an external `nonReentrant` function
/// cannot `external`-call another `nonReentrant` one in the same tx.
/// - Pattern: check-effects-interactions still applies inside the body.
modifier nonReentrant() {
require (_reentrancyStatus == _UNLOCKED, "Reentrancy");
_reentrancyStatus = _LOCKED;
_;
_reentrancyStatus = _UNLOCKED;
}
/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
/// @notice Initializes Moon: mints full supply to deployer, wires SUN/Vault, and seeds SUN accounting.
/// @dev Scales `supply` and `sunSupply` by 1e18; requires non-zero key addrs and `v2FactoryFee <= 10_000` BPS.
/// Caches factory/fee, marks Moon/SUN/Vault as ineligible for reflections, seeds SUN accounting,
/// enlists sunDeployer in the active rotation, and emits the initial {Transfer}.
/// @param supply Whole-token MOON amount to mint to deployer (unscaled; 18 decimals applied).
/// @param verifiedV2Factory Canonical V2 factory used to recognize taxable pairs.
/// @param v2FactoryFee Factory swap fee in basis points (e.g., 30 = 0.30%).
/// @param sunAddress SUN ERC-20 used for distributions.
/// @param vaultAddress Vault that holds rays.
/// @param sunDeployer Initial SUN holder to seed reflection index.
/// @param sunSupply Whole-token SUN credited to `sunDeployer` (unscaled; 18 decimals applied).
constructor(
uint256 supply,
address verifiedV2Factory,
uint24 v2FactoryFee,
address sunAddress,
address vaultAddress,
address sunDeployer,
uint256 sunSupply
)
{
// ──────────────────────────── Sanity checks ────────────────────────────
// Require all critical external addresses (factory, SUN, Vault) to be set.
require(
verifiedV2Factory != address(0) &&
sunAddress != address(0) &&
sunDeployer != address(0) &&
vaultAddress != address(0),
"zero address"
);
// Ensure AMM fee (in BPS) is not larger than 100% (10_000 BPS).
require(v2FactoryFee <= FEE_DENOMINATOR, "fee too large");
// Bound the unscaled MOON and SUN supplies so that, once multiplied by 1e18,
// they can still be safely packed into the internal uint112 fields used elsewhere.
require(supply <= type(uint112).max / 1e18 &&
sunSupply <= type(uint112).max / 1e18, "Supply too large");
// ──────────────────────── Immutable configuration ───────────────────────
// Save addresses/fee knobs used for pair detection & AMM math.
factory = verifiedV2Factory; // Canonical (taxable) V2 factory
sun = sunAddress; // SUN token used for payouts
vault = vaultAddress; // Custody for rays
totalSupply = supply * 1e18; // Scale to 18 decimals
totalSunSupply = sunSupply * 1e18; // Scale to 18 decimals
feeBps = v2FactoryFee; // e.g., 30 for 0.30%
feeMul = uint24(FEE_DENOMINATOR - feeBps); // e.g., 9_970 for 0.30%
// ─────────────────────────── Mint MOON to deployer ──────────────────────
// Mint the entire MOON supply to the deployer (`msg.sender`).
balances[msg.sender] = totalSupply;
// Cache the deployer as a known class-1 MOON holder (regular holder, not a pair).
// (micro-gas: avoids first-touch classification on their first transfer)
moonClass[msg.sender] = 1;
// Standard ERC-20 Transfer event for the initial mint.
emit Transfer(address(0), msg.sender, totalSupply);
// ────────────────────────── Seed SUN bookkeeping ────────────────────────
// Add the initial SUN holder to the active rotation list.
activeMembers.push(sunDeployer);
// Mark MOON, SUN, and Vault as class-2 for SUN eligibility (ineligible).
// - MOON contract itself (no dividends to the token contract)
// - SUN token contract
// - Vault (custody contract)
members[address(this)].class = 2;
members[sunAddress].class = 2;
members[vaultAddress].class = 2;
// Initialize the global dividend index & active SUN supply:
// index = 0 (no value distributed yet)
// active = totalSunSupply (all initial SUN held by `sunDeployer`)
writeIndexActive(0, uint128(totalSunSupply));
// Initialize the `sunDeployer` member record:
// class = 1 (eligible holder)
// status = 2 (in activeMembers rotation)
writeMember(sunDeployer, 0, 0, 1, 2);
// Initialize DEAD as ineligible for emissions.
// class = 2 (ineligible)
writeMember(DEAD, 0, 0, 2, 0);
}
/*//////////////////////////////////////////////////////////////////////////
ERC-20 — VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Return MOON balance of `owner`.
/// @dev Can revert only when part of a deceptive swap.
function balanceOf(address owner) public view override returns (uint256) {
require(_unlocked(owner), "Moon: Pair locked. Call skim()");
return balances[owner];
}
/// @notice Return allowance from `owner` to `spender`.
function allowance(address owner, address spender) public view override returns (uint256) {
return allowances[owner][spender];
}
/// @notice Returns the number of tokens currently in the swap queue.
function swapQueueLength() external view returns (uint256) {
return swapQueue.length;
}
/// @notice Returns the number of rays currently marked as bright rays.
function brightRaysLength() external view returns (uint256) {
return brightRays.length;
}
/// @notice Returns the number of active SUN members in the rotation.
function activeMembersLength() external view returns (uint256) {
return activeMembers.length;
}
/// @notice Returns the current global pool and oath values (MOON-value terms).
function getPoolOath() external view returns (uint128 pool, uint128 oath) {
(pool, oath) = readPoolOath();
}
/// @notice Returns the current global dividend index and active SUN supply.
function getIndexActive() external view returns (uint128 index, uint128 active) {
(index, active) = readIndexActive();
}
/// @notice Returns the stored member record for a given SUN holder.
/// @dev Exposes the persisted fields of the Member struct from storage.
/// @param a The SUN holder’s address.
/// @return pastIndex Last index checkpoint for this member (1e32-scaled).
/// @return uncollected Accrued but unclaimed MOON-value credit.
/// @return class Eligibility class (1 = eligible, 2 = ineligible).
/// @return status List status code (0 = none, 1 = pending removal, 2 = active).
function getMember(address a)
external
view
returns (uint128 pastIndex, uint112 uncollected, uint8 class, uint8 status)
{
(pastIndex, uncollected, class, status) = readMember(a);
}
/// @notice Returns the stored ray accounting data for a given paired token.
/// @dev Provides both persisted fields and live Vault balance/decoded quotes.
/// @param token The address of the paired token (ray) to inspect.
/// @return pair Verified V2 pair that contains MOON and this token.
/// @return reserve Credited Vault reserve for this token.
/// @return liveVaultBal Current on-chain Vault balance for this token.
/// @return pending Pending MOON earmarked for swap-back.
/// @return swapStatus Swap queue status.
/// @return brightStatus Bright-rays list status.
/// @return thisZero True if MOON is token0 in the pair.
/// @return decodedQuote Committed price quote (MOON per token, 1e38-scaled).
/// @return candidateQuote Candidate intra-block high quote (1e38-scaled).
/// @return updateBlock Block number of last quote update.
function getRay(address token)
external
view
returns (
address pair,
uint112 reserve,
uint256 liveVaultBal,
uint112 pending,
uint8 swapStatus,
uint8 brightStatus,
bool thisZero,
uint256 decodedQuote,
uint256 candidateQuote,
uint64 updateBlock
)
{
Ray memory r;
r.self = token;
(,liveVaultBal) = _safeBalanceOf(token, vault);
(r.pair) = readRay0(r.self);
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
(r.quote, r.candidateQuote, r.updateBlock) = readRay2(r.self);
return (
r.pair,
r.reserve,
liveVaultBal,
r.pending,
r.swapStatus,
r.brightStatus,
r.thisZero,
r.quote,
r.candidateQuote,
r.updateBlock
);
}
/*//////////////////////////////////////////////////////////////////////////
ERC-20 — MUTATIVE (ALLOWANCE & TRANSFERS)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Approve `spender` to transfer up to `amount` MOON on caller’s behalf.
/// @dev Emits an {Approval} event.
function approve(address spender, uint256 amount)
external
override
returns (bool)
{
_approve(msg.sender, spender, amount);
return true;
}
/// @notice Transfer `amount` MOON from msg.sender to `to`.
/// @dev Triggers tax logic when interacting with class-2 V2 pairs.
function transfer(address to, uint256 amount)
external
override
nonReentrant
returns (bool)
{
_transfer(msg.sender, to, amount);
return true;
}
/// @notice Move `amount` MOON from `from` to `to` using caller’s allowance.
/// @dev Deducts allowance and emits {Transfer} & {Approval} events.
function transferFrom(address from, address to, uint256 amount)
external
override
nonReentrant
returns (bool)
{
uint256 currentAllowance = allowances[from][msg.sender];
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
allowances[from][msg.sender] = currentAllowance - amount;
}
emit Approval(from, msg.sender, currentAllowance - amount);
}
_transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////////////////
ERC-20 — INTERNAL MUTATORS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Set `spender`'s allowance over `owner`'s tokens to `amount`.
/// @dev Internal helper, emits an {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal {
allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/// @notice Permanently destroys `amount` of MOON from `from`, reducing the total supply.
/// @dev
/// Behavior:
/// • Updates storage under `unchecked` (safe after guards).
/// • Emits the ERC-20 canonical {Transfer} to the zero address.
/// @param from The address whose balance is reduced (often `address(this)`).
/// @param amount The amount of MOON to destroy (18-decimals).
function _burn(address from, uint256 amount) internal {
require(balances[from] >= amount);
unchecked {
balances[from] -= amount;
totalSupply -= amount;
}
emit Transfer(from, address(0), amount); // ERC-20 canonical burn signal
}
/// @notice Internal untaxed transfer helper — moves MOON without any classification or hooks.
/// @dev
/// • Performs only balance updates and emits the standard {Transfer} event.
/// • Skips all taxation, swap-back, and radiation logic.
/// • Reverts if `from` lacks sufficient balance.
/// @param from Source address.
/// @param to Destination address (may be any non-zero address).
/// @param amount Amount of MOON to move (18-decimals).
function _transferExempt(address from, address to, uint256 amount) internal {
require(to != address(0));
require(balances[from] >= amount);
unchecked {
balances[from] -= amount;
balances[to] += amount;
}
emit Transfer(from, to, amount);
}
/*//////////////////////////////////////////////////////////////////////////
EXTERNAL ENTRYPOINTS (USER / SELF)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Claims caller’s SUN emissions and opportunistically advances maintenance.
/// @dev If eligible, settles the caller via `_emitRay()`, then attempts one swap-back and
/// runs the radiation loop before committing updated aggregates. Calling is optional;
/// emissions accrue automatically.
function collect() external nonReentrant {
// Snapshot caller and their membership record.
Member memory m;
m.self = msg.sender;
(m.pastIndex, m.uncollected, m.class, m.status) = readMember(m.self);
// Snapshot global aggregates (pool/oath/index/active) for this maintenance pass.
(uint128 pool, uint128 oath) = readPoolOath();
(uint128 index, uint128 active) = readIndexActive();
GlobalMemory memory g = GlobalMemory(pool, oath, index, active);
// If the caller is an eligible SUN holder, try to settle their pending value.
if (m.class == 1) {
Ray memory r;
(g, r) = _emitRay(g, r, m);
if (r.self != address(0)) {
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
}
}
// Opportunistic maintenance: try one swap-back (skip none) and advance payouts.
g = _swapBack(g, address(0));
g = _radiate(g);
_commitFromG(g);
}
/// @notice Thin wrapper that is nonReentrant and delegates to the internal implementation.
function manuallyReclassify(address addr) external nonReentrant
returns (uint8 moonC, uint8 sunC)
{
return _manuallyReclassify(addr);
}
/// @notice Manually fund a ray with MOON (adds to that ray’s pending and updates value).
/// @dev External wrapper; simply delegates to the internal implementation.
/// Callable by anyone. Transfer from the caller to this contract is untaxed.
/// Does not require a Ray with V2 MOON pair initialized.
function manuallyFundRay(address ray, uint256 amount) external nonReentrant {
_manuallyFundRay(ray, amount);
}
/// @notice SUN transfer hook that keeps MOON’s accounting correct.
/// @dev Only callable by `sun`. Respects MOON's reentrancy window.
/// Delegates full bookkeeping to `_transferSun()` using
/// pre-transfer balances for fair accrual.
/// @param from SUN holder sending tokens.
/// @param to SUN holder receiving tokens.
/// @param fromBal `from` holder’s SUN balance before the transfer (1e18-scaled).
/// @param toBal `to` holder’s SUN balance before the transfer (1e18-scaled).
/// @param amount SUN amount moved (1e18-scaled).
function transferSun(
address from,
address to,
uint256 fromBal,
uint256 toBal,
uint256 amount
) external {
// Restrict caller to the configured SUN contract.
require(msg.sender == sun, "Can only be called by SUN");
// Respect Moon’s reentrancy window.
require(_reentrancyStatus == _UNLOCKED ,"Reentrancy");
_transferSun(from, to, fromBal, toBal, amount);
}
/*//////////////////////////////////////////////////////////////////////////
TRANSFER ENGINE (MOON TAX LOGIC)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Core MOON transfer routine with pair‑aware taxation.
/// @dev
/// - Applies 3% tax on verified V2 swaps; all other movements are untaxed.
/// - Tax is routed to `this` and attributed to the ray; then one swap‑back
/// and a radiation step are opportunistically executed.
/// - Sending to `DEAD` performs a hard burn and returns early.
/// @param from Sender address.
/// @param to Recipient address (non‑zero).
/// @param amount MOON amount to move.
function _transfer(address from, address to, uint256 amount) internal {
// Basic guards
require(to != address(0), "ERC20: transfer to zero address");
require(balances[from] >= amount, "ERC20: Balance insufficient");
// Burning path (official sink)
if (to == DEAD) {
_burn(from, amount);
return;
}
// Classify: verified‑pair swap (taxed) vs everything else (untaxed)
(bool taxed, Ray memory r) = _transferType(amount, from, to);
if (taxed) {
// Split to recipient and protocol tax
uint256 tax = (amount * SWAP_TAX) / 1e18;
uint256 postTax = amount - tax;
unchecked {
balances[from] -= amount;
balances[to] += postTax;
balances[address(this)] += tax;
}
emit Transfer(from, to, postTax);
emit Transfer(from, address(this), tax);
// Attribute tax to the ray (may enqueue for swap‑back)
_updatePending(r, tax);
// Opportunistic maintenance: one swap‑back (skip current ray) + radiation
(uint128 pool, uint128 oath) = readPoolOath();
(uint128 index, uint128 active) = readIndexActive();
GlobalMemory memory g = GlobalMemory(pool, oath, index, active);
// Performs system maintenance.
g = _swapBack(g, r.self);
g = _radiate(g);
// Defensive re‑read of active supply, then commit
_commitFromG(g);
} else {
unchecked {
balances[from] -= amount;
balances[to] += amount;
}
emit Transfer(from, to, amount);
}
}
/// @notice Classifies an address for MOON‑pair semantics (tax scope).
/// @dev
/// - Returns 2 iff `a` is the canonical V2 pair (from `factory`) that
/// contains MOON; otherwise 1.
/// - On first positive identification, caches ray token + orientation,
/// wires `pairs[a].ray`, and emits {RayInitialized}.
/// - All probes are gas‑capped; failures degrade to class 1.
/// @param a Address to classify.
/// @return c 2 = verified V2 MOON pair; 1 = regular holder/other.
function _getMoonClass(address a) internal returns (uint8 c) {
// Cached
c = moonClass[a];
if (c != 0) return c;
// EOAs are regular
if (a.code.length == 0) { c = 1; moonClass[a] = c; return c; }
// token0()/token1() probe
(bool ok0, address t0) = _probeToken(a, SEL_TOKEN0, 40000);
if (!ok0) { c = 1; moonClass[a] = c; return c; }
(bool ok1, address t1) = _probeToken(a, SEL_TOKEN1, 40000);
if (!ok1) { c = 1; moonClass[a] = c; return c; }
// Must include MOON
if (t0 != address(this) && t1 != address(this)) {
c = 1; moonClass[a] = c; return c;
}
// Factory confirmation
(, address official) = _probeGetPair(t0, t1, 40000);
if (official != a) { c = 1; moonClass[a] = c; return c; }
// Initialize ray state (pair + orientation)
Ray memory r;
r.pair = a;
if (t1 == address(this)) { r.self = t0; r.thisZero = false; } // MOON is token1
else if (t0 == address(this)) { r.self = t1; r.thisZero = true; } // MOON is token0
pairs[r.pair].ray = r.self;
moonClass[a] = 2;
(r.reserve, r.pending, r.swapStatus, r.brightStatus,) = readRay1(r.self);
writeRay0(r.self, a);
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
emit RayInitialized(r.self);
return 2;
}
/// @notice Distinguishes taxed swaps from untaxed LP mints/burns/off‑pair transfers.
/// @dev
/// - If neither side is a verified MOON pair → untaxed.
/// - If any reserve is zero (bootstrap) → untaxed.
/// - Opposite‑sign deltas in (A,M) or unchanged A → swap (taxed).
/// - Otherwise apply a proportionality window (~±20%); outside → swap,
/// inside → LP mint/burn (untaxed) with a temporary `swapLock`.
/// Note: for mint or burn to be properly identified, MOON must be sent second.
/// For burns this happens automatically as long as the other token has a
/// smaller contract address than MOON. Hence MOON's large address.
/// @param amount MOON amount moved.
/// @param from Sender.
/// @param to Recipient.
/// @return taxed True if swap (taxed); false otherwise.
/// @return r Ray context (pair, token, reserves, orientation).
function _transferType(
uint256 amount,
address from,
address to
)
internal
returns (
bool taxed,
Ray memory r
)
{
if (amount == 0) return (false, r);
uint8 classFrom = _getMoonClass(from);
uint8 classTo = _getMoonClass(to);
// Neither touches a verified pair ⇒ untaxed
if (!(classFrom == 2 || classTo == 2)) {
return (false, r);
}
// Resolve ray + orientation + reserves
r.pair = (classFrom == 2) ? from : to;
r.self = pairs[r.pair].ray;
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
(bool okR, uint112 r0, uint112 r1, /*uint32 ts*/) = _probeReserves(r.pair, 40000);
if (!okR) return (true, r); // Broken pair ⇒ taxed
r.ra = r.thisZero ? _cap112(r1) : _cap112(r0);
r.rm = r.thisZero ? _cap112(r0) : _cap112(r1);
// Reconcile any prior swapLock
uint256 bm = balances[r.pair]; // MOON live balance
if (pairs[r.pair].swapLock) {
// Tax the untaxed MOON in the pair
// While swapLock is true, (bm - rm) amount was falsely untaxed.
if (bm > r.rm){
uint256 tax = (bm - r.rm) * SWAP_TAX / 1e18;
_transferExempt(r.pair, address(this), tax);
_updatePending(r, tax);
}
pairs[r.pair].swapLock = false;
}
// Live A balance
(bool okB, uint256 ba) = _safeBalanceOf(r.self, r.pair); // other‑token live bal
if (!okB) return (true, r); // Broken token ⇒ taxed
// Adjust MOON balance directionally after this transfer
if (to == r.pair) bm = _satAdd(bm, amount); // M→A swap or mint
else if (from == r.pair) bm = _satSub(bm, amount); // A→M swap or burn
// Bootstrap guard
if (r.ra == 0 || r.rm == 0) return (false, r);
// Sign test (opposite signs ⇒ swap); unchanged A ⇒ swap
if (!_sameSign(ba, r.ra, bm, r.rm) || ba == r.ra) {
return (true, r);
}
// Proportionality (~±20%) — outside window ⇒ swap
uint256 da = ba > r.ra ? ba - r.ra : r.ra - ba; // Difference between ba and ra
uint256 dm = bm > r.rm ? bm - r.rm : r.rm - bm; // Difference between bm and rm
unchecked {
// Cross terms for the ratio comparison: da/dm = r.ra/r.rm ⇔ da*r.rm = dm*r.ra
uint256 a = da * r.rm;
uint256 b = dm * r.ra;
// Scale once and compare to 6/5 (1.2) and 4/5 (0.8) bounds.
uint256 a5 = a * 5;
// Out of bounds if a/b > 1.2 or a/b < 0.8
if (a5 > b * 6 || a5 < b * 4) return (true, r);
}
// Inside window ⇒ LP mint/burn (untaxed); engage lock until completion
pairs[r.pair].swapLock = true;
}
/// @notice Accrues MOON tax to a ray and enqueues it for swap‑back when thresholded.
/// @dev
/// - `pending` saturates to uint112.
/// - Enqueues once when `pending ≥ MIN_SWAP_BACK`.
/// - If the ray is SUN, burn the tax immediately (do not queue).
/// @param tax Newly collected MOON.
function _updatePending(Ray memory r, uint256 tax) internal {
if (r.self == sun) {_burn(address(this), tax); return;}
r.pending = _cap112(r.pending + tax);
if (r.pending >= MIN_SWAP_BACK && r.swapStatus == 0) {
swapQueue.push(r.self);
r.swapStatus = 2; // in list
}
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
}
/// @notice Read‑only guard used by `balanceOf` to enforce swap‑lock semantics.
/// @dev
/// - While `swapLock` is set, verifies that the (balance - reserve) are the same
/// sign for each token. If not, returns false (caller should `skim`).
/// - Returns true when unlocked or probes fail (fail‑open).
/// Note: SwapLock is only enabled when transferType identifies an LP action (Mint/Burn)
/// This function ensures that the action is really an LP action and not a swap.
/// This function should never return false outside of a malicious swap.
/// @param pair The pair address to check.
/// @return Whether the pair is safe/unlocked for balance queries.
function _unlocked(address pair) internal view returns (bool){
bool swapLock = pairs[pair].swapLock;
if (!swapLock) return true;
Ray memory r;
r.pair = pair;
r.self = pairs[r.pair].ray;
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
// Stored reserves
(bool okR, uint112 r0, uint112 r1, /*uint32 ts*/) = _probeReserves(pair, 40000);
if (!okR) return true;
// Map to (A,M)
r.ra = r.thisZero ? _cap112(r1) : _cap112(r0);
r.rm = r.thisZero ? _cap112(r0) : _cap112(r1);
// Live balances
uint256 bm = balances[pair];
(bool ok, uint256 ba) = _safeBalanceOf(r.self, pair);
if (!ok) return true;
// If signs still match, it's safe; otherwise block
if (_sameSign(ba, r.ra, bm, r.rm)) return true;
return false;
}
/*//////////////////////////////////////////////////////////////////////////
SWAPBACK (QUEUE & SWAP)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Attempts at most one swap-back from the queue, then revalues the ray.
/// @dev
/// - Picks a ray pseudo-randomly, pruning small/invalid entries on the way.
/// - Reads reserves, computes a safe `amountIn` (appreciation-coverage guard
/// + small impact guard), and performs a bounded self-call swap.
/// - On success: decrements `pending`, re-probes reserves, revalues the ray,
/// and updates global aggregates.
/// - On failure: removes the ray from the queue and returns.
/// @param skipRay Optional ray to avoid (the ray in the user’s current taxed swap).
function _swapBack(GlobalMemory memory g, address skipRay)
internal
returns (GlobalMemory memory)
{
// Early exit if nothing to do
uint256 len = swapQueue.length;
if (len == 0) return g;
// Pseudo-random starting point.
uint256 cursor = block.prevrandao + activeMembersCursor;
Ray memory r;
bool found;
// Candidate selection + lazy pruning (bounded attempts)
for (uint256 attempts = 0; attempts < 6; ++attempts) {
len = swapQueue.length;
if (len == 0) return g;
cursor = (cursor + 1) % len;
r.self = swapQueue[cursor];
// Skip the ray currently used by the calling swap
if (r.self == skipRay) {
if (len == 1) return g;
continue;
}
// Load packed ray fields
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
// Cull entries below threshold
if (r.pending < MIN_SWAP_BACK) {
_removeFromList(swapQueue, cursor);
writeRay1(r.self, r.reserve, r.pending, 0, r.brightStatus, r.thisZero);
// If queue emptied, we’re done; otherwise continue with element swapped into `cursor`
len = swapQueue.length;
if (len == 0) return g;
continue;
}
// Resolve pair and cached quotes for downstream logic
(r.quote, r.candidateQuote, r.updateBlock) = readRay2(r.self);
r.pair = readRay0(r.self);
found = true;
break;
}
if (!found) return g;
// Probe reserves (drop from queue if probe fails or 0 reserves)
(bool okR, uint112 r0, uint112 r1, /*uint32 ts*/) = _probeReserves(r.pair, 40000);
if (!okR || r0 == 0 || r1 == 0) {
_removeFromList(swapQueue, cursor);
writeRay1(r.self, r.reserve, r.pending, 0, r.brightStatus, r.thisZero);
return g;
}
r.ra = r.thisZero ? r1 : r0; // other token reserve
r.rm = r.thisZero ? r0 : r1; // MOON reserve
// A) Leave enough pending to cover appreciation tax after the swap
uint256 maxInAppreciation = _maxInForAppreciation(r);
// B) Keep price impact no greater than SWAP_TAX
uint256 maxInFee = (r.rm * SWAP_TAX) / 1e18;
// Combine guards and clamp to availability
uint256 maxIn = maxInAppreciation <= maxInFee ? maxInAppreciation : maxInFee;
uint256 amountIn = r.pending <= maxIn ? r.pending : maxIn;
// Too small → drop from queue and update new price
if (amountIn < MIN_SWAP_BACK) {
_removeFromList(swapQueue, cursor);
r.swapStatus = 0;
g = _updateValue(g, r);
return g;
}
// Execute swap via bounded self-call
bytes memory swapCalldata = abi.encodeWithSelector(
this.__swapWithPair.selector,
r.pair,
r.thisZero,
amountIn,
r.rm,
r.ra
);
(bool ok, ) = address(this).call{gas: SWAPBACK_CALL_GAS}(swapCalldata);
if (ok) {
// Swap succeeded — shrink pending and optionally dequeue
r.pending = r.pending - uint112(amountIn);
if (r.pending < MIN_SWAP_BACK && r.swapStatus == 2) {
_removeFromList(swapQueue, cursor);
r.swapStatus = 0;
}
// Re-probe reserves (best effort)
(okR, r0, r1, /*uint32 ts*/) = _probeReserves(r.pair, 40000);
if (okR) {
r.ra = r.thisZero ? r1 : r0;
r.rm = r.thisZero ? r0 : r1;
} // If probe fails, use old reserves
// Revalue and update global aggregates
g = _updateValue(g, r);
return g;
} else {
// Swap failed — remove from queue
_removeFromList(swapQueue, cursor);
writeRay1(r.self, r.reserve, r.pending, 0, r.brightStatus, r.thisZero);
return g;
}
}
/// @notice Computes the max MOON-in that keeps pending sufficient to pay appreciation tax post-swap.
/// @dev
/// Goal:
/// - Choose `x ≤ r.pending` so that after swapping `x`, the remaining `pending` can still
/// cover the appreciation tax that would be owed if the ray’s MOON‑denominated value
/// increases using the *post‑swap* reserves and quote.
/// Notes:
/// - Considers pair fee, pair bonus (portion left in the pool), and any uncredited tokens
/// already in the Vault for this ray.
/// - Uses saturating math and defensive guards; never reverts. Returns a ceil’d `x`.
/// @param r Ray snapshot (expects `rm`, `ra`, `reserve`, `quote`, `pending`, `self`).
/// @return x Max MOON-in to swap now while preserving coverage.
function _maxInForAppreciation(Ray memory r) internal view returns (uint256 x) {
// Unpack & early guards
uint256 P = r.pending;
if (P == 0) return 0;
uint256 M = r.rm;
uint256 A = r.ra;
if (M == 0 || A == 0) return 0;
// Live Vault balance of token (uncredited portion considered as part of new value)
(, uint256 B) = _safeBalanceOf(r.self, vault);
// Shorthands (locals are cheap; immutables/consts already are)
uint256 WAD = 1e18;
uint256 D = FEE_DENOMINATOR; // 10_000
uint256 F = feeMul; // ≤ 10_000
uint256 K = APPRECIATION_TAX; // 0.20e18
// Pair bonus and complement
uint256 bBonusW = PAIR_BONUS; // 0.25e18
uint256 gammaW = WAD - bBonusW; // compile-time fold; safe
// Pending scaling vs reserves, prior credited value in MOON terms
// pW = ceil(P * WAD / M) — safe to compute directly (P*WAD < 2^256)
uint256 pW;
unchecked { pW = (P * WAD + (M - 1)) / M; }
uint256 vq = _mulDivDown(r.reserve, r.quote, 1e38);
uint256 kBetaUp = _mulDivUp(K, vq, M);
// Constant term with up-bias (unsigned sign+magnitude)
uint256 lhs = _mulDivDown(K, B, A); // cache: used twice
uint256 rhs = _satAdd(kBetaUp, pW);
bool negC = lhs < rhs;
uint256 absC = negC ? (rhs - lhs) : (lhs - rhs);
// Helper pieces for coefficients
uint256 FBoverA = _mulDivDown(F, B, A);
// Fgamma = floor(F * gammaW / WAD) — safe (F*gammaW < 2^256)
uint256 Fgamma;
unchecked { Fgamma = (F * gammaW) / WAD; }
uint256 FBplus = _satAdd(FBoverA, Fgamma);
// Quadratic/linear coefficients (WAD units, up-biased)
// aW = floor(bBonusW * F / D) — safe (product < 2^256)
uint256 aW = (bBonusW * F) / D;
// KFoverD cached once; may saturate inside _mulDivDown (that’s fine)
uint256 KFoverD = _mulDivDown(K, FBplus, D);
aW = _satAdd(aW, KFoverD);
uint256 bWpos = _satAdd(WAD, lhs); // lhs was floor(K*B/A)
bWpos = _satAdd(bWpos, KFoverD); // reuse
uint256 sTot = _satAdd(pW, kBetaUp);
// neg = ceil( ceil(sTot * bBonusW / WAD) * F / D )
uint256 tmp = _mulDivUp(sTot, bBonusW, WAD);
uint256 neg = _mulDivUp(tmp, F, D);
uint256 bW = (neg >= bWpos) ? 0 : (bWpos - neg);
// Solve for t (normalized by WAD), using ceil where needed to preserve up-bias
uint256 tW = 0;
if (aW == 0) {
if (negC) {
tW = _mulDivUp(absC, WAD, bW); // may saturate if bW==0 → OK (clamped later)
}
} else {
bool safe_b2 = (bW == 0) || (bW <= type(uint256).max / bW);
bool safe_a4 = (aW <= type(uint256).max / 4);
bool safe_four = safe_a4 && (absC == 0 || ((aW << 2) <= type(uint256).max / absC));
bool safe_den = (aW <= type(uint256).max / 2);
if (safe_b2 && safe_four && safe_den) {
uint256 b2; unchecked { b2 = bW * bW; }
uint256 four_a_abs_c; unchecked { four_a_abs_c = (aW << 2) * absC; }
uint256 disc;
if (!negC) {
disc = (b2 >= four_a_abs_c) ? (b2 - four_a_abs_c) : 0;
} else {
disc = (type(uint256).max - b2 < four_a_abs_c) ? type(uint256).max : (b2 + four_a_abs_c);
}
uint256 s = _isqrt(disc);
if (s > bW) {
uint256 num; unchecked { num = s - bW; }
uint256 den; unchecked { den = aW << 1; }
tW = _mulDivUp(num, WAD, den);
}
} else {
if (negC) {
tW = _mulDivUp(absC, WAD, bW);
}
}
}
// Convert normalized t to MOON-in, ceil, and clamp to pending
x = _mulDivUp(tW, M, WAD);
if (x > P) x = P;
}
/// @notice Internal router for MOON→token swaps on a verified V2 pair (self-call only).
/// @dev
/// - Transfers `amountIn` MOON into the pair, recomputes the *actual* input,
/// applies factory fee and the pair bonus, and routes output directly to the Vault.
/// - Reverts on zero output to avoid no-op swaps.
/// - Orientation-aware: if MOON is token0, outputs token1; otherwise token0.
/// @param pair V2 pair to swap against.
/// @param thisZero True if MOON is token0 in the pair.
/// @param amountIn Intended MOON input (ledger transfer).
/// @param reserveIn Pre-swap MOON reserve (orientation-correct).
/// @param reserveOut Pre-swap other-token reserve (orientation-correct).
function __swapWithPair(
address pair,
bool thisZero,
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external {
// Self-call only
require(msg.sender == address(this), "Moon: self-call only");
// Disable swapLock
if (pairs[pair].swapLock) {
pairs[pair].swapLock = false;
}
// Router-style ledger move: push MOON to the pair
_transferExempt(address(this), pair, amountIn);
// Compute the actual input observed by the pair
uint256 realIn = _satSub(balances[pair], reserveIn);
// V2 x*y=k with factory fee
uint256 inWithFee = realIn * uint256(feeMul);
uint256 numerator = inWithFee * reserveOut;
uint256 denominator = reserveIn * FEE_DENOMINATOR + inWithFee;
uint256 amountOut = (denominator == 0) ? 0 : (numerator / denominator);
// Apply pair bonus (leave a fraction in the pool)
amountOut = amountOut * (1e18 - PAIR_BONUS) / 1e18;
require(amountOut != 0);
// Execute the swap; send proceeds to the Vault
if (thisZero) {
IUniswapV2Pair(pair).swap(0, amountOut, vault, new bytes(0));
} else {
IUniswapV2Pair(pair).swap(amountOut, 0, vault, new bytes(0));
}
}
/*//////////////////////////////////////////////////////////////////////////
LIGHT ENGINE (RADIATE/EMIT)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Processes active SUN members within a fixed gas stipend and advances payouts.
/// @dev
/// - Round‑robin over `activeMembers` from `activeMembersCursor`.
/// - Removes stale entries (no longer active)
/// - Calls {_emitRay} for active entries, then persists the new cursor.
function _radiate(GlobalMemory memory g)
internal
returns (GlobalMemory memory)
{
// Open gas window and snapshot cursor (monotonic measure: start - gasleft() increases)
uint256 start = gasleft();
uint256 i = activeMembersCursor;
Ray memory r;
Member memory m;
// Iterate while we have gas budget
while (start - gasleft() < RADIATE_GAS_LIMIT) {
uint256 lenNow = activeMembers.length;
if (lenNow == 0) break; // nothing to do
if (i >= lenNow) i = 0; // wrap if list shrank
// Load member snapshot
m.self = activeMembers[i];
(m.pastIndex, m.uncollected, m.class, m.status) = readMember(m.self);
if (m.status != 2) {
// Remove from rotation (swap‑and‑pop); do not advance i (new element sits at i)
_removeFromList(activeMembers, i);
writeMember(m.self, m.pastIndex, m.uncollected, m.class, 0);
} else {
// Active entry: attempt an emission, then advance
(g, r) = _emitRay(g, r, m);
unchecked { ++i; }
}
}
// Persist new credited reserves
if (r.self != address(0)) {
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
}
// Persist cursor for next pass
activeMembersCursor = i;
return g;
}
/// @notice Settles a SUN holder’s MOON‑valued claim by releasing tokens from the Vault.
/// @dev
/// - Accrues the caller’s pending MOON‑value using Δindex and their SUN balance at the start
/// of the interval, folding any previously stored `uncollected`.
/// - Pays out against “bright” ray buckets. A member is typically paid from a single token
/// type per call; if that bucket cannot cover the scaled claim, a new bucket is selected.
/// - Payouts respect current coverage (pool/oath). Accounting advances as if the full
/// unscaled value were settled, while the member actually receives `unscaled × pool/oath`.
/// - Grief‑resistant by design: Whether the token transfer succeeds or not, the global pool
/// is reduced by the realized value removed from the bucket. When the transfer succeeds,
/// the member’s claim (`m.uncollected`) and the global (`g.oath`) are debited proportionally.
/// - List maintenance is O(1). This function only writes ray state immediately when it has to
/// delist; otherwise the caller of {_emitRay} persists the last touched ray with a single
/// packed write after the loop.
/// @param r acts as a reusable cursor: it may persist across calls, and is only
/// written back to storage when the selected bucket is fully drained or delisted.
function _emitRay(GlobalMemory memory g, Ray memory r, Member memory m)
internal
returns (GlobalMemory memory, Ray memory)
{
// Accrue the member’s claim for the current index window.
// Index is monotonic; wrapping is allowed in unchecked math.
uint128 deltaIndex;
unchecked { deltaIndex = g.index - m.pastIndex; }
if (m.class != 1) return (g, r);
if (g.oath == 0) return (g, r);
// Resolve the member’s SUN balance at the beginning of this index interval
if (deltaIndex != 0) {
(/*ok*/, m.balance) = _safeBalanceOf(sun, m.self);
uint256 earned = _divIndex(uint256(m.balance) * uint256(deltaIndex));
m.uncollected = _cap112(earned + uint256(m.uncollected));
}
if (m.uncollected < MIN_EMIT) return (g, r);
// Seed for pseudo‑random bucket order.
uint256 seed;
if (r.self == address(0)) {
seed = uint256(keccak256(abi.encodePacked(block.prevrandao, m.self)));
}
// Settle against at most MAX_PAYOUT_TOKENS buckets to bound work per call.
uint256 iter;
while (m.uncollected >= MIN_EMIT && iter < MAX_PAYOUT_TOKENS) {
unchecked { ++iter; }
if (g.pool < MIN_EMIT || g.oath < MIN_EMIT) break;
// Cursor resets to a new ray only when the current one delists or is too small.
if (r.self == address(0)) {
uint256 len = brightRays.length;
if (len == 0) break;
// Xorshift to mix the seed between selections.
unchecked {
seed ^= (seed << 13);
seed ^= (seed >> 7);
seed ^= (seed << 17);
}
// Choose a bucket and fetch its persisted accounting.
r.cursor = seed % len;
r.self = brightRays[r.cursor];
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
(r.quote, r.candidateQuote, r.updateBlock) = readRay2(r.self);
// Lazy prune: if it is no longer marked “in‑list”, drop it now.
if (r.brightStatus != 2) {
_removeFromList(brightRays, r.cursor);
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, 0, r.thisZero);
r.self = address(0);
continue;
}
}
// Scale the member’s unscaled claim by the live coverage ratio.
// The member receives: paid = unscaled × (pool / oath)
// while accounting burns the full unscaled amount from `m.uncollected` and `g.oath`.
uint256 scaled = _mulDivDown(uint256(m.uncollected), uint256(g.pool), g.oath);
// Convert scaled MOON‑value into whole tokens available from this ray,
// using the committed quote.
uint256 payAmt = (r.quote == 0) ? 0 : _mulDivDown(scaled, 1e38, r.quote);
if (payAmt == 0) {
r.self = address(0);
continue;
}
// Clamp to the credited reserve (never over‑draw a bucket).
if (payAmt > r.reserve) payAmt = r.reserve;
// Compute the value to be removed from the pool at the committed quote.
uint256 tokenValBefore = _mulDivDown(uint256(r.reserve), r.quote, 1e38);
r.reserve = _cap112(_satSub(uint256(r.reserve), payAmt));
uint256 tokenValAfter = _mulDivDown(uint256(r.reserve), r.quote, 1e38);
uint256 rayDeltaVal = tokenValBefore - tokenValAfter; // realized value
// Attempt release from the Vault (bounded gas, tolerant of non‑standard ERC‑20s).
// Zero‑length return is considered success; revert or false is failure.
(bool ok, ) = vault.call{gas: 150_000}(
abi.encodeWithSelector(
IVault.releaseToMember.selector,
r.self,
payAmt,
m.self
)
);
// When the transfer succeeds, burn the member’s unscaled claim and reduce global oath
if (ok) {
uint256 payValUnscaled = (g.pool == 0)
? 0
: _mulDivDown(rayDeltaVal, uint256(g.oath), uint256(g.pool));
if (payValUnscaled > m.uncollected) payValUnscaled = m.uncollected;
g.oath = _satSub128(g.oath, _cap128(payValUnscaled));
m.uncollected = _cap112(_satSub(uint256(m.uncollected), payValUnscaled));
}
// The global pool is debited by the realized value regardless of transfer outcome.
g.pool = _satSub128(g.pool, _cap128(rayDeltaVal));
// If the bucket’s residual value falls below the emission floor, delist it immediately.
// Remove its residual contribution from the pool and clear its bright flag.
if (tokenValAfter < MIN_EMIT) {
_removeFromList(brightRays, r.cursor);
r.brightStatus = 0;
g.pool = _satSub128(g.pool, _cap128(tokenValAfter));
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
r.self = address(0);
}
}
writeMember(m.self, g.index, m.uncollected, m.class, m.status);
return (g, r);
}
/*//////////////////////////////////////////////////////////////////////////
SUN ACCOUNTING & ELIGIBILITY (TRACKING)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Bookkeeps a SUN transfer so index accrual, rotation, and active supply stay correct.
/// @dev
/// - Uses **pre‑transfer** balances (supplied by SUN) to accrue each side fairly at the
/// current index, then snapshots both to close the window.
/// - Maintains the active member list (eligible = balance ≥ MIN_SUN_RADIATE)
/// and updates the global count of eligible SUN (`active`) only when class boundaries are crossed.
/// @param from SUN holder sending tokens.
/// @param to SUN holder receiving tokens.
/// @param fromBal Pre‑transfer SUN balance of `from` (1e18‑scaled).
/// @param toBal Pre‑transfer SUN balance of `to` (1e18‑scaled).
/// @param amount SUN amount moved (1e18‑scaled).
function _transferSun(address from, address to, uint256 fromBal, uint256 toBal, uint256 amount) internal {
// No-op guard: zero amount or self-transfer has no accounting effect.
if (amount == 0 || from == to) return;
Member memory f;
Member memory t;
f.self = from;
f.balance = fromBal;
t.self = to;
t.balance = toBal;
(f.pastIndex, f.uncollected, f.class, f.status) = readMember(f.self);
(t.pastIndex, t.uncollected, t.class, t.status) = readMember(t.self);
// First-touch classification if unknown. Only class-1 holders accrue & rotate.
if (f.class == 0) f = _getSunClass(f);
if (t.class == 0) t = _getSunClass(t);
(uint128 index, uint128 active) = readIndexActive();
// Accrue “uncollected” using the **pre-transfer** balances at this index.
if (f.class == 1) f = _recordUncollected(f, index);
if (t.class == 1) t = _recordUncollected(t, index);
// Snapshot both members to the current index to close this accrual window.
f.pastIndex = index;
t.pastIndex = index;
// Calculate post transfer balances.
unchecked {
f.balance -=amount;
t.balance +=amount;
}
// Maintain active-member rotation: class-1 with balance ≥ MIN_SUN_RADIATE are listed.
if (f.class == 1) f.status = _updateList(activeMembers, f.self, f.status, f.balance >= MIN_SUN_RADIATE);
if (t.class == 1) t.status = _updateList(activeMembers, t.self, t.status, t.balance >= MIN_SUN_RADIATE);
// Update global count of eligible (class-1) SUN tokens used by index math.
_updateActiveToken(f.class, t.class, amount, index, active);
writeMember(f.self, f.pastIndex, f.uncollected, f.class, f.status);
writeMember(t.self, t.pastIndex, t.uncollected, t.class, t.status);
}
/// @notice Classifies an address for SUN eligibility (1 = eligible EOA/holder, 2 = ineligible pool).
/// @dev
/// - EOAs → class 1.
/// - Pool‑like contracts (V2/V3 token0(), Uniswap V1 tokenAddress(), ERC‑6909 balanceOf) → class 2.
/// - All probes are gas‑capped STATICCALLs; failures degrade to class 1.
/// - View‑only helper; does not write to storage.
/// Note: all other contracts can hold SUN and receive reflections.
/// Pair‑like contracts are only excluded to avoid wasting rays that would get trapped in them.
/// @param m Member snapshot with `self` set.
/// @return m Same struct with `class` updated in memory.
function _getSunClass(Member memory m)
internal view
returns (Member memory)
{
address member = m.self;
// EOAs eligible by default
if (member.code.length == 0) { m.class = 1; return m; }
// V2/V3‑style pools expose token0()
(bool ok0, address t0) = _probeToken(member, SEL_TOKEN0, 50000);
if (ok0 && t0 != address(0)) { m.class = 2; return m; }
// Uniswap V1 pairs expose tokenAddress()
(bool ok2, address t1) = _probeToken(member, SEL_V1_tokenAddr, 50000);
if (ok2 && t1 != address(0)) { m.class = 2; return m; }
// ERC‑6909 multi‑token style exposes balanceOf(address,uint256)
(bool ok3, /*uint256*/) = _probeBalanceOf6909(member, address(1), 0, 50000);
if (ok3) { m.class = 2; return m; }
m.class = 1;
return m;
}
/// @notice Accrues a member’s uncollected MOON‑valued claim up to `index`.
/// @dev
/// - If balance > 0 and `index` moved, adds round‑to‑nearest( balance * Δindex / 1e32 ).
/// - Does not change `pastIndex`; caller should snapshot after accrual.
/// - Caps to uint112 to preserve storage packing.
/// @param m Member snapshot (expects `balance` and `pastIndex` set).
/// @param index Current global index (1e32‑scaled).
/// @return m Member with `uncollected` updated (capped).
function _recordUncollected(Member memory m, uint128 index) internal pure returns (Member memory){
if (m.balance != 0 && index != m.pastIndex) {
uint128 delta;
// Index is monotonic; wrapping allowed in unchecked math
unchecked { delta = index - m.pastIndex; }
// Newly earned unscaled value (rounded to nearest via _divIndex)
uint256 add = _divIndex(uint256(m.balance) * uint256(delta));
m.uncollected = _cap112(m.uncollected + add);
}
return m;
}
/// @notice Adjusts global eligible SUN supply when tokens enter/exit class‑1.
/// @dev
/// - Increases `active` when tokens move into class‑1; decreases when they leave it.
/// - No change for 1→1 or 2→2 moves.
/// - Writes `(index, active)` only when `active` changes to save gas.
/// @param classFrom Previous class (1 = eligible).
/// @param classTo New class (1 = eligible).
/// @param amount SUN amount moved (1e18‑scaled).
/// @param index Current global index (persisted alongside `active`).
/// @param active Snapshot of current eligible supply to adjust/persist.
function _updateActiveToken(
uint8 classFrom,
uint8 classTo,
uint256 amount,
uint128 index,
uint128 active
) internal {
uint256 oldActive = active; // avoid unnecessary writes
if (classTo == 1 && classFrom != 1) {
// Entering class‑1
active = _satAdd128(active, uint128(amount));
} else if (classFrom == 1 && classTo != 1) {
// Leaving class‑1
active = _satSub128(active, uint128(amount));
}
// Persist only if changed
if (active != oldActive) {
writeIndexActive(index, active);
}
}
/*//////////////////////////////////////////////////////////////////////////
VALUE & QUOTE TRACKING
//////////////////////////////////////////////////////////////////////////*/
/// @notice Revalues a ray (paired token) against the Vault and updates pool/oath/index.
/// @dev
/// - Refresh credited reserve from the Vault and update the committed quote.
/// - Compute prior value (oldReserve @ oldQuote), then apply appreciation coverage:
/// burn from `r.pending` first, then withhold reserve for any unpaid portion.
/// - Maintain list membership (bright rays) and apply Δvalue to (pool/oath/index).
/// - Persist updated packed fields (reserve/pending/status/orientation).
/// @return g Updated global aggregates.
function _updateValue(
GlobalMemory memory g,
Ray memory r
) internal returns (GlobalMemory memory){
// Previous credited reserve; refresh from Vault (safe read, capped)
uint256 oldReserve = r.reserve;
(bool ok, uint256 balance) = _safeBalanceOf(r.self, vault);
if (ok) r.reserve = _cap112(balance);
// Update quote; capture the previously committed quote for oldVal
uint256 oldQuote;
(r, oldQuote) = _updateQuote(r);
// Prior value at (oldReserve, oldQuote); then apply appreciation coverage
uint256 oldVal = _mulDivDown(oldReserve, oldQuote, 1e38);
uint256 newVal;
(r, newVal) = _applyAppreciationTax(r, oldVal);
// Maintain bright‑ray membership by current value
uint8 oldStatus = r.brightStatus;
r.brightStatus = _updateList(brightRays, r.self, r.brightStatus, newVal > MIN_EMIT);
// Apply Δvalue:
// - If active: positive deltas lift pool, oath and index; negatives reduce pool only.
// - If delisted now: remove its previous contribution immediately.
int256 deltaVal = int256(newVal);
if (r.brightStatus == 2) {
if (oldStatus == 2) deltaVal -= int256(oldVal);
if (deltaVal >= 0) {
uint128 inc = _cap128(uint256(deltaVal));
g.pool = _satAdd128(g.pool, inc);
if (g.active > 0) { // if active = 0 only pool increases
g.oath = _satAdd128(g.oath, inc);
uint256 indexInc = uint256(inc) * 1e32 / g.active;
unchecked { g.index += uint128(indexInc); } // index can wrap
}
} else {
g.pool = _satSub128(g.pool, _cap128(uint256(-deltaVal))); // oath and index unaffected
}
} else if (r.brightStatus == 1 && oldStatus == 2) { // Removed from brightRays
g.pool = _satSub128(g.pool, _cap128(oldVal));
}
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
return g;
}
/// @notice Updates a ray’s committed quote using spot and intra‑block highs.
/// @dev
/// Policy:
/// - If spot > committed: lift immediately (quote=candidate=spot).
/// - Same block: candidate tracks the running max.
/// - New block: commit last block’s high to `quote`, seed `candidate` with current spot.
/// Quotes are 1e38‑scaled MOON‑per‑token and encoded/decoded via Uint256Float96.
/// Note: This system prevents artificial price dips that increase ray emission amount.
/// @return r Ray snapshot with refreshed quote fields.
/// @return prevQuote The committed quote before this update.
function _updateQuote(Ray memory r)
internal
returns (Ray memory, uint256 prevQuote)
{
prevQuote = r.quote;
// 1e38‑scaled spot = rm/ra (if both non‑zero), then encode/decode to align rounding
uint256 spot;
if (r.ra != 0 && r.rm != 0) {
spot = r.rm * 1e38 / r.ra; // No overflow risk
} else return (r, prevQuote);
// Encode and Decode for standardization.
uint96 spot96 = Uint256Float96.encode(spot);
spot = Uint256Float96.decode(spot96);
// Immediate lift on higher spot (or when previous quote is zero)
if (spot > prevQuote || prevQuote == 0) {
r.quote = spot;
r.candidateQuote = spot;
r.updateBlock = uint64(block.number);
writeRay2(r.self, r.quote, r.candidateQuote, r.updateBlock);
return (r, prevQuote);
}
// Same block: keep a running intra‑block max in candidate
if (uint256(r.updateBlock) == block.number) {
if (spot > r.candidateQuote) {
r.candidateQuote = spot;
writeRay2(r.self, r.quote, r.candidateQuote, r.updateBlock);
}
return (r, prevQuote);
}
// New block: commit last block’s high; seed candidate with current spot
r.quote = r.candidateQuote;
r.candidateQuote = spot;
r.updateBlock = uint64(block.number);
writeRay2(r.self, r.quote, r.candidateQuote, r.updateBlock);
return (r, prevQuote);
}
/// @notice Applies appreciation coverage: pay for positive value increases or withhold reserve.
/// @dev
/// - If the ray’s MOON‑denominated value rose vs `oldVal`, charge
/// `due = ceil(APPRECIATION_TAX * (newVal - oldVal) / 1e18)`.
/// - Pay from `r.pending` first (burning MOON). Any unpaid portion is “withheld” by
/// reducing the credited reserve so that unpaid gains are not counted.
/// - Returns the final `newVal` after withholding (uses the committed quote).
function _applyAppreciationTax(
Ray memory r,
uint256 oldVal
)
internal
returns (Ray memory, uint256 newVal)
{
if (r.quote == 0) return(r, 0);
// Tax is only applied to positive delta
newVal = _mulDivDown(uint256(r.reserve), r.quote, 1e38);
if (newVal <= oldVal) return (r, newVal);
uint256 dValue;
unchecked { dValue = newVal - oldVal; }
// Required coverage for this event
uint256 unpaidValue = _mulDivUp(APPRECIATION_TAX, dValue, 1e18);
if (r.pending != 0){
// Burn from pending, clamped by pending and contract balance
uint256 payNow = (unpaidValue <= r.pending) ? unpaidValue : r.pending;
uint256 moonBal = balances[address(this)];
payNow = payNow <= moonBal ? payNow : moonBal;
r.pending = r.pending - uint112(payNow);
_burn(address(this), payNow);
unpaidValue = _satSub(unpaidValue, payNow);
}
// Withhold any unpaid portion by reducing credited reserve
if (unpaidValue != 0) {
uint256 unpaidValueTokens = _mulDivUp(unpaidValue, 1e18, APPRECIATION_TAX);
uint256 unpaidTokens = _mulDivUp(unpaidValueTokens, 1e38, r.quote);
r.reserve = uint112(_satSub(r.reserve, unpaidTokens));
}
// Recompute value from the final credited reserve
newVal = _mulDivDown(r.reserve, r.quote, 1e38);
return (r, newVal);
}
/*//////////////////////////////////////////////////////////////////////////
LIST MANAGEMENT (LIST / DELIST / INIT)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Maintains membership flags for a rotation list (add / mark‑for‑removal).
/// @dev
/// Status codes (shared convention):
/// 0 = not in list
/// 1 = pending removal (will be physically removed later when index is known)
/// 2 = in list
///
/// Policy:
/// - If `shouldBe` and not already 2: ensure presence (push if 0) and set to 2.
/// - If not `shouldBe` and currently 2: mark as 1 (defer actual removal to keep this path O(1)).
/// - Returns the new status to be persisted by the caller.
/// @param list Target storage array.
/// @param self Address whose membership is being updated.
/// @param status Current persisted status for `self`.
/// @param shouldBe Whether `self` should be in the list given current conditions.
/// @return New status code for `self`.
function _updateList(
address[] storage list,
address self,
uint8 status,
bool shouldBe
) internal returns (uint8) {
if (shouldBe && status != 2) {
// Ensure presence when transitioning to "in list"
if (status == 0) {
list.push(self);
}
status = 2;
} else if (!shouldBe && status == 2) {
// Mark for later physical removal
status = 1;
}
return status;
}
/// @notice Removes element at index `i` from `list` in O(1) via swap‑and‑pop (order not preserved).
/// @dev Caller must ensure `i < list.length`. Reverts if array is empty.
/// @param list The storage array to modify.
/// @param i Index to remove.
function _removeFromList(address[] storage list, uint256 i) internal {
uint256 last = list.length - 1;
if (i != last) {
list[i] = list[last];
}
// Manually decrement list.length to avoid clearing slot (gas savings)
assembly {
let lenSlot := list.slot
sstore(lenSlot, sub(sload(lenSlot), 1))
}
}
/*//////////////////////////////////////////////////////////////////////////
MANUAL MAINTENANCE
//////////////////////////////////////////////////////////////////////////*/
/// @notice Re-probes MOON pair detection and SUN eligibility for `addr`, reconciling accounting if eligibility flips.
/// @dev
/// MOON side:
/// - Verified pairs are sticky (remain class 2). Otherwise clear cached class and re‑probe via `_getMoonClass`.
/// SUN side:
/// - If currently not ineligible, recompute advisory class in memory.
/// - If it becomes ineligible (class 2): recycle value back into index and remove from activeMembers list
/// - Returns latest observed classes: `moonC` (1|2) and `sunC` (1|2).
/// @param addr Address to reclassify.
/// @return moonC Latest MOON class after (optional) re-probe (2 = verified pair; 1 = regular).
/// @return sunC Latest SUN eligibility (2 = ineligible; 1 = eligible).
function _manuallyReclassify(address addr)
internal
returns (uint8 moonC, uint8 sunC)
{
/*─────────────────────── MOON CLASS (pair detection) ───────────────────────*/
// Keep verified pairs sticky; otherwise clear and re‑probe.
uint8 cachedMoon = moonClass[addr];
if (cachedMoon < 2) {
moonClass[addr] = 0; // clear only when not a verified pair
moonC = _getMoonClass(addr); // re‑classify (2 = verified V2 pair; else 1)
} else {
moonC = cachedMoon; // leave sticky classification as‑is
}
/*──────────────────────── SUN CLASS (eligibility) ──────────────────────────*/
Member memory m;
m.self = addr;
(m.pastIndex, m.uncollected, m.class, m.status) = readMember(m.self);
uint8 oldClass = m.class;
// Recompute advisory SUN class in memory (no write unless it becomes 2).
if (m.class != 2) {
m = _getSunClass(m);
sunC = m.class;
} else {
// Already ineligible; return current state.
sunC = 2;
}
if (oldClass == 1 && m.class == 2) {
// Update active SUN supply and remove from rotation.
(uint128 index, uint128 active) = readIndexActive();
(,m.balance) = _safeBalanceOf(sun, m.self); // live SUN balance (safe read)
_updateActiveToken(oldClass, 2, m.balance, index, active);
(, active) = readIndexActive();
// Recycle value back into index
m = _recordUncollected(m, index);
if (active != 0){
uint256 indexInc = uint256(m.uncollected) * 1e32 / active;
unchecked { index += uint128(indexInc); } // index can wrap
writeIndexActive(index, active);
} else { // If for some reason active is 0, oath is decremented instead.
(uint128 pool, uint128 oath) = readPoolOath();
writePoolOath(pool, _satSub128(oath, _cap128(uint256(m.uncollected))));
}
m.uncollected = 0;
m.pastIndex = index;
// Delist from activeMembers rotation
m.status = _updateList(activeMembers, m.self, m.status, false);
}
writeMember(m.self, m.pastIndex, m.uncollected, m.class, m.status);
}
/// @notice Adds MOON to a ray’s `pending` and optionally updates value/enqueue for swap-back.
/// @dev
/// - Pulls `amount` from caller to this contract (untaxed internal transfer).
/// - Increases `pending` (saturating). If the ray’s pair is initialized and reserves exist:
/// • Enqueue for swap-back when `pending ≥ MIN_SWAP_BACK` and not already queued.
/// • Update value/quotes and global aggregates, then commit.
/// - If pair is uninitialized or probes fail, only the packed ray fields are updated.
/// @param ray Ray token address.
/// @param amount MOON amount to add to `pending`.
function _manuallyFundRay(address ray, uint256 amount) internal {
// Ray cannot be MOON, SUN, a class 2 pair, or an EOA.
require(ray != address(this)
&& ray.code.length > 0
&& pairs[ray].ray == address(0)
&& ray != sun
&& amount != 0, "Moon: invalid ray or 0 amount");
// Pull MOON into the contract (untaxed internal transfer)
_transferExempt(msg.sender, address(this), amount);
// Initialize local ray context
Ray memory r;
r.self = ray;
(r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero) = readRay1(r.self);
// Add to pending (saturating); queuing decision happens below
r.pending = _cap112(r.pending + amount);
// If the pair is initialized, try to update value and queue if appropriate
bool valueUpdated;
r.pair = readRay0(r.self);
if (r.pair != address(0)) {
(bool okR, uint112 r0, uint112 r1, /*uint32 ts*/) = _probeReserves(r.pair, 40000);
if (okR && r0 != 0 && r1 != 0) {
r.ra = r.thisZero ? r1 : r0; // other token reserve
r.rm = r.thisZero ? r0 : r1; // MOON reserve
// Enqueue for swap-back once threshold is met and not already listed
if (r.pending >= MIN_SWAP_BACK && r.swapStatus == 0) {
swapQueue.push(r.self);
r.swapStatus = 2; // in list
}
// Refresh value + globals, then commit
(uint128 pool, uint128 oath) = readPoolOath();
(uint128 index, uint128 active) = readIndexActive();
GlobalMemory memory g = GlobalMemory(pool, oath, index, active);
(r.quote, r.candidateQuote, r.updateBlock) = readRay2(r.self);
g = _updateValue(g, r);
valueUpdated = true;
// Opportunistic maintenance: try one swap-back (skip none) and advance payouts.
g = _swapBack(g, address(0));
g = _radiate(g);
_commitFromG(g);
}
}
if (!valueUpdated) {
writeRay1(r.self, r.reserve, r.pending, r.swapStatus, r.brightStatus, r.thisZero);
}
}
/*//////////////////////////////////////////////////////////////////////////
STORAGE HELPERS
//////////////////////////////////////////////////////////////////////////*/
// Bit masks
uint256 private constant _MASK_64 = (uint256(1) << 64) - 1;
uint256 private constant _MASK_96 = (uint256(1) << 96) - 1;
uint256 private constant _MASK_112 = (uint256(1) << 112) - 1;
uint256 private constant _MASK_128 = (uint256(1) << 128) - 1;
/// Base slot of: mapping(address => Member) internal members;
function _membersBase() private pure returns (bytes32 s) {
assembly ("memory-safe") { s := members.slot }
}
/// Base slot of: mapping(address => Ray) internal rays;
function _raysBase() private pure returns (bytes32 s) {
assembly ("memory-safe") { s := rays.slot }
}
/// Slot of: uint256 private packedIndexAndActive; // [hi 128]=active | [lo 128]=index
function _idxActSlot() private pure returns (bytes32 s) {
assembly ("memory-safe") { s := packedIndexAndActive.slot }
}
/// Slot of: uint256 private packedPoolAndOath; // [hi 128]=oath | [lo 128]=pool
/// (Kept the function name your helpers already reference: _poolOathSlot)
function _poolOathSlot() private pure returns (bytes32 s) {
assembly ("memory-safe") { s := packedPoolAndOath.slot }
}
/// @dev keccak256(abi.encode(key, base))
function _map(bytes32 base, address key) private pure returns (bytes32 slot) {
assembly ("memory-safe") {
mstore(0x00, key)
mstore(0x20, base)
slot := keccak256(0x00, 0x40)
}
}
/// @dev Computes the base storage slot for `members[who]` (slot0 of the Member struct).
function _memberSlot0(address who) private pure returns (bytes32 s0) {
s0 = _map(_membersBase(), who);
}
/// @dev Computes the base storage slot for `rays[token]` (slot0 of the Ray struct).
function _raySlot0(address token) private pure returns (bytes32 s0) {
s0 = _map(_raysBase(), token);
}
/// @dev Computes slot1 of `rays[token]` (holds reserve/pending/status/orientation fields).
function _raySlot1(address token) private pure returns (bytes32 s1) {
bytes32 s0 = _raySlot0(token);
unchecked { s1 = bytes32(uint256(s0) + 1); }
}
/// @dev Computes slot2 of `rays[token]` (holds packed quote96/candidateQuote96/updateBlock).
function _raySlot2(address token) private pure returns (bytes32 s2) {
bytes32 s0 = _raySlot0(token);
unchecked { s2 = bytes32(uint256(s0) + 2); }
}
/// @notice Read (index, active) from `packedIndexAndActive`.
function readIndexActive() internal view returns (uint128 index, uint128 active) {
bytes32 s = _idxActSlot();
uint256 w; assembly ("memory-safe") { w := sload(s) }
index = uint128(w);
active = uint128(w >> 128);
}
/// @notice Write (index, active) to `packedIndexAndActive`.
function writeIndexActive(uint128 index, uint128 active) internal {
bytes32 s = _idxActSlot();
uint256 nw = (uint256(active) << 128) | uint256(index);
assembly ("memory-safe") { sstore(s, nw) }
}
/// @notice Read (pool, oath) from `packedPoolAndOath`.
function readPoolOath() internal view returns (uint128 pool, uint128 oath) {
bytes32 s = _poolOathSlot();
uint256 w; assembly ("memory-safe") { w := sload(s) }
pool = uint128(w);
oath = uint128(w >> 128);
}
/// @notice Write (pool, oath) to `packedPoolAndOath`.
function writePoolOath(uint128 pool, uint128 oath) internal {
bytes32 s = _poolOathSlot();
uint256 nw = (uint256(oath) << 128) | uint256(pool);
assembly ("memory-safe") { sstore(s, nw) }
}
/// @notice Read members[who].slot0 → (pastIndex:128, uncollected:112, class:8, status:8).
/// @dev Single-slot layout. All bits are densely packed as documented above.
function readMember(address who)
internal
view
returns (uint128 pastIndex, uint112 uncollected, uint8 class, uint8 status)
{
bytes32 s0 = _memberSlot0(who);
uint256 w; assembly ("memory-safe") { w := sload(s0) }
pastIndex = uint128( w & _MASK_128 );
uncollected = uint112((w >> 128) & _MASK_112 );
class = uint8 ((w >> 240) & 0xFF);
status = uint8 ((w >> 248) & 0xFF);
}
/// @notice Write members[who].slot0 ← (pastIndex:128, uncollected:112, class:8, status:8).
/// @dev Densely packs fields and overwrites the entire word.
function writeMember(address who, uint128 pastIndex, uint112 uncollected, uint8 class, uint8 status) internal {
bytes32 s0 = _memberSlot0(who);
uint256 nw =
(uint256(pastIndex) & _MASK_128) |
((uint256(uncollected) & _MASK_112) << 128) |
((uint256(class) & 0xFF) << 240) |
((uint256(status) & 0xFF) << 248);
assembly ("memory-safe") { sstore(s0, nw) }
}
/// @notice Read rays[token].slot0 → (pair).
function readRay0(address token)
internal
view
returns (address pair)
{
bytes32 s0 = _raySlot0(token);
uint256 w; assembly ("memory-safe") { w := sload(s0) }
pair = address(uint160(w));
}
/// @notice Write rays[token].slot0 ← (pair).
function writeRay0(address token, address pair) internal {
bytes32 s0 = _raySlot0(token);
uint256 nw = uint256(uint160(pair));
assembly ("memory-safe") { sstore(s0, nw) }
}
/// @notice Read rays[token].slot1 → (reserve:uint112, pending:uint112, swapStatus:uint8, brightStatus:uint8, thisZero:bool).
/// @dev The topmost 8 bits [248..255] are reserved and ignored.
function readRay1(address token)
internal
view
returns (uint112 reserve, uint112 pending, uint8 swapStatus, uint8 brightStatus, bool thisZero)
{
bytes32 s1 = _raySlot1(token);
uint256 w; assembly ("memory-safe") { w := sload(s1) }
reserve = uint112( w & _MASK_112 );
pending = uint112((w >> 112) & _MASK_112 );
swapStatus = uint8 ((w >> 224) & 0xFF);
brightStatus = uint8 ((w >> 232) & 0xFF);
thisZero = ((w >> 240) & 0xFF) != 0;
}
/// @notice Write rays[token].slot1 ← (reserve:uint112, pending:uint112, swapStatus:uint8, brightStatus:uint8, thisZero:bool).
/// @dev Preserves the topmost reserved 8-bit lane [248..255]; only updates bits [0..247].
function writeRay1(
address token,
uint112 reserve,
uint112 pending,
uint8 swapStatus,
uint8 brightStatus,
bool thisZero
) internal {
bytes32 s1 = _raySlot1(token);
// Low 248 bits (everything we control)
uint256 lw =
(uint256(reserve) & _MASK_112) |
((uint256(pending) & _MASK_112) << 112) |
((uint256(swapStatus) & 0xFF) << 224) |
((uint256(brightStatus) & 0xFF) << 232) |
((thisZero ? uint256(1) : uint256(0)) << 240);
// Preserve the reserved top 8 bits [248..255] from the existing word.
uint256 oldW; assembly ("memory-safe") { oldW := sload(s1) }
uint256 keepTop8 = oldW & ~((uint256(1) << 248) - 1);
uint256 nw = keepTop8 | lw;
assembly ("memory-safe") { sstore(s1, nw) }
}
/// @notice Read rays[token].slot2 → (quote:uint256, candidateQuote:uint256, updateBlock:uint64).
/// @dev Decodes Float96-packed quote fields into uint256 for call‑sites.
function readRay2(address token)
internal
view
returns (uint256 quote, uint256 candidateQuote, uint64 updateBlock)
{
bytes32 s2 = _raySlot2(token);
uint256 w; assembly ("memory-safe") { w := sload(s2) }
uint96 qPacked = uint96(w & _MASK_96);
uint96 cPacked = uint96((w >> 96) & _MASK_96);
updateBlock = uint64((w >> 192) & _MASK_64);
quote = Uint256Float96.decode(qPacked);
candidateQuote = Uint256Float96.decode(cPacked);
}
/// @notice Write rays[token].slot2 ← (quote:uint256, candidateQuote:uint256, updateBlock:uint64).
/// @dev Encodes the uint256 quote fields into Float96 before packing.
function writeRay2(address token, uint256 quote, uint256 candidateQuote, uint64 updateBlock) internal {
bytes32 s2 = _raySlot2(token);
uint96 qPacked = Uint256Float96.encode(quote);
uint96 cPacked = Uint256Float96.encode(candidateQuote);
uint256 nw = uint256(qPacked)
| (uint256(cPacked) << 96)
| (uint256(updateBlock) << 192);
assembly ("memory-safe") { sstore(s2, nw) }
}
/*//////////////////////////////////////////////////////////////////////////
SAFE STATIC PROBE SYSTEM
////////////////////////////////////////////////////////////////////////////*/
/// @notice Probes a target contract for an `address` return value (e.g., token0/token1).
/// @dev
/// Behavior
/// • Performs a gas‑capped `STATICCALL` via {_staticProbeRaw} expecting a 32‑byte ABI word.
/// • On success, decodes the low 20 bytes as an `address`.
/// • On failure (revert, short/empty return, etc.), returns `(false, address(0))`.
/// Notes
/// • This helper is used for various “what token is this?” checks, and never reverts.
/// • It is safe against non‑standard contracts: all decoding is bounds‑checked.
/// @param target Contract to probe.
/// @param selector 4‑byte function selector (e.g., `SEL_TOKEN0`, `SEL_TOKEN1`, `SEL_V1_tokenAddr`).
/// @param gasCap Gas limit to forward to the probe (keeps failures cheap/deterministic).
/// @return ok True if the call succeeded and returned at least 32 bytes.
/// @return token Decoded address (low 20 bytes of the first 32‑byte word) or zero on failure.
function _probeToken(address target, bytes4 selector, uint256 gasCap)
internal
view
returns (bool ok, address token)
{
bytes memory out;
(ok, out) = _staticProbeRaw(target, selector, 0, 0, 0, gasCap, 32);
if (!ok) return (false, address(0));
// ABI returns address left‑padded in 32 bytes — take the low 20 bytes.
assembly ("memory-safe") {
token := and(mload(add(out, 32)), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/// @notice Reads Uniswap‑V2‑style reserves from a pair via `getReserves()`.
/// @dev
/// Behavior
/// • Calls `pair.getReserves()` via a gas‑capped `STATICCALL`.
/// • Expects exactly 96 bytes: (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast).
/// • Returns `(false, 0, 0, 0)` on failure.
/// • On success, saturates each reserve to `uint112` to guard against maliciously wide values.
/// Security & Robustness
/// • Never reverts; unsuitable/misbehaving pairs simply yield `ok=false`.
/// • Saturating downcasts ensure internal math cannot overflow reserve‑sized lanes.
/// @param pair The V2‑style pair to probe.
/// @param gasCap Gas limit to forward to the probe.
/// @return ok True when a 96‑byte response was obtained.
/// @return r0 Reserve0 (saturated to uint112).
/// @return r1 Reserve1 (saturated to uint112).
/// @return ts Pair’s `blockTimestampLast` (uint32).
function _probeReserves(address pair, uint256 gasCap)
internal
view
returns (bool ok, uint112 r0, uint112 r1, uint32 ts)
{
bytes memory out;
(ok, out) = _staticProbeRaw(pair, SEL_GETRESERVES, 0, 0, 0, gasCap, 96);
if (!ok) return (false, 0, 0, 0);
// Decode three 32‑byte words; only low bits of the first two are used post‑saturation.
uint256 a;
uint256 b;
uint256 c;
assembly ("memory-safe") {
let ptr := add(out, 32)
a := mload(ptr)
b := mload(add(ptr, 32))
c := mload(add(ptr, 64))
}
// Saturating downcasts (guard against malicious wide values).
r0 = a > type(uint112).max ? type(uint112).max : uint112(a);
r1 = b > type(uint112).max ? type(uint112).max : uint112(b);
ts = uint32(c);
}
/// @notice Asks a canonical factory for a pair address via `getPair(tokenA, tokenB)`.
/// @dev
/// Behavior
/// • Performs a gas‑capped `STATICCALL` to the factory’s `getPair(a,b)`, expecting 32 bytes.
/// • On success, decodes and returns the low 20 bytes as an `address`.
/// • On failure, returns `(false, address(0))`.
/// Notes
/// • This is used to verify that an observed pair is the official one constructed by `factory`.
/// @param tokenA First token of the pair (order usually irrelevant in factories).
/// @param tokenB Second token of the pair.
/// @param gasCap Gas limit to forward to the probe.
/// @return ok True when a 32‑byte response was obtained.
/// @return pair The decoded pair address (low 20 bytes) or zero on failure.
function _probeGetPair(address tokenA, address tokenB, uint256 gasCap)
internal
view
returns (bool ok, address pair)
{
bytes memory out;
(ok, out) = _staticProbeRaw(
factory,
SEL_GETPAIR,
uint256(uint160(tokenA)),
uint256(uint160(tokenB)),
2,
gasCap,
32
);
if (!ok) return (false, address(0));
// ABI returns address left‑padded in 32 bytes — take the low 20 bytes.
assembly ("memory-safe") {
pair := and(mload(add(out, 32)), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/// @notice Safe, gas‑capped `balanceOf(owner)` probe against an ERC‑20‑like `token`.
/// @dev
/// Behavior
/// • Calls `token.balanceOf(owner)` as a gas‑capped `STATICCALL`.
/// • On success, decodes a 32‑byte word and returns it, clamped to `uint112` (reserve‑style bound).
/// • On failure, returns `(false, 0)`.
/// Notes
/// • Some non‑standard tokens return no/short data or revert; all cases are treated as failure.
/// • Clamping to `uint112` preserves packing assumptions used elsewhere in this contract.
/// @param token ERC‑20‑style token to query.
/// @param owner Account whose balance is being probed.
/// @return ok True if the probe returned at least 32 bytes.
/// @return bal Decoded balance (clamped to `uint112`) or 0 on failure.
function _safeBalanceOf(address token, address owner)
internal
view
returns (bool ok, uint256 bal)
{
bytes memory out;
(ok, out) = _staticProbeRaw(
token,
SEL_BALANCEOF,
uint256(uint160(owner)),
0,
1,
30000,
32
);
if (!ok) return (false, 0);
assembly ("memory-safe") { bal := mload(add(out, 32)) }
if (bal > type(uint112).max) bal = type(uint112).max;
}
/// @notice ERC‑6909‑style `balanceOf(owner, id)` probe for multi‑token pools.
/// @dev
/// Behavior
/// • Calls `token.balanceOf(owner, id)` via gas‑capped `STATICCALL`.
/// • On success, decodes a 32‑byte word and returns it.
/// • On failure, returns `(false, 0)`.
/// Notes
/// • Used purely as a detection probe for “multi‑asset” style pool contracts.
/// • Never reverts; unsuitable targets just yield `ok=false`.
/// @param token Multi‑token contract to query.
/// @param owner Account whose balance is being probed.
/// @param id Token ID within the multi‑token contract.
/// @param gasCap Gas limit to forward to the probe.
/// @return ok True when a 32‑byte response was obtained.
/// @return bal Decoded balance or 0 on failure.
function _probeBalanceOf6909(address token, address owner, uint256 id, uint256 gasCap)
internal
view
returns (bool ok, uint256 bal)
{
bytes memory out;
(ok, out) = _staticProbeRaw(
token,
SEL_BALANCEOF6909,
uint256(uint160(owner)),
id,
2,
gasCap,
32
);
if (!ok) return (false, 0);
assembly ("memory-safe") { bal := mload(add(out, 32)) }
}
/// @notice Uniswap V1‑style `tokenAddress()` probe (returns the ERC‑20 token held by the V1 pair).
/// @dev Thin wrapper over {_probeToken} using `SEL_V1_tokenAddr`. See {_probeToken} for semantics.
/// @param v1Pair The V1‑style pair to query.
/// @param gasCap Gas limit to forward to the probe.
/// @return ok True if a 32‑byte response was obtained.
/// @return token Decoded token address or zero on failure.
function _probeV1TokenAddress(address v1Pair, uint256 gasCap)
internal
view
returns (bool ok, address token)
{
return _probeToken(v1Pair, SEL_V1_tokenAddr, gasCap);
}
/// @notice Low‑level, gas‑capped `STATICCALL` helper with flexible ABI encoding and length checks.
/// @dev
/// Behavior
/// • Builds calldata for 0, 1, or 2 arguments with the provided `selector`.
/// • For zero‑arg calls, first tries canonical 4‑byte selector encoding; if that fails or returns
/// fewer than `minLen` bytes, retries with a 32‑byte padded selector (certain odd interfaces expect this).
/// • Executes a `STATICCALL` with `gasCap` forwarded.
/// • If `success && ret.length >= minLen`, copies exactly `minLen` bytes into a freshly allocated buffer
/// and returns `(true, out)`. Otherwise returns `(false, "")`.
/// Constraints & Conventions
/// • `minLen` in this codebase is always a multiple of 32 (32 or 96), enabling tight word‑wise copying.
/// • This function never reverts; it is safe to use in heuristics and detection flows.
/// @param target Contract to call.
/// @param selector 4‑byte function selector.
/// @param arg0 First ABI argument (ignored when `argCount == 0`).
/// @param arg1 Second ABI argument (used when `argCount == 2`).
/// @param argCount Number of ABI arguments to encode: 0, 1, or 2.
/// @param gasCap Gas forwarded to the `STATICCALL`.
/// @param minLen Minimum return‑data length required for success (bytes).
/// @return ok True if the call succeeded and returned at least `minLen` bytes.
/// @return out A `bytes` buffer of length `minLen` containing the first `minLen` bytes of returndata.
function _staticProbeRaw(
address target,
bytes4 selector,
uint256 arg0,
uint256 arg1,
uint256 argCount,
uint256 gasCap,
uint256 minLen
) internal view returns (bool ok, bytes memory out) {
bytes memory data;
if (argCount == 0) {
data = abi.encodeWithSelector(selector); // 4-byte calldata
} else if (argCount == 1) {
data = abi.encodeWithSelector(selector, arg0);
} else {
data = abi.encodeWithSelector(selector, arg0, arg1);
}
bool success;
uint256 size;
// First attempt: copy 0 bytes on call, then copy only minLen if size is sufficient
assembly ("memory-safe") {
success := staticcall(gasCap, target, add(data, 0x20), mload(data), 0, 0)
size := returndatasize()
}
if (argCount == 0 && (!success || size < minLen)) {
// Retry with 32-byte padded selector
bytes memory data32 = new bytes(32);
assembly ("memory-safe") {
mstore(add(data32, 32), shl(224, selector))
success := staticcall(gasCap, target, add(data32, 0x20), 0x20, 0, 0)
size := returndatasize()
}
}
ok = success && size >= minLen;
if (!ok) return (false, bytes(""));
// Allocate exactly minLen and copy only that much from the return buffer
out = new bytes(minLen);
assembly ("memory-safe") {
returndatacopy(add(out, 0x20), 0, minLen)
}
}
/*//////////////////////////////////////////////////////////////////////////
INTERNAL UTILITIES (MATH & PROBES)
//////////////////////////////////////////////////////////////////////////*/
/// @notice Saturating subtraction – clamps at zero instead of under-flowing.
function _satSub(uint256 a, uint256 b) internal pure returns (uint256 r) {
unchecked { r = a > b ? a - b : 0; }
}
/// @notice Saturating addition – clamps at max uint256 instead of overflowing.
function _satAdd(uint256 a, uint256 b) internal pure returns (uint256 r) {
unchecked {
r = a + b;
if (r < a) {
// Overflow happened, clamp to max
r = type(uint256).max;
}
}
}
/// @notice Saturating subtraction – clamps at zero instead of under-flowing.
function _satSub128(uint128 a, uint128 b) internal pure returns (uint128 r) {
unchecked { r = a > b ? a - b : 0; }
}
/// @notice Saturating addition – clamps at max uint128 instead of overflowing.
function _satAdd128(uint128 a, uint128 b) internal pure returns (uint128 r) {
unchecked {
r = a + b;
if (r < a) {
// Overflow happened, clamp to max
r = type(uint128).max;
}
}
}
/// @notice Compare signs of (a1 - a2) and (b1 - b2) without subtracting.
/// @dev Zero is inclusive; only false when one delta >0 and the other <0
function _sameSign(uint256 a1, uint256 a2, uint256 b1, uint256 b2)
internal
pure
returns (bool)
{
return (a1 >= a2 && b1 >= b2) || (a1 <= a2 && b1 <= b2);
}
// Floor: saturating semantics, with 512 fallback only on overflow.
function _mulDivDown(uint256 a, uint256 b, uint256 d) internal pure returns (uint256 r) {
unchecked {
// Common trivials first (helps inlining + constant folding)
if ((a | b) == 0) return 0;
if (d == 0) return type(uint256).max;
// Try 256-bit product; if it overflowed, bail to 512
uint256 p = a * b;
if (a == 0 || p / a == b) {
return p / d; // floor
}
}
// Rare: overflow -> 512-bit saturated path
return __mulDiv512_sat(a, b, d, false);
}
// Ceil: saturating semantics, with 512 fallback only on overflow.
function _mulDivUp(uint256 a, uint256 b, uint256 d) internal pure returns (uint256 r) {
unchecked {
if ((a | b) == 0) return 0;
if (d == 0) return type(uint256).max;
uint256 p = a * b;
if (a == 0 || p / a == b) {
uint256 q = p / d;
// ceil (avoid wrap on MAX)
return q + (((p % d) != 0 && q != type(uint256).max) ? 1 : 0);
}
}
return __mulDiv512_sat(a, b, d, true);
}
/// @dev Shared 512-bit mulDiv (floor/ceil) with saturation. Never reverts.
function __mulDiv512_sat(uint256 a, uint256 b, uint256 d, bool roundUp) private pure returns (uint256 result) {
assembly ("memory-safe") {
// Defensive: saturate if d == 0 (shouldn't happen due to early returns above)
if iszero(d) {
result := not(0)
}
if d {
// 512-bit multiply: [prod1 prod0] = a * b
let mm := mulmod(a, b, not(0))
let prod0 := mul(a, b)
let prod1 := sub(sub(mm, prod0), lt(mm, prod0))
// If quotient wouldn't fit in 256 bits, saturate: (prod1 >= d)
if iszero(gt(d, prod1)) {
result := not(0)
}
if gt(d, prod1) {
// Compute remainder and make division exact.
let rem := mulmod(a, b, d)
prod1 := sub(prod1, gt(rem, prod0))
prod0 := sub(prod0, rem)
// Factor powers of two out of d.
let twos := and(d, sub(0, d))
d := div(d, twos)
// Divide [prod1 prod0] by the factored power of two.
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1) // twos = 2^256 / twos
prod0 := or(prod0, mul(prod1, twos))
// Compute floor quotient q. If reduced d == 1, skip inverse.
let q
if eq(d, 1) {
q := prod0
}
if iszero(eq(d, 1)) {
// Newton-Raphson modular inverse of odd d modulo 2^256 (6 steps)
let inv := xor(mul(3, d), 2)
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
q := mul(prod0, inv)
}
result := q
// If round up: add 1 iff (remainder != 0) and result != MAX (saturate instead of wrap)
if roundUp {
result := add(result, and(gt(rem, 0), iszero(eq(result, not(0)))))
}
}
}
}
}
/// @notice Caps a uint256 value down to uint128, clamping at the max if overflow.
function _cap128(uint256 x) internal pure returns (uint128) {
return x > type(uint128).max ? type(uint128).max : uint128(x);
}
/// @notice Caps a uint256 value down to uint112, clamping at the max if overflow.
function _cap112(uint256 x) internal pure returns (uint112) {
return x > type(uint112).max ? type(uint112).max : uint112(x);
}
/// @dev Integer floor sqrt with a good first guess; 7 Newton steps are enough for 256-bit.
function _isqrt(uint256 x) internal pure returns (uint256 y) {
if (x == 0) return 0;
uint256 z = uint256(1) << (_log2(x) >> 1);
unchecked {
// 7 Newton iterations
for (uint256 i; i < 7; ++i) { z = (z + x / z) >> 1; }
}
y = z;
// Safe clamp: avoids overflow when y >= 2^128
if (y > 0 && y > x / y) y--;
}
/// @dev Tiny log2 used to seed sqrt (gas-cheap).
function _log2(uint256 x) internal pure returns (uint256 n) {
if (x >= 2**128) { x >>= 128; n += 128; }
if (x >= 2**64) { x >>= 64; n += 64; }
if (x >= 2**32) { x >>= 32; n += 32; }
if (x >= 2**16) { x >>= 16; n += 16; }
if (x >= 2**8) { x >>= 8; n += 8; }
if (x >= 2**4) { x >>= 4; n += 4; }
if (x >= 2**2) { x >>= 2; n += 2; }
if (x >= 2**1) { n += 1; }
}
// Round to nearest integer: Tie is rounded down
// Returns round( num / INDEX_DECIMALS ).
function _divIndex(uint256 num) internal pure returns (uint256 q) {
// q = floor(num / 1e32), r = num - q*1e32 (0 <= r < 1e32)
q = num / INDEX_DECIMALS;
uint256 r = num - q * INDEX_DECIMALS;
// Compare 2*r to the denominator to avoid floating point
uint256 twice = r * 2;
if (twice > INDEX_DECIMALS) {
unchecked { q += 1; } // strictly above half -> round up
}
// else below half -> keep q
}
/// @dev commits global g values if the slots changed.
/// Always refers to cached 'active'
function _commitFromG(GlobalMemory memory g) internal {
(uint128 _index, uint128 _active) = readIndexActive();
(uint128 _pool, uint128 _oath) = readPoolOath();
if (g.index != _index) {
// keep the latest active; only advance index
writeIndexActive(g.index, _active);
}
if (g.pool != _pool || g.oath != _oath) {
writePoolOath(g.pool, g.oath);
}
}
}