MemeFolioDistributor
0x616f634efde75d9401defb6ef3f158223b5b9f23
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
12,679 bytes
ABI entries
121
46 read · 18 write
License
none
Contract information
- Address
- 0x616f634efde75d9401defb6ef3f158223b5b9f23
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x7861A13054…F58333520b
- Creation tx
- 0xb4259e0665…a6fa637c12
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (46)
ADMIN_TRANSFER_DELAY() → uint48
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
completedCycleIds(bytes32) → bool
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
isRewardToken(address) → bool
lastProcessedHolder() → address
maxDistributionBatchSize() → uint256
minHolderBalance() → uint256
nextDistribution() → uint256
owner() → address
paidHolderCount() → uint256
paused() → bool
pendingDefaultAdmin() → address, uint48
pendingDefaultAdminDelay() → uint48, uint48
pendingRewardTokenAt(uint256) → address
pendingRewardTokenUpdateExecutableAt() → uint256
pendingRewardTokenUpdateId() → bytes32
potAt(uint256) → uint256
processedEligibleBalance() → uint256
rewardTokenAt(uint256) → address
rewardTokenCount() → uint256
rewardTokenUpdateNonce() → uint256
snapshotBlock() → uint256
snapshotBlockHash() → bytes32
sourceToken() → address
supportsInterface(bytes4) → bool
treasury() → address
usedCycleIds(bytes32) → bool
Events (19)
BatchPaidCycleCancelledCycleStartedDefaultAdminDelayChangeCanceledDefaultAdminDelayChangeScheduledDefaultAdminTransferCanceledDefaultAdminTransferScheduledDistributionCompletedForeignTokenRescuedNativeRescuedPausedPayoutSkippedRewardTokenUpdateCancelledRewardTokenUpdateExecutedRewardTokenUpdateProposedRoleAdminChangedRoleGrantedRoleRevokedUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "contract IMemeFolioSnapshotToken",
"name": "sourceToken_",
"type": "address"
},
{
"internalType": "contract IERC20[]",
"name": "rewardTokens_",
"type": "address[]"
},
{
"internalType": "uint256",
"name": "maxDistributionBatchSize_",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "excludedAccounts_",
"type": "address[]"
}
],
"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": "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": [],
"name": "InvalidRewardTokenCount",
"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": "NoPendingRewardTokenUpdate",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "ProtectedToken",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"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"
},
{
"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": "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": "bytes32",
"name": "proposalId",
"type": "bytes32"
}
],
"name": "RewardTokenUpdateCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "proposalId",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "address[]",
"name": "previousRewardTokens",
"type": "address[]"
},
{
"indexed": false,
"internalType": "address[]",
"name": "rewardTokens",
"type": "address[]"
}
],
"name": "RewardTokenUpdateExecuted",
"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[]",
"name": "rewardTokens",
"type": "address[]"
}
],
"name": "RewardTokenUpdateProposed",
"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": "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": "cancelCurrentCycle",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "cancelDefaultAdminTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "cancelRewardTokenUpdate",
"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": [
{
"internalType": "bytes32",
"name": "cycleId",
"type": "bytes32"
}
],
"name": "completedCycleIds",
"outputs": [
{
"internalType": "bool",
"name": "completed",
"type": "bool"
}
],
"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": "executeRewardTokenUpdate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "expectedHolderCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"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": "isRewardToken",
"outputs": [
{
"internalType": "bool",
"name": "registered",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastProcessedHolder",
"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": "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": "pendingRewardTokenAt",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingRewardTokenUpdateExecutableAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingRewardTokenUpdateId",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"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": "contract IERC20[]",
"name": "rewardTokens_",
"type": "address[]"
}
],
"name": "proposeRewardTokens",
"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": "rewardTokenUpdateNonce",
"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 IMemeFolioSnapshotToken",
"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 IMemeFolioSnapshotToken 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);
}
/// @title MemeFolioDistributor
/// @notice INDEX-style, balance-proportional distribution for exactly five reward tokens.
/// @dev Historical balances and the threshold-eligible supply are read from the immutable source
/// token checkpoints. The operator can trigger a finalized snapshot block but cannot upload or
/// alter balances. Sorted holder batches are operator-only to prevent a third party from
/// poisoning the irreversible cursor with a valid but incomplete prefix.
contract MemeFolioDistributor 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 = 1 hours;
uint256 public constant REWARD_TOKEN_COUNT = 5;
uint256 public constant REWARD_TOKEN_UPDATE_DELAY = 6 hours;
IMemeFolioSnapshotToken public immutable sourceToken;
address public immutable treasury;
uint256 public immutable minHolderBalance;
uint256 public immutable maxDistributionBatchSize;
IERC20[] private _rewardTokens;
IERC20[] private _pendingRewardTokens;
address[] private _excludedAccounts;
uint256[] private _pots;
mapping(address token => bool registered) public isRewardToken;
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 pendingRewardTokenUpdateId;
uint256 public pendingRewardTokenUpdateExecutableAt;
uint256 public rewardTokenUpdateNonce;
uint256 public snapshotBlock;
bytes32 public snapshotBlockHash;
uint256 public expectedHolderCount;
uint256 public paidHolderCount;
uint256 public eligible;
uint256 public processedEligibleBalance;
uint256 public cursor;
uint256 public nextDistribution;
address public lastProcessedHolder;
bool public cycleActive;
error ActiveCycleMismatch(bytes32 expected, bytes32 actual);
error BatchTooLarge(uint256 supplied, uint256 maximum);
error CycleAlreadyUsed(bytes32 cycleId);
error CycleAlreadyPaid(uint256 holdersPaid);
error CycleInProgress();
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 InvalidRewardTokenCount();
error InvalidHolderOrder(address previous, address current);
error InvalidSnapshotBlock(uint256 snapshotBlock, uint256 currentBlock);
error NativeTransferFailed();
error NoCycle();
error NoDistributionPot();
error NoEligibleHolders();
error NoPendingRewardTokenUpdate();
error ProtectedToken(address token);
error RewardTokenUpdateNotReady(uint256 executableAt);
error SnapshotMetadataMismatch();
error TooEarly(uint256 nextAllowedTimestamp);
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 RewardTokenUpdateProposed(
bytes32 indexed proposalId, uint256 executableAt, address[] rewardTokens
);
event RewardTokenUpdateCancelled(bytes32 indexed proposalId);
event RewardTokenUpdateExecuted(
bytes32 indexed proposalId, address[] previousRewardTokens, address[] rewardTokens
);
constructor(
address admin,
address operator,
address treasury_,
IMemeFolioSnapshotToken sourceToken_,
IERC20[] memory rewardTokens_,
uint256 maxDistributionBatchSize_,
address[] memory excludedAccounts_
) AccessControlDefaultAdminRules(ADMIN_TRANSFER_DELAY, admin) {
if (
admin == address(0) || operator == address(0) || treasury_ == address(0)
|| address(sourceToken_) == address(0)
) revert InvalidAddress();
if (rewardTokens_.length != REWARD_TOKEN_COUNT) revert InvalidRewardTokenCount();
if (maxDistributionBatchSize_ == 0 || maxDistributionBatchSize_ > 100) {
revert InvalidBatchSize();
}
sourceToken = sourceToken_;
treasury = treasury_;
minHolderBalance = sourceToken_.minHolderBalance();
if (minHolderBalance == 0) revert InvalidAddress();
maxDistributionBatchSize = maxDistributionBatchSize_;
for (uint256 i; i < rewardTokens_.length; ++i) {
IERC20 token = rewardTokens_[i];
address tokenAddress = address(token);
if (
tokenAddress == address(0) || tokenAddress == address(sourceToken_)
|| isRewardToken[tokenAddress]
) revert InvalidAddress();
isRewardToken[tokenAddress] = true;
_rewardTokens.push(token);
_exclude(tokenAddress);
}
_exclude(admin);
_exclude(operator);
_exclude(treasury_);
_exclude(address(sourceToken_));
_exclude(address(this));
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 _rewardTokens.length;
}
function rewardTokenAt(uint256 index) external view returns (address) {
return address(_rewardTokens[index]);
}
function pendingRewardTokenAt(uint256 index) external view returns (address) {
if (pendingRewardTokenUpdateId == bytes32(0)) revert NoPendingRewardTokenUpdate();
return address(_pendingRewardTokens[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];
}
/// @notice Announces a replacement five-token basket that can be activated after six hours.
/// @dev Proposing does not pause or otherwise affect the currently active reward basket.
function proposeRewardTokens(IERC20[] calldata rewardTokens_)
external
onlyRole(DEFAULT_ADMIN_ROLE)
returns (bytes32 proposalId)
{
_validateProposedRewardTokens(rewardTokens_);
bytes32 replacedProposalId = pendingRewardTokenUpdateId;
if (replacedProposalId != bytes32(0)) {
emit RewardTokenUpdateCancelled(replacedProposalId);
}
delete _pendingRewardTokens;
address[] memory proposedAddresses = new address[](REWARD_TOKEN_COUNT);
for (uint256 i; i < rewardTokens_.length; ++i) {
IERC20 token = rewardTokens_[i];
_pendingRewardTokens.push(token);
proposedAddresses[i] = address(token);
}
uint256 nonce = ++rewardTokenUpdateNonce;
proposalId = keccak256(abi.encode(block.chainid, address(this), nonce, proposedAddresses));
uint256 executableAt = block.timestamp + REWARD_TOKEN_UPDATE_DELAY;
pendingRewardTokenUpdateId = proposalId;
pendingRewardTokenUpdateExecutableAt = executableAt;
emit RewardTokenUpdateProposed(proposalId, executableAt, proposedAddresses);
}
/// @notice Cancels the currently announced basket replacement.
function cancelRewardTokenUpdate() external onlyRole(DEFAULT_ADMIN_ROLE) {
bytes32 proposalId = pendingRewardTokenUpdateId;
if (proposalId == bytes32(0)) revert NoPendingRewardTokenUpdate();
_clearPendingRewardTokenUpdate();
emit RewardTokenUpdateCancelled(proposalId);
}
/// @notice Activates an announced basket after the public delay and between reward cycles.
function executeRewardTokenUpdate()
external
onlyRole(DEFAULT_ADMIN_ROLE)
whenPaused
nonReentrant
{
bytes32 proposalId = pendingRewardTokenUpdateId;
if (proposalId == bytes32(0)) revert NoPendingRewardTokenUpdate();
if (cycleActive) revert CycleInProgress();
uint256 executableAt = pendingRewardTokenUpdateExecutableAt;
// Timestamp is the explicit public six-hour governance delay; small validator drift is immaterial.
// forge-lint: disable-next-line(block-timestamp)
if (block.timestamp < executableAt) revert RewardTokenUpdateNotReady(executableAt);
address[] memory previousAddresses = new address[](REWARD_TOKEN_COUNT);
address[] memory proposedAddresses = new address[](REWARD_TOKEN_COUNT);
for (uint256 i; i < _rewardTokens.length; ++i) {
address previous = address(_rewardTokens[i]);
previousAddresses[i] = previous;
isRewardToken[previous] = false;
}
delete _rewardTokens;
for (uint256 i; i < _pendingRewardTokens.length; ++i) {
IERC20 token = _pendingRewardTokens[i];
address tokenAddress = address(token);
_rewardTokens.push(token);
proposedAddresses[i] = tokenAddress;
isRewardToken[tokenAddress] = true;
_exclude(tokenAddress);
}
delete _pots;
_clearPendingRewardTokenUpdate();
emit RewardTokenUpdateExecuted(proposalId, previousAddresses, proposedAddresses);
}
/// @notice Freezes the five reward pots against an immutable historical token snapshot.
/// @dev The snapshot must be one of the 256 recent completed blocks so its hash can be verified
/// by the EVM. All balances and the denominator are then read from source-token checkpoints.
function startCycle(bytes32 cycleId, uint256 snapshotBlock_, bytes32 snapshotBlockHash_)
external
onlyRole(OPERATOR_ROLE)
whenNotPaused
nonReentrant
{
if (cycleActive) revert CycleInProgress();
// Timestamp is the intended hourly keeper gate; single-block drift is immaterial here.
// 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 = false;
delete _pots;
for (uint256 i; i < _rewardTokens.length; ++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_);
}
/// @notice Pays every reward token to the next bounded, strictly sorted holder page.
/// @dev The historical balance is queried onchain for every holder. Amounts are
/// floor(pot * holderBalance / eligible). Dust and skipped transfers roll forward.
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);
uint256 rewardCount = _rewardTokens.length;
for (uint256 i; i < rewardCount; ++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();
}
/// @notice Abandons a bad active cycle. Reward balances remain for the next cycle.
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);
}
/// @notice Recovers unrelated tokens only. Registered reward pots can never be rescued.
function rescueForeignToken(IERC20 token, uint256 amount)
external
onlyRole(DEFAULT_ADMIN_ROLE)
whenPaused
nonReentrant
{
if (address(token) == address(0)) revert InvalidAddress();
if (isRewardToken[address(token)]) revert ProtectedToken(address(token));
token.safeTransfer(treasury, amount);
emit ForeignTokenRescued(address(token), 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 _exclude(address account) private {
if (account == address(0)) revert InvalidAddress();
if (excludedFromRewards[account]) return;
excludedFromRewards[account] = true;
_excludedAccounts.push(account);
}
function _validateProposedRewardTokens(IERC20[] calldata rewardTokens_) private view {
if (rewardTokens_.length != REWARD_TOKEN_COUNT) revert InvalidRewardTokenCount();
for (uint256 i; i < rewardTokens_.length; ++i) {
address tokenAddress = address(rewardTokens_[i]);
if (
tokenAddress == address(0) || tokenAddress == address(sourceToken)
|| tokenAddress == address(this) || tokenAddress.code.length == 0
) revert InvalidAddress();
for (uint256 j; j < i; ++j) {
if (address(rewardTokens_[j]) == tokenAddress) revert InvalidAddress();
}
}
}
function _clearPendingRewardTokenUpdate() private {
delete _pendingRewardTokens;
pendingRewardTokenUpdateId = bytes32(0);
pendingRewardTokenUpdateExecutableAt = 0;
}
function _payHolder(bytes32 cycleId, address holder, uint256 balance, uint256 eligible_)
private
{
for (uint256 tokenIndex; tokenIndex < _rewardTokens.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/reverting balanceOf is treated as zero so one bad reward token cannot brick all five.
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) }
}
}
}
Chain explorer4773msChain node80ms