MemeFolioDistributorV2

0xb288130e52d5f0125e7c03461bcd77f7d504f3bc

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
17,157 bytes
ABI entries
155
65 read · 21 write
License
none

Contract information

Address
0xb288130e52d5f0125e7c03461bcd77f7d504f3bc
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xae3f4eccc3…e0d9c47897

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (65)

ADMIN_TRANSFER_DELAY()uint48
COMMUNITY_TOKEN_COUNT()uint256
COMMUNITY_TOKEN_UPDATE_DELAY()uint256
COMMUNITY_TOKEN_UPDATE_EXPIRY()uint256
CORE_TOKEN_COUNT()uint256
DEFAULT_ADMIN_ROLE()bytes32
DISTRIBUTION_INTERVAL()uint256
OPERATOR_ROLE()bytes32
PAUSER_ROLE()bytes32
REWARD_TOKEN_COUNT()uint256
REWARD_TOKEN_UPDATE_DELAY()uint256
activeCycleId()bytes32
cancelledCycleIds(bytes32)bool
communityBasketInitialized()bool
communityController()address
communityTokenAt(uint256)address
communityUpdateNonce()uint256
completedCycleIds(bytes32)bool
coreTokenAt(uint256)address
coreUpdateNonce()uint256
cursor()uint256
cycleActive()bool
defaultAdmin()address
defaultAdminDelay()uint48
defaultAdminDelayIncreaseWait()uint48
eligible()uint256
excludedAccountAt(uint256)address
excludedAccountCount()uint256
excludedFromRewards(address)bool
expectedHolderCount()uint256
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
isCoreRewardToken(address)bool
isPendingCoreRewardToken(address)bool
isPendingRewardToken(address)bool
isProtectedRewardToken(address)bool
isRewardToken(address)bool
lastCompletedHolderCount()uint256
lastProcessedHolder()address
legacyDistributor()address
maxDistributionBatchSize()uint256
minHolderBalance()uint256
nextDistribution()uint256
owner()address
paidHolderCount()uint256
paused()bool
pendingCommunityExecutableAt()uint256
pendingCommunityExpiresAt()uint256
pendingCommunityProposalId()bytes32
pendingCommunityTokenAt(uint256)address
pendingCoreExecutableAt()uint256
pendingCoreProposalId()bytes32
pendingCoreTokenAt(uint256)address
pendingDefaultAdmin()address, uint48
pendingDefaultAdminDelay()uint48, uint48
potAt(uint256)uint256
processedEligibleBalance()uint256
rewardTokenAt(uint256)address
rewardTokenCount()uint256
snapshotBlock()uint256
snapshotBlockHash()bytes32
sourceToken()address
supportsInterface(bytes4)bool
treasury()address
usedCycleIds(bytes32)bool

Events (23)

BatchPaidCommunityTokenUpdateExecutedCommunityTokenUpdateExpiredCommunityTokenUpdateProposedCoreTokenUpdateCancelledCoreTokenUpdateExecutedCoreTokenUpdateProposedCycleCancelledCycleStartedDefaultAdminDelayChangeCanceledDefaultAdminDelayChangeScheduledDefaultAdminTransferCanceledDefaultAdminTransferScheduledDistributionCompletedForeignTokenRescuedNativeRescuedPausedPayoutSkippedRetiredRewardDustTransferredRoleAdminChangedRoleGrantedRoleRevokedUnpaused

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "communityController_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "legacyDistributor_",
        "type": "address"
      },
      {
        "internalType": "contract IMemeFolioSnapshotTokenV2",
        "name": "sourceToken_",
        "type": "address"
      },
      {
        "internalType": "address[5]",
        "name": "coreTokens_",
        "type": "address[5]"
      },
      {
        "internalType": "address[3]",
        "name": "communityTokens_",
        "type": "address[3]"
      },
      {
        "internalType": "uint256",
        "name": "maxDistributionBatchSize_",
        "type": "uint256"
      },
      {
        "internalType": "address[]",
        "name": "excludedAccounts_",
        "type": "address[]"
      },
      {
        "internalType": "uint256",
        "name": "initialDistributionTimestamp_",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AccessControlBadConfirmation",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "name": "AccessControlEnforcedDefaultAdminDelay",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "AccessControlEnforcedDefaultAdminRules",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "defaultAdmin",
        "type": "address"
      }
    ],
    "name": "AccessControlInvalidDefaultAdmin",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "internalType": "bytes32",
        "name": "neededRole",
        "type": "bytes32"
      }
    ],
    "name": "AccessControlUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "expected",
        "type": "bytes32"
      },
      {
        "internalType": "bytes32",
        "name": "actual",
        "type": "bytes32"
      }
    ],
    "name": "ActiveCycleMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maximum",
        "type": "uint256"
      }
    ],
    "name": "BatchTooLarge",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expiresAt",
        "type": "uint256"
      }
    ],
    "name": "CommunityTokenUpdateNotExpired",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expiresAt",
        "type": "uint256"
      }
    ],
    "name": "CommunityTokenUpdatePastExpiry",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "holdersPaid",
        "type": "uint256"
      }
    ],
    "name": "CycleAlreadyPaid",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "CycleAlreadyUsed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CycleInProgress",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "processed",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      }
    ],
    "name": "EligibleBalanceExceeded",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "processed",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      }
    ],
    "name": "EligibleBalanceMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyBatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EnforcedPause",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      }
    ],
    "name": "ExcludedHolder",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ExpectedPause",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "minimum",
        "type": "uint256"
      }
    ],
    "name": "HolderBelowMinimum",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAddress",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidBatchSize",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "expected",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "actual",
        "type": "uint256"
      }
    ],
    "name": "InvalidCursor",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidHolderCount",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "previous",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "current",
        "type": "address"
      }
    ],
    "name": "InvalidHolderOrder",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "supplied",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "currentTimestamp",
        "type": "uint256"
      }
    ],
    "name": "InvalidInitialDistributionTimestamp",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "InvalidRewardTokenBalance",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "snapshotBlock",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "currentBlock",
        "type": "uint256"
      }
    ],
    "name": "InvalidSnapshotBlock",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NativeTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoCycle",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoDistributionPot",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoEligibleHolders",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoPendingCommunityUpdate",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoPendingCoreUpdate",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "PendingRewardTokenUpdate",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "ProtectedToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "balance",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "maximum",
        "type": "uint256"
      }
    ],
    "name": "RewardTokenBalanceTooHigh",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "executableAt",
        "type": "uint256"
      }
    ],
    "name": "RewardTokenUpdateNotReady",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint8",
        "name": "bits",
        "type": "uint8"
      },
      {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "SafeCastOverflowedUintDowncast",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SnapshotMetadataMismatch",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "nextAllowedTimestamp",
        "type": "uint256"
      }
    ],
    "name": "TooEarly",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "caller",
        "type": "address"
      }
    ],
    "name": "UnauthorizedCommunityController",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnfinalizedCommunityRound",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fromIndex",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "toIndex",
        "type": "uint256"
      }
    ],
    "name": "BatchPaid",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address[3]",
        "name": "previousTokens",
        "type": "address[3]"
      },
      {
        "indexed": false,
        "internalType": "address[3]",
        "name": "tokens",
        "type": "address[3]"
      }
    ],
    "name": "CommunityTokenUpdateExecuted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "expiredAt",
        "type": "uint256"
      }
    ],
    "name": "CommunityTokenUpdateExpired",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "executableAt",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address[3]",
        "name": "tokens",
        "type": "address[3]"
      }
    ],
    "name": "CommunityTokenUpdateProposed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      }
    ],
    "name": "CoreTokenUpdateCancelled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "address[5]",
        "name": "previousTokens",
        "type": "address[5]"
      },
      {
        "indexed": false,
        "internalType": "address[5]",
        "name": "tokens",
        "type": "address[5]"
      }
    ],
    "name": "CoreTokenUpdateExecuted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "executableAt",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address[5]",
        "name": "tokens",
        "type": "address[5]"
      }
    ],
    "name": "CoreTokenUpdateProposed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "CycleCancelled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "snapshotBlock",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "bytes32",
        "name": "snapshotBlockHash",
        "type": "bytes32"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eligibleHolders",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "eligibleBalance",
        "type": "uint256"
      }
    ],
    "name": "CycleStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "DefaultAdminDelayChangeCanceled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      },
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "effectSchedule",
        "type": "uint48"
      }
    ],
    "name": "DefaultAdminDelayChangeScheduled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [],
    "name": "DefaultAdminTransferCanceled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint48",
        "name": "acceptSchedule",
        "type": "uint48"
      }
    ],
    "name": "DefaultAdminTransferScheduled",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "frozenPot",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "holders",
        "type": "uint256"
      }
    ],
    "name": "DistributionCompleted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "ForeignTokenRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "NativeRescued",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "holder",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "PayoutSkipped",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "RetiredRewardDustTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ADMIN_TRANSFER_DELAY",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "COMMUNITY_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "COMMUNITY_TOKEN_UPDATE_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "COMMUNITY_TOKEN_UPDATE_EXPIRY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "CORE_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DISTRIBUTION_INTERVAL",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "OPERATOR_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "PAUSER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "REWARD_TOKEN_COUNT",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "REWARD_TOKEN_UPDATE_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "activeCycleId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      }
    ],
    "name": "beginDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelCoreTokenUpdate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelCurrentCycle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cancelDefaultAdminTransfer",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "cancelledCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "cancelled",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      }
    ],
    "name": "changeDefaultAdminDelay",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "communityBasketInitialized",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "communityController",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "communityTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "communityUpdateNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "completedCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "completed",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "coreTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "coreUpdateNonce",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cursor",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "cycleActive",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdmin",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdminDelay",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "defaultAdminDelayIncreaseWait",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "expectedCursor",
        "type": "uint256"
      },
      {
        "internalType": "address[]",
        "name": "holders",
        "type": "address[]"
      }
    ],
    "name": "distributeBatch",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "eligible",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "excludedAccountAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "excludedAccountCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "excludedFromRewards",
    "outputs": [
      {
        "internalType": "bool",
        "name": "excluded",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "executeCommunityTokenUpdate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "executeCoreTokenUpdate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "expectedHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "expireCommunityTokenUpdate",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isCoreRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isPendingCoreRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "pendingCore",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isPendingRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "pending",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isProtectedRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "protected_",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "isRewardToken",
    "outputs": [
      {
        "internalType": "bool",
        "name": "active",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastCompletedHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lastProcessedHolder",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "legacyDistributor",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "maxDistributionBatchSize",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "minHolderBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "nextDistribution",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paidHolderCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityExecutableAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityExpiresAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCommunityProposalId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "pendingCommunityTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCoreExecutableAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingCoreProposalId",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "pendingCoreTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingDefaultAdmin",
    "outputs": [
      {
        "internalType": "address",
        "name": "newAdmin",
        "type": "address"
      },
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingDefaultAdminDelay",
    "outputs": [
      {
        "internalType": "uint48",
        "name": "newDelay",
        "type": "uint48"
      },
      {
        "internalType": "uint48",
        "name": "schedule",
        "type": "uint48"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "potAt",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "processedEligibleBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[3]",
        "name": "tokens",
        "type": "address[3]"
      }
    ],
    "name": "proposeCommunityTokens",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[5]",
        "name": "tokens",
        "type": "address[5]"
      }
    ],
    "name": "proposeCoreTokens",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "proposalId",
        "type": "bytes32"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueForeignToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "rescueNative",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "rewardTokenAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rewardTokenCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "rollbackDefaultAdminDelay",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "snapshotBlock",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "snapshotBlockHash",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "sourceToken",
    "outputs": [
      {
        "internalType": "contract IMemeFolioSnapshotTokenV2",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      },
      {
        "internalType": "uint256",
        "name": "snapshotBlock_",
        "type": "uint256"
      },
      {
        "internalType": "bytes32",
        "name": "snapshotBlockHash_",
        "type": "bytes32"
      }
    ],
    "name": "startCycle",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "cycleId",
        "type": "bytes32"
      }
    ],
    "name": "usedCycleIds",
    "outputs": [
      {
        "internalType": "bool",
        "name": "used",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

import {
    AccessControlDefaultAdminRules
} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

interface IMemeFolioSnapshotTokenV2 is IERC20 {
    function minHolderBalance() external view returns (uint256);
    function getPastBalance(address account, uint256 blockNumber) external view returns (uint256);
    function getPastEligibleSupply(uint256 blockNumber) external view returns (uint256);
    function getPastEligibleHolderCount(uint256 blockNumber) external view returns (uint256);
}

interface IMemeFolioCommunityRoundState {
    function hasUnfinalizedRound() external view returns (bool);
}

/// @title MemeFolioDistributorV2
/// @notice Snapshot-based proportional distributions across five core tokens, expanding to eight
///         only after holder voting initializes the three community slots.
/// @dev Core administration remains with the delayed admin. Community slots are proposed only by
///      the immutable voting controller after its six-hour public vote and are then permissionlessly
///      executable between reward cycles without a second duplicate delay.
contract MemeFolioDistributorV2 is AccessControlDefaultAdminRules, Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    uint48 public constant ADMIN_TRANSFER_DELAY = 2 days;
    uint256 public constant DISTRIBUTION_INTERVAL = 15 minutes;
    uint256 public constant CORE_TOKEN_COUNT = 5;
    uint256 public constant COMMUNITY_TOKEN_COUNT = 3;
    uint256 public constant REWARD_TOKEN_COUNT = 8;
    uint256 public constant REWARD_TOKEN_UPDATE_DELAY = 6 hours;
    uint256 public constant COMMUNITY_TOKEN_UPDATE_DELAY = 0;
    uint256 public constant COMMUNITY_TOKEN_UPDATE_EXPIRY = 7 days;

    IMemeFolioSnapshotTokenV2 public immutable sourceToken;
    address public immutable treasury;
    address public immutable communityController;
    address public immutable legacyDistributor;
    uint256 public immutable minHolderBalance;
    uint256 public immutable maxDistributionBatchSize;

    IERC20[8] private _rewardTokens;
    address[5] private _pendingCoreTokens;
    address[3] private _pendingCommunityTokens;
    address[] private _excludedAccounts;
    uint256[] private _pots;

    mapping(address token => bool active) public isRewardToken;
    mapping(address token => bool core) private _isActiveCoreRewardToken;
    mapping(address token => bool pendingCore) public isPendingCoreRewardToken;
    mapping(address token => bool pending) public isPendingRewardToken;
    mapping(address token => bool protected_) public isProtectedRewardToken;
    mapping(address account => bool excluded) public excludedFromRewards;
    mapping(bytes32 cycleId => bool used) public usedCycleIds;
    mapping(bytes32 cycleId => bool completed) public completedCycleIds;
    mapping(bytes32 cycleId => bool cancelled) public cancelledCycleIds;

    bytes32 public activeCycleId;
    bytes32 public pendingCoreProposalId;
    bytes32 public pendingCommunityProposalId;
    uint256 public pendingCoreExecutableAt;
    uint256 public pendingCommunityExecutableAt;
    uint256 public pendingCommunityExpiresAt;
    uint256 public coreUpdateNonce;
    uint256 public communityUpdateNonce;
    uint256 public snapshotBlock;
    bytes32 public snapshotBlockHash;
    uint256 public expectedHolderCount;
    uint256 public paidHolderCount;
    uint256 public lastCompletedHolderCount;
    uint256 public eligible;
    uint256 public processedEligibleBalance;
    uint256 public cursor;
    uint256 public nextDistribution;
    address public lastProcessedHolder;
    bool public cycleActive;
    bool public communityBasketInitialized;

    error ActiveCycleMismatch(bytes32 expected, bytes32 actual);
    error BatchTooLarge(uint256 supplied, uint256 maximum);
    error CycleAlreadyUsed(bytes32 cycleId);
    error CycleAlreadyPaid(uint256 holdersPaid);
    error CycleInProgress();
    error CommunityTokenUpdateNotExpired(uint256 expiresAt);
    error CommunityTokenUpdatePastExpiry(uint256 expiresAt);
    error EmptyBatch();
    error EligibleBalanceExceeded(uint256 processed, uint256 expected);
    error EligibleBalanceMismatch(uint256 processed, uint256 expected);
    error ExcludedHolder(address holder);
    error HolderBelowMinimum(address holder, uint256 balance, uint256 minimum);
    error InvalidAddress();
    error InvalidBatchSize();
    error InvalidCursor(uint256 expected, uint256 actual);
    error InvalidHolderCount();
    error InvalidHolderOrder(address previous, address current);
    error InvalidInitialDistributionTimestamp(uint256 supplied, uint256 currentTimestamp);
    error InvalidRewardTokenBalance(address token);
    error InvalidSnapshotBlock(uint256 snapshotBlock, uint256 currentBlock);
    error NativeTransferFailed();
    error NoCycle();
    error NoDistributionPot();
    error NoEligibleHolders();
    error NoPendingCoreUpdate();
    error NoPendingCommunityUpdate();
    error PendingRewardTokenUpdate();
    error ProtectedToken(address token);
    error RewardTokenBalanceTooHigh(address token, uint256 balance, uint256 maximum);
    error RewardTokenUpdateNotReady(uint256 executableAt);
    error SnapshotMetadataMismatch();
    error TooEarly(uint256 nextAllowedTimestamp);
    error UnfinalizedCommunityRound();
    error UnauthorizedCommunityController(address caller);

    event CycleStarted(
        bytes32 indexed cycleId,
        uint256 indexed snapshotBlock,
        bytes32 snapshotBlockHash,
        uint256 eligibleHolders,
        uint256 eligibleBalance
    );
    event BatchPaid(bytes32 indexed cycleId, uint256 fromIndex, uint256 toIndex);
    event PayoutSkipped(
        bytes32 indexed cycleId, address indexed token, address indexed holder, uint256 amount
    );
    event DistributionCompleted(
        bytes32 indexed cycleId, address indexed token, uint256 frozenPot, uint256 holders
    );
    event CycleCancelled(bytes32 indexed cycleId);
    event NativeRescued(address indexed treasury, uint256 amount);
    event ForeignTokenRescued(address indexed token, address indexed treasury, uint256 amount);
    event CoreTokenUpdateProposed(
        bytes32 indexed proposalId, uint256 executableAt, address[5] tokens
    );
    event CoreTokenUpdateExecuted(
        bytes32 indexed proposalId, address[5] previousTokens, address[5] tokens
    );
    event CoreTokenUpdateCancelled(bytes32 indexed proposalId);
    event CommunityTokenUpdateProposed(
        bytes32 indexed proposalId, uint256 executableAt, address[3] tokens
    );
    event CommunityTokenUpdateExecuted(
        bytes32 indexed proposalId, address[3] previousTokens, address[3] tokens
    );
    event CommunityTokenUpdateExpired(bytes32 indexed proposalId, uint256 expiredAt);
    event RetiredRewardDustTransferred(
        address indexed token, address indexed treasury, uint256 amount
    );

    constructor(
        address admin,
        address operator,
        address treasury_,
        address communityController_,
        address legacyDistributor_,
        IMemeFolioSnapshotTokenV2 sourceToken_,
        address[5] memory coreTokens_,
        address[3] memory communityTokens_,
        uint256 maxDistributionBatchSize_,
        address[] memory excludedAccounts_,
        uint256 initialDistributionTimestamp_
    ) AccessControlDefaultAdminRules(ADMIN_TRANSFER_DELAY, admin) {
        if (
            operator == address(0) || treasury_ == address(0) || communityController_ == address(0)
                || communityController_.code.length == 0 || legacyDistributor_ == address(0)
                || legacyDistributor_.code.length == 0 || address(sourceToken_) == address(0)
                || address(sourceToken_).code.length == 0
        ) revert InvalidAddress();
        if (maxDistributionBatchSize_ == 0 || maxDistributionBatchSize_ > 100) {
            revert InvalidBatchSize();
        }
        if (initialDistributionTimestamp_ != 0 && initialDistributionTimestamp_ < block.timestamp) {
            revert InvalidInitialDistributionTimestamp(
                initialDistributionTimestamp_, block.timestamp
            );
        }

        sourceToken = sourceToken_;
        treasury = treasury_;
        communityController = communityController_;
        legacyDistributor = legacyDistributor_;
        minHolderBalance = sourceToken_.minHolderBalance();
        if (minHolderBalance == 0) revert InvalidAddress();
        maxDistributionBatchSize = maxDistributionBatchSize_;
        nextDistribution = initialDistributionTimestamp_;

        address[8] memory initialTokens;
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            initialTokens[i] = coreTokens_[i];
        }
        bool hasInitialCommunityToken;
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            initialTokens[CORE_TOKEN_COUNT + i] = communityTokens_[i];
            if (communityTokens_[i] != address(0)) hasInitialCommunityToken = true;
        }
        uint256 initialRewardTokenCount =
            hasInitialCommunityToken ? REWARD_TOKEN_COUNT : CORE_TOKEN_COUNT;
        _validateBasket(initialTokens, initialRewardTokenCount);
        communityBasketInitialized = hasInitialCommunityToken;
        for (uint256 i; i < initialRewardTokenCount; ++i) {
            address token = initialTokens[i];
            _rewardTokens[i] = IERC20(token);
            isRewardToken[token] = true;
            isProtectedRewardToken[token] = true;
            if (i < CORE_TOKEN_COUNT) _isActiveCoreRewardToken[token] = true;
            _exclude(token);
        }

        _exclude(admin);
        _exclude(operator);
        _exclude(treasury_);
        _exclude(address(sourceToken_));
        _exclude(address(this));
        _exclude(legacyDistributor_);
        for (uint256 i; i < excludedAccounts_.length; ++i) {
            _exclude(excludedAccounts_[i]);
        }

        _grantRole(OPERATOR_ROLE, operator);
        _grantRole(PAUSER_ROLE, admin);
    }

    receive() external payable { }

    function rewardTokenCount() external view returns (uint256) {
        return _activeRewardTokenCount();
    }

    function rewardTokenAt(uint256 index) external view returns (address) {
        return address(_rewardTokens[index]);
    }

    function coreTokenAt(uint256 index) external view returns (address) {
        return address(_rewardTokens[index]);
    }

    function isCoreRewardToken(address token) external view returns (bool) {
        return _isActiveCoreRewardToken[token] || isPendingCoreRewardToken[token];
    }

    function communityTokenAt(uint256 index) external view returns (address) {
        return address(_rewardTokens[CORE_TOKEN_COUNT + index]);
    }

    function pendingCoreTokenAt(uint256 index) external view returns (address) {
        if (pendingCoreProposalId == bytes32(0)) revert NoPendingCoreUpdate();
        return _pendingCoreTokens[index];
    }

    function pendingCommunityTokenAt(uint256 index) external view returns (address) {
        if (pendingCommunityProposalId == bytes32(0)) revert NoPendingCommunityUpdate();
        return _pendingCommunityTokens[index];
    }

    function potAt(uint256 index) external view returns (uint256) {
        return _pots[index];
    }

    function excludedAccountCount() external view returns (uint256) {
        return _excludedAccounts.length;
    }

    function excludedAccountAt(uint256 index) external view returns (address) {
        return _excludedAccounts[index];
    }

    function proposeCommunityTokens(address[3] calldata tokens)
        external
        returns (bytes32 proposalId)
    {
        if (msg.sender != communityController) {
            revert UnauthorizedCommunityController(msg.sender);
        }
        _prepareCommunityProposalQueue();
        _validateCommunityBasket(tokens);

        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            _pendingCommunityTokens[i] = tokens[i];
            isPendingRewardToken[tokens[i]] = true;
        }
        uint256 nonce = ++communityUpdateNonce;
        proposalId = keccak256(abi.encode(block.chainid, address(this), "COMMUNITY", nonce, tokens));
        uint256 executableAt = block.timestamp + COMMUNITY_TOKEN_UPDATE_DELAY;
        uint256 expiresAt = executableAt + COMMUNITY_TOKEN_UPDATE_EXPIRY;
        pendingCommunityProposalId = proposalId;
        pendingCommunityExecutableAt = executableAt;
        pendingCommunityExpiresAt = expiresAt;
        emit CommunityTokenUpdateProposed(proposalId, executableAt, tokens);
    }

    function executeCommunityTokenUpdate() external nonReentrant {
        bytes32 proposalId = pendingCommunityProposalId;
        if (proposalId == bytes32(0)) revert NoPendingCommunityUpdate();
        if (cycleActive) revert CycleInProgress();
        uint256 executableAt = pendingCommunityExecutableAt;
        // forge-lint: disable-next-line(block-timestamp)
        if (block.timestamp < executableAt) revert RewardTokenUpdateNotReady(executableAt);
        uint256 expiresAt = pendingCommunityExpiresAt;
        // forge-lint: disable-next-line(block-timestamp)
        if (block.timestamp >= expiresAt) revert CommunityTokenUpdatePastExpiry(expiresAt);

        address[3] memory previousTokens;
        address[8] memory nextBasket = _activeBasket();
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            previousTokens[i] = address(_rewardTokens[CORE_TOKEN_COUNT + i]);
            nextBasket[CORE_TOKEN_COUNT + i] = _pendingCommunityTokens[i];
        }
        _validateFullBasket(nextBasket);
        _retireRemovedTokens(previousTokens, nextBasket);
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            address token = _pendingCommunityTokens[i];
            _rewardTokens[CORE_TOKEN_COUNT + i] = IERC20(token);
            _activateRewardToken(token, false);
        }
        communityBasketInitialized = true;
        _clearPendingCommunityUpdate();
        delete _pots;
        emit CommunityTokenUpdateExecuted(proposalId, previousTokens, _communityBasket());
    }

    function expireCommunityTokenUpdate() external {
        bytes32 proposalId = pendingCommunityProposalId;
        if (proposalId == bytes32(0)) revert NoPendingCommunityUpdate();
        uint256 expiresAt = pendingCommunityExpiresAt;
        // forge-lint: disable-next-line(block-timestamp)
        if (block.timestamp < expiresAt) revert CommunityTokenUpdateNotExpired(expiresAt);
        _clearPendingCommunityUpdate();
        emit CommunityTokenUpdateExpired(proposalId, expiresAt);
    }

    function proposeCoreTokens(address[5] calldata tokens)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        returns (bytes32 proposalId)
    {
        if (IMemeFolioCommunityRoundState(communityController).hasUnfinalizedRound()) {
            revert UnfinalizedCommunityRound();
        }
        _requireNoPendingUpdate();
        _validateCoreBasket(tokens);

        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            _pendingCoreTokens[i] = tokens[i];
            isPendingRewardToken[tokens[i]] = true;
            isPendingCoreRewardToken[tokens[i]] = true;
        }
        uint256 nonce = ++coreUpdateNonce;
        proposalId = keccak256(abi.encode(block.chainid, address(this), "CORE", nonce, tokens));
        uint256 executableAt = block.timestamp + REWARD_TOKEN_UPDATE_DELAY;
        pendingCoreProposalId = proposalId;
        pendingCoreExecutableAt = executableAt;
        emit CoreTokenUpdateProposed(proposalId, executableAt, tokens);
    }

    function cancelCoreTokenUpdate() external onlyRole(DEFAULT_ADMIN_ROLE) {
        bytes32 proposalId = pendingCoreProposalId;
        if (proposalId == bytes32(0)) revert NoPendingCoreUpdate();
        _clearPendingCoreUpdate();
        emit CoreTokenUpdateCancelled(proposalId);
    }

    function executeCoreTokenUpdate()
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        whenPaused
        nonReentrant
    {
        bytes32 proposalId = pendingCoreProposalId;
        if (proposalId == bytes32(0)) revert NoPendingCoreUpdate();
        if (cycleActive) revert CycleInProgress();
        uint256 executableAt = pendingCoreExecutableAt;
        // forge-lint: disable-next-line(block-timestamp)
        if (block.timestamp < executableAt) revert RewardTokenUpdateNotReady(executableAt);

        address[5] memory previousTokens;
        address[8] memory nextBasket = _activeBasket();
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            previousTokens[i] = address(_rewardTokens[i]);
            nextBasket[i] = _pendingCoreTokens[i];
            _isActiveCoreRewardToken[previousTokens[i]] = false;
        }
        _validateBasket(nextBasket, _activeRewardTokenCount());
        _retireRemovedTokens(previousTokens, nextBasket);
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            address token = _pendingCoreTokens[i];
            _rewardTokens[i] = IERC20(token);
            _activateRewardToken(token, true);
        }
        _clearPendingCoreUpdate();
        delete _pots;
        emit CoreTokenUpdateExecuted(proposalId, previousTokens, _coreBasket());
    }

    function startCycle(bytes32 cycleId, uint256 snapshotBlock_, bytes32 snapshotBlockHash_)
        external
        onlyRole(OPERATOR_ROLE)
        whenNotPaused
        nonReentrant
    {
        if (cycleActive) revert CycleInProgress();
        // forge-lint: disable-next-line(block-timestamp)
        if (block.timestamp < nextDistribution) revert TooEarly(nextDistribution);
        if (cycleId == bytes32(0) || snapshotBlockHash_ == bytes32(0)) {
            revert SnapshotMetadataMismatch();
        }
        if (
            snapshotBlock_ >= block.number || block.number - snapshotBlock_ > 256
                || blockhash(snapshotBlock_) != snapshotBlockHash_
        ) {
            revert InvalidSnapshotBlock(snapshotBlock_, block.number);
        }
        if (usedCycleIds[cycleId]) revert CycleAlreadyUsed(cycleId);

        uint256 holderCount = sourceToken.getPastEligibleHolderCount(snapshotBlock_);
        if (holderCount == 0) revert InvalidHolderCount();
        uint256 eligible_ = sourceToken.getPastEligibleSupply(snapshotBlock_);
        for (uint256 i; i < _excludedAccounts.length; ++i) {
            uint256 excludedBalance =
                sourceToken.getPastBalance(_excludedAccounts[i], snapshotBlock_);
            if (excludedBalance >= minHolderBalance) {
                eligible_ -= excludedBalance;
                --holderCount;
            }
        }
        if (eligible_ == 0) revert NoEligibleHolders();

        bool hasPot;
        delete _pots;
        uint256 rewardCount = _activeRewardTokenCount();
        for (uint256 i; i < rewardCount; ++i) {
            uint256 pot = _safeBalanceOf(_rewardTokens[i]);
            _pots.push(pot);
            if (pot != 0) hasPot = true;
        }
        if (!hasPot) revert NoDistributionPot();

        usedCycleIds[cycleId] = true;
        activeCycleId = cycleId;
        snapshotBlock = snapshotBlock_;
        snapshotBlockHash = snapshotBlockHash_;
        expectedHolderCount = holderCount;
        paidHolderCount = 0;
        eligible = eligible_;
        processedEligibleBalance = 0;
        cursor = 0;
        lastProcessedHolder = address(0);
        cycleActive = true;
        nextDistribution = block.timestamp + DISTRIBUTION_INTERVAL;
        emit CycleStarted(cycleId, snapshotBlock_, snapshotBlockHash_, holderCount, eligible_);
    }

    function distributeBatch(bytes32 cycleId, uint256 expectedCursor, address[] calldata holders)
        external
        onlyRole(OPERATOR_ROLE)
        whenNotPaused
        nonReentrant
    {
        if (!cycleActive) revert NoCycle();
        if (cycleId != activeCycleId) revert ActiveCycleMismatch(activeCycleId, cycleId);
        if (expectedCursor != cursor) revert InvalidCursor(cursor, expectedCursor);
        uint256 count = holders.length;
        if (count == 0) revert EmptyBatch();
        if (count > maxDistributionBatchSize) {
            revert BatchTooLarge(count, maxDistributionBatchSize);
        }

        uint256 holderCount = expectedHolderCount;
        uint256 end = cursor + count;
        if (end > holderCount) revert InvalidHolderCount();
        uint256 eligible_ = eligible;

        address previousHolder = lastProcessedHolder;
        uint256 processedBalance = processedEligibleBalance;
        for (uint256 i; i < count; ++i) {
            address holder = holders[i];
            if (holder <= previousHolder) revert InvalidHolderOrder(previousHolder, holder);
            if (excludedFromRewards[holder]) revert ExcludedHolder(holder);
            uint256 balance = sourceToken.getPastBalance(holder, snapshotBlock);
            if (balance < minHolderBalance) {
                revert HolderBelowMinimum(holder, balance, minHolderBalance);
            }
            previousHolder = holder;
            processedBalance += balance;
            if (processedBalance > eligible_) {
                revert EligibleBalanceExceeded(processedBalance, eligible_);
            }
            _payHolder(cycleId, holder, balance, eligible_);
        }

        uint256 from = cursor;
        cursor = end;
        paidHolderCount = end;
        processedEligibleBalance = processedBalance;
        lastProcessedHolder = previousHolder;
        emit BatchPaid(cycleId, from, end);

        if (end == holderCount) {
            if (processedBalance != eligible_) {
                revert EligibleBalanceMismatch(processedBalance, eligible_);
            }
            cycleActive = false;
            completedCycleIds[cycleId] = true;
            activeCycleId = bytes32(0);
            lastCompletedHolderCount = holderCount;
            for (uint256 i; i < _pots.length; ++i) {
                emit DistributionCompleted(
                    cycleId, address(_rewardTokens[i]), _pots[i], holderCount
                );
            }
        }
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _unpause();
    }

    function cancelCurrentCycle() external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused nonReentrant {
        bytes32 cycleId = activeCycleId;
        if (cycleId == bytes32(0)) revert NoCycle();
        if (cursor != 0) revert CycleAlreadyPaid(cursor);
        cycleActive = false;
        activeCycleId = bytes32(0);
        cancelledCycleIds[cycleId] = true;
        emit CycleCancelled(cycleId);
    }

    function rescueForeignToken(IERC20 token, uint256 amount)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        whenPaused
        nonReentrant
    {
        address tokenAddress = address(token);
        if (tokenAddress == address(0)) revert InvalidAddress();
        if (
            isRewardToken[tokenAddress] || isPendingRewardToken[tokenAddress]
                || isProtectedRewardToken[tokenAddress]
        ) revert ProtectedToken(tokenAddress);
        token.safeTransfer(treasury, amount);
        emit ForeignTokenRescued(tokenAddress, treasury, amount);
    }

    function rescueNative(uint256 amount)
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        whenPaused
        nonReentrant
    {
        (bool success,) = payable(treasury).call{ value: amount }("");
        if (!success) revert NativeTransferFailed();
        emit NativeRescued(treasury, amount);
    }

    function _activateRewardToken(address token, bool core) private {
        isRewardToken[token] = true;
        _isActiveCoreRewardToken[token] = core;
        isProtectedRewardToken[token] = true;
        _exclude(token);
    }

    function _retireRemovedTokens(address[3] memory previousTokens, address[8] memory nextBasket)
        private
    {
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            _retireIfRemoved(previousTokens[i], nextBasket);
        }
    }

    function _retireRemovedTokens(address[5] memory previousTokens, address[8] memory nextBasket)
        private
    {
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            _retireIfRemoved(previousTokens[i], nextBasket);
        }
    }

    function _retireIfRemoved(address token, address[8] memory nextBasket) private {
        if (token == address(0)) return;
        for (uint256 i; i < REWARD_TOKEN_COUNT; ++i) {
            if (nextBasket[i] == token) return;
        }
        uint256 balance = _strictBalanceOf(IERC20(token));
        uint256 maximum = lastCompletedHolderCount;
        if (balance > maximum) revert RewardTokenBalanceTooHigh(token, balance, maximum);
        isRewardToken[token] = false;
        _isActiveCoreRewardToken[token] = false;
        if (balance != 0) IERC20(token).safeTransfer(treasury, balance);
        emit RetiredRewardDustTransferred(token, treasury, balance);
    }

    function _validateCommunityBasket(address[3] calldata tokens) private view {
        address[8] memory basket = _activeBasket();
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            basket[CORE_TOKEN_COUNT + i] = tokens[i];
        }
        _validateFullBasket(basket);
    }

    function _validateCoreBasket(address[5] calldata tokens) private view {
        address[8] memory basket = _activeBasket();
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            basket[i] = tokens[i];
        }
        _validateBasket(basket, _activeRewardTokenCount());
    }

    function _validateFullBasket(address[8] memory basket) private view {
        _validateBasket(basket, REWARD_TOKEN_COUNT);
    }

    function _validateBasket(address[8] memory basket, uint256 count) private view {
        for (uint256 i; i < count; ++i) {
            address token = basket[i];
            if (
                token == address(0) || token == address(sourceToken) || token == address(this)
                    || token.code.length == 0
            ) revert InvalidAddress();
            for (uint256 j; j < i; ++j) {
                if (basket[j] == token) revert InvalidAddress();
            }
        }
    }

    function _requireNoPendingUpdate() private view {
        if (pendingCoreProposalId != bytes32(0) || pendingCommunityProposalId != bytes32(0)) {
            revert PendingRewardTokenUpdate();
        }
    }

    /// @dev A successful vote has priority over a stale admin proposal. A previous community vote
    ///      remains protected for its full public execution window and is only cleared once expired.
    function _prepareCommunityProposalQueue() private {
        bytes32 communityProposalId = pendingCommunityProposalId;
        if (communityProposalId != bytes32(0)) {
            uint256 expiresAt = pendingCommunityExpiresAt;
            // forge-lint: disable-next-line(block-timestamp)
            if (block.timestamp < expiresAt) revert PendingRewardTokenUpdate();
            _clearPendingCommunityUpdate();
            emit CommunityTokenUpdateExpired(communityProposalId, expiresAt);
        }

        bytes32 coreProposalId = pendingCoreProposalId;
        if (coreProposalId != bytes32(0)) {
            _clearPendingCoreUpdate();
            emit CoreTokenUpdateCancelled(coreProposalId);
        }
    }

    function _clearPendingCoreUpdate() private {
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            address token = _pendingCoreTokens[i];
            isPendingRewardToken[token] = false;
            isPendingCoreRewardToken[token] = false;
        }
        delete _pendingCoreTokens;
        pendingCoreProposalId = bytes32(0);
        pendingCoreExecutableAt = 0;
    }

    function _clearPendingCommunityUpdate() private {
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            isPendingRewardToken[_pendingCommunityTokens[i]] = false;
        }
        delete _pendingCommunityTokens;
        pendingCommunityProposalId = bytes32(0);
        pendingCommunityExecutableAt = 0;
        pendingCommunityExpiresAt = 0;
    }

    function _activeBasket() private view returns (address[8] memory basket) {
        for (uint256 i; i < REWARD_TOKEN_COUNT; ++i) {
            basket[i] = address(_rewardTokens[i]);
        }
    }

    function _coreBasket() private view returns (address[5] memory basket) {
        for (uint256 i; i < CORE_TOKEN_COUNT; ++i) {
            basket[i] = address(_rewardTokens[i]);
        }
    }

    function _communityBasket() private view returns (address[3] memory basket) {
        for (uint256 i; i < COMMUNITY_TOKEN_COUNT; ++i) {
            basket[i] = address(_rewardTokens[CORE_TOKEN_COUNT + i]);
        }
    }

    function _activeRewardTokenCount() private view returns (uint256) {
        return communityBasketInitialized ? REWARD_TOKEN_COUNT : CORE_TOKEN_COUNT;
    }

    function _exclude(address account) private {
        if (account == address(0)) revert InvalidAddress();
        if (excludedFromRewards[account]) return;
        excludedFromRewards[account] = true;
        _excludedAccounts.push(account);
    }

    function _payHolder(bytes32 cycleId, address holder, uint256 balance, uint256 eligible_)
        private
    {
        for (uint256 tokenIndex; tokenIndex < _pots.length; ++tokenIndex) {
            uint256 amount = Math.mulDiv(_pots[tokenIndex], balance, eligible_);
            if (amount == 0) continue;
            IERC20 token = _rewardTokens[tokenIndex];
            if (!token.trySafeTransfer(holder, amount)) {
                emit PayoutSkipped(cycleId, address(token), holder, amount);
            }
        }
    }

    /// @dev A malformed balance getter is zero so one reward cannot stop unrelated payouts.
    function _safeBalanceOf(IERC20 token) private view returns (uint256 bal) {
        bytes memory payload = abi.encodeCall(IERC20.balanceOf, (address(this)));
        assembly ("memory-safe") {
            let success := staticcall(gas(), token, add(payload, 0x20), mload(payload), 0x00, 0x20)
            if and(success, iszero(lt(returndatasize(), 0x20))) { bal := mload(0x00) }
        }
    }

    /// @dev Retirement is irreversible, so its accounting must not tolerate a bad balance getter.
    function _strictBalanceOf(IERC20 token) private view returns (uint256 balance) {
        (bool success, bytes memory result) =
            address(token).staticcall(abi.encodeCall(IERC20.balanceOf, (address(this))));
        if (!success || result.length != 32) revert InvalidRewardTokenBalance(address(token));
        balance = abi.decode(result, (uint256));
    }
}
Chain explorer4340msChain node96ms