IndexManager

0x475928c29f890658a6ebce28127d186e13d8e266

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
8,479 bytes
ABI entries
68
20 read · 17 write
License
none

Contract information

Address
0x475928c29f890658a6ebce28127d186e13d8e266
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x7a98774b88…cea84c7591

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

BPS()uint16
adapter()address
circulatingSupplyOverride(address)uint256
constituents(uint256)address, uint16, bool
constituentsLength()uint256
indexOfToken(address)uint256
isKeeper(address)bool
lastRebalance()uint64
maxBuyPerTx()uint256
maxSlippageBps()uint16
maxWeightBps()uint16
minMcapWeth()uint256
oracle()address
owner()address
pendingOwner()address
rebalanceInterval()uint64
tracker()address
treasury()address
treasuryIndexSkimBps()uint16
weth()address

Events (16)

AdapterSetBoughtCirculatingOverrideSetConstituentActiveConstituentAddedDistributedKeeperSetOracleSetOwnershipTransferStartedOwnershipTransferredParamsSetRebalancedTrackerSetTreasurySetWeightSetWeightingParamsSet

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "initialOwner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "adapter_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "oracle_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "treasuryIndexSkimBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "maxSlippageBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint256",
        "name": "maxBuyPerTx_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "BadParam",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "BudgetExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "DuplicateConstituent",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "Inactive",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotKeeper",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RebalanceTooSoon",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TrackerAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TrackerNotSet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnknownConstituent",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "adapter",
        "type": "address"
      }
    ],
    "name": "AdapterSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "wethIn",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      }
    ],
    "name": "Bought",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "supply",
        "type": "uint256"
      }
    ],
    "name": "CirculatingOverrideSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "name": "ConstituentActive",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "ConstituentAdded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toTreasury",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toHolders",
        "type": "uint256"
      }
    ],
    "name": "Distributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "keeper",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "KeeperSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "oracle",
        "type": "address"
      }
    ],
    "name": "OracleSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "skimBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "slipBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "maxBuyPerTx",
        "type": "uint256"
      }
    ],
    "name": "ParamsSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "timestamp",
        "type": "uint64"
      }
    ],
    "name": "Rebalanced",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "tracker",
        "type": "address"
      }
    ],
    "name": "TrackerSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TreasurySet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "weightBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "mcapWeth",
        "type": "uint256"
      }
    ],
    "name": "WeightSet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "maxWeightBps",
        "type": "uint16"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "minMcapWeth",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "rebalanceInterval",
        "type": "uint64"
      }
    ],
    "name": "WeightingParamsSet",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "adapter",
    "outputs": [
      {
        "internalType": "contract IDexAdapter",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "addConstituent",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "circulatingSupplyOverride",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "constituents",
    "outputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "weightBps",
        "type": "uint16"
      },
      {
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "constituentsLength",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "distribute",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "wethAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minOut",
        "type": "uint256"
      }
    ],
    "name": "executeBuy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amountOut",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "indexOfToken",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "tracker_",
        "type": "address"
      }
    ],
    "name": "initTracker",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "isKeeper",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastRebalance",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxBuyPerTx",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxSlippageBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxWeightBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minMcapWeth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "oracle",
    "outputs": [
      {
        "internalType": "contract IPriceOracle",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rebalance",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rebalanceInterval",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescue",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "name": "setActive",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "adapter_",
        "type": "address"
      }
    ],
    "name": "setAdapter",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "supply",
        "type": "uint256"
      }
    ],
    "name": "setCirculatingOverride",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "keeper",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "enabled",
        "type": "bool"
      }
    ],
    "name": "setKeeper",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "oracle_",
        "type": "address"
      }
    ],
    "name": "setOracle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "skimBps",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "slipBps",
        "type": "uint16"
      },
      {
        "internalType": "uint256",
        "name": "maxBuy",
        "type": "uint256"
      }
    ],
    "name": "setParams",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      }
    ],
    "name": "setTreasury",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "maxWeightBps_",
        "type": "uint16"
      },
      {
        "internalType": "uint256",
        "name": "minMcapWeth_",
        "type": "uint256"
      },
      {
        "internalType": "uint64",
        "name": "rebalanceInterval_",
        "type": "uint64"
      }
    ],
    "name": "setWeightingParams",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tracker",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasuryIndexSkimBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "contract IERC20",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Governed} from "./access/Governed.sol";
import {IDexAdapter} from "./interfaces/IDexAdapter.sol";
import {IPriceOracle} from "./interfaces/IPriceOracle.sol";
import {IDividendTracker} from "./interfaces/IDividendTracker.sol";

/// @title  IndexManager
/// @notice Holds the memecoin index composition, weights each constituent by its LIVE market cap (TWAP price ×
///         circulating supply) via `rebalance()`, TWAP-buys constituents in small slippage-guarded chunks, and
///         forwards bought memes to the immutable DividendTracker for auto-distribution to $MEMEDEX holders.
///
///         Weights are dynamic: as memecoin market caps rise/fall relative to each other, `rebalance()`
///         recomputes the buy weights from the price oracle. An optional per-constituent cap keeps any single
///         meme from dominating, and a market-cap floor drops dust/dead constituents for the period.
contract IndexManager is Governed, ReentrancyGuard {
    using SafeERC20 for IERC20;

    uint16 public constant BPS = 10_000;

    struct Constituent {
        address token;
        uint16 weightBps; // DERIVED by rebalance() from live market cap (not manually set)
        bool active; // false = ejected: no new buys, held balance still distributed
    }

    IERC20 public immutable weth;
    IDexAdapter public adapter; // v4 (or v2) swap adapter for buying constituents
    IPriceOracle public oracle;
    address public treasury;
    address public tracker; // DividendTracker, set once

    Constituent[] public constituents;
    mapping(address => uint256) public indexOfToken; // token => index+1 (0 = absent)
    mapping(address => uint256) public circulatingSupplyOverride; // 0 => use totalSupply() (FDV)

    uint16 public treasuryIndexSkimBps; // % of each bought balance kept for the treasury on distribute
    uint16 public maxSlippageBps; // on-chain floor guard for every buy
    uint16 public maxWeightBps; // per-constituent cap (10000 = no cap)
    uint256 public minMcapWeth; // market-cap floor (in WETH) to be included this period
    uint256 public maxBuyPerTx; // cap on WETH spent per executeBuy (TWAP chunk ceiling)
    uint64 public rebalanceInterval; // min seconds between rebalances
    uint64 public lastRebalance;

    event ConstituentAdded(address indexed token);
    event ConstituentActive(address indexed token, bool active);
    event CirculatingOverrideSet(address indexed token, uint256 supply);
    event Rebalanced(uint64 timestamp);
    event WeightSet(address indexed token, uint16 weightBps, uint256 mcapWeth);
    event Bought(address indexed token, uint256 wethIn, uint256 amountOut);
    event Distributed(address indexed token, uint256 toTreasury, uint256 toHolders);
    event OracleSet(address oracle);
    event AdapterSet(address adapter);
    event TrackerSet(address tracker);
    event TreasurySet(address treasury);
    event ParamsSet(uint16 skimBps, uint16 slipBps, uint256 maxBuyPerTx);
    event WeightingParamsSet(uint16 maxWeightBps, uint256 minMcapWeth, uint64 rebalanceInterval);

    error ZeroAddress();
    error BadParam();
    error UnknownConstituent();
    error Inactive();
    error DuplicateConstituent();
    error BudgetExceeded();
    error TrackerAlreadySet();
    error TrackerNotSet();
    error RebalanceTooSoon();

    constructor(
        address initialOwner,
        address weth_,
        address adapter_,
        address oracle_,
        address treasury_,
        uint16 treasuryIndexSkimBps_,
        uint16 maxSlippageBps_,
        uint256 maxBuyPerTx_
    ) Governed(initialOwner) {
        if (weth_ == address(0) || adapter_ == address(0) || oracle_ == address(0) || treasury_ == address(0)) {
            revert ZeroAddress();
        }
        if (treasuryIndexSkimBps_ > BPS || maxSlippageBps_ > BPS) revert BadParam();
        weth = IERC20(weth_);
        adapter = IDexAdapter(adapter_);
        oracle = IPriceOracle(oracle_);
        treasury = treasury_;
        treasuryIndexSkimBps = treasuryIndexSkimBps_;
        maxSlippageBps = maxSlippageBps_;
        maxBuyPerTx = maxBuyPerTx_;
        maxWeightBps = BPS; // no cap by default
    }

    // --- wiring / params (owner) -------------------------------------------

    /// @notice Set the DividendTracker ONCE, permanently — fixes the holder-fund destination.
    function initTracker(address tracker_) external onlyOwner {
        if (tracker_ == address(0)) revert ZeroAddress();
        if (tracker != address(0)) revert TrackerAlreadySet();
        tracker = tracker_;
        emit TrackerSet(tracker_);
    }

    function setOracle(address oracle_) external onlyOwner {
        if (oracle_ == address(0)) revert ZeroAddress();
        oracle = IPriceOracle(oracle_);
        emit OracleSet(oracle_);
    }

    function setAdapter(address adapter_) external onlyOwner {
        if (adapter_ == address(0)) revert ZeroAddress();
        adapter = IDexAdapter(adapter_);
        emit AdapterSet(adapter_);
    }

    function setTreasury(address treasury_) external onlyOwner {
        if (treasury_ == address(0)) revert ZeroAddress();
        treasury = treasury_;
        emit TreasurySet(treasury_);
    }

    function setParams(uint16 skimBps, uint16 slipBps, uint256 maxBuy) external onlyOwner {
        if (skimBps > BPS || slipBps > BPS) revert BadParam();
        treasuryIndexSkimBps = skimBps;
        maxSlippageBps = slipBps;
        maxBuyPerTx = maxBuy;
        emit ParamsSet(skimBps, slipBps, maxBuy);
    }

    function setWeightingParams(uint16 maxWeightBps_, uint256 minMcapWeth_, uint64 rebalanceInterval_)
        external
        onlyOwner
    {
        if (maxWeightBps_ == 0 || maxWeightBps_ > BPS) revert BadParam();
        maxWeightBps = maxWeightBps_;
        minMcapWeth = minMcapWeth_;
        rebalanceInterval = rebalanceInterval_;
        emit WeightingParamsSet(maxWeightBps_, minMcapWeth_, rebalanceInterval_);
    }

    function addConstituent(address token) external onlyOwner {
        if (token == address(0)) revert ZeroAddress();
        if (indexOfToken[token] != 0) revert DuplicateConstituent();
        constituents.push(Constituent({token: token, weightBps: 0, active: true}));
        indexOfToken[token] = constituents.length;
        emit ConstituentAdded(token);
    }

    /// @notice Emergency eject / re-include. Ejected tokens get no new buys; any held balance is still
    ///         distributed to holders on the next distribute() so nobody is stranded.
    function setActive(address token, bool active) external onlyOwner {
        constituents[_idx(token)].active = active;
        emit ConstituentActive(token, active);
    }

    /// @notice Override circulating supply used for the market-cap weight (e.g. exclude burns/locked). 0 =
    ///         use the token's full totalSupply (fully-diluted valuation).
    function setCirculatingOverride(address token, uint256 supply) external onlyOwner {
        circulatingSupplyOverride[token] = supply;
        emit CirculatingOverrideSet(token, supply);
    }

    // --- dynamic market-cap weighting (keeper) -----------------------------

    /// @notice Recompute every active constituent's weight from its live market cap (TWAP price × circulating
    ///         supply), apply the per-constituent cap and market-cap floor, and store the result. Keeper calls
    ///         this on a cadence; weights then track relative market caps automatically.
    function rebalance() external onlyKeeper {
        if (rebalanceInterval > 0 && block.timestamp < lastRebalance + rebalanceInterval) revert RebalanceTooSoon();
        uint256 n = constituents.length;

        uint256[] memory mcap = new uint256[](n);
        bool[] memory capped = new bool[](n);
        bool[] memory included = new bool[](n);
        uint256[] memory weight = new uint256[](n);

        for (uint256 i = 0; i < n; i++) {
            if (!constituents[i].active) continue;
            address token = constituents[i].token;
            uint256 supply = circulatingSupplyOverride[token];
            if (supply == 0) supply = IERC20(token).totalSupply();
            uint256 m = oracle.valueInWeth(token, supply);
            if (m >= minMcapWeth && m > 0) {
                mcap[i] = m;
                included[i] = true;
            }
        }

        // Iterative capping ("water-filling"): repeatedly allocate remaining bps proportional to market cap
        // among uncapped constituents; any that exceed maxWeightBps are clamped and their excess redistributed.
        uint256 remainingBps = BPS;
        for (uint256 pass = 0; pass < n; pass++) {
            uint256 totalUncapped = 0;
            for (uint256 i = 0; i < n; i++) {
                if (included[i] && !capped[i]) totalUncapped += mcap[i];
            }
            if (totalUncapped == 0) break;

            bool changed = false;
            for (uint256 i = 0; i < n; i++) {
                if (!included[i] || capped[i]) continue;
                uint256 w = (remainingBps * mcap[i]) / totalUncapped;
                if (w > maxWeightBps) {
                    capped[i] = true;
                    weight[i] = maxWeightBps;
                    changed = true;
                }
            }
            if (changed) {
                // recompute remaining after clamping this pass's over-cap entries
                remainingBps = BPS;
                for (uint256 i = 0; i < n; i++) {
                    if (capped[i]) remainingBps -= weight[i];
                }
            } else {
                for (uint256 i = 0; i < n; i++) {
                    if (included[i] && !capped[i]) weight[i] = (remainingBps * mcap[i]) / totalUncapped;
                }
                break;
            }
        }

        for (uint256 i = 0; i < n; i++) {
            uint16 wBps = uint16(weight[i]);
            constituents[i].weightBps = wBps;
            emit WeightSet(constituents[i].token, wBps, mcap[i]);
        }
        lastRebalance = uint64(block.timestamp);
        emit Rebalanced(uint64(block.timestamp));
    }

    // --- TWAP buying (keeper) ----------------------------------------------

    /// @notice Buy `wethAmount` of one constituent. Called repeatedly in small chunks at randomized intervals
    ///         by the keeper to TWAP the treasury's entry and avoid predictable-sandwich MEV. Chunk sizing per
    ///         constituent is driven by the current `weightBps` (set by rebalance()).
    function executeBuy(address token, uint256 wethAmount, uint256 minOut)
        external
        onlyKeeper
        nonReentrant
        returns (uint256 amountOut)
    {
        Constituent memory c = constituents[_idx(token)];
        if (!c.active) revert Inactive();
        if (wethAmount == 0 || (maxBuyPerTx > 0 && wethAmount > maxBuyPerTx)) revert BadParam();
        if (wethAmount > weth.balanceOf(address(this))) revert BudgetExceeded();

        // On-chain slippage floor from the oracle's smoothed price (WETH per 1e18 token). The keeper's `minOut`
        // (from the v4 Quoter) and this floor are combined — the stricter wins. DEX-agnostic (v2 or v4).
        uint256 pxPer1e18 = oracle.valueInWeth(token, 1e18);
        uint256 floorOut;
        if (pxPer1e18 > 0) {
            uint256 expectedOut = (wethAmount * 1e18) / pxPer1e18;
            floorOut = (expectedOut * (BPS - maxSlippageBps)) / BPS;
        }
        uint256 effMin = minOut > floorOut ? minOut : floorOut;

        IERC20(address(weth)).forceApprove(address(adapter), wethAmount);
        amountOut = adapter.buy(token, wethAmount, effMin, address(this));
        emit Bought(token, wethAmount, amountOut);
    }

    // --- distribution to holders (keeper) ----------------------------------

    /// @notice Send bought memes to the DividendTracker for auto-distribution. For every constituent (active
    ///         or ejected) with a balance: skim the treasury %, forward the rest to the tracker, and credit it
    ///         pro-rata. Skips entirely when there are no eligible holders yet (funds wait for the next call).
    function distribute() external onlyKeeper nonReentrant {
        if (tracker == address(0)) revert TrackerNotSet();
        if (IDividendTracker(tracker).totalShares() == 0) return; // no holders yet; keep funds for later

        uint256 n = constituents.length;
        for (uint256 i = 0; i < n; i++) {
            address token = constituents[i].token;
            uint256 bal = IERC20(token).balanceOf(address(this));
            if (bal == 0) continue;

            uint256 toTreasury = (bal * treasuryIndexSkimBps) / BPS;
            uint256 toHolders = bal - toTreasury;

            if (toTreasury > 0) IERC20(token).safeTransfer(treasury, toTreasury);
            if (toHolders > 0) {
                IERC20(token).safeTransfer(tracker, toHolders);
                IDividendTracker(tracker).distributeReward(token, toHolders);
            }
            emit Distributed(token, toTreasury, toHolders);
        }
    }

    // --- views / helpers ---------------------------------------------------

    function constituentsLength() external view returns (uint256) {
        return constituents.length;
    }

    function _idx(address token) private view returns (uint256) {
        uint256 pos = indexOfToken[token];
        if (pos == 0) revert UnknownConstituent();
        return pos - 1;
    }

    /// @notice Recover ONLY foreign tokens accidentally sent here. Cannot touch WETH (the buy budget) or any
    ///         index constituent (holder-destined memes) — so the owner can never siphon the distribution path.
    function rescue(address token, address to, uint256 amount) external onlyOwner {
        if (to == address(0)) revert ZeroAddress();
        if (token == address(weth) || indexOfToken[token] != 0) revert BadParam();
        IERC20(token).safeTransfer(to, amount);
    }
}
Chain explorer3553msChain node77ms