Smart contract check by Deepseek-R1
Prompt: Please - check attached smart contract, consider the Key Token Principles * No presale, ICO, IDO, or private sale. * No allocations for the creator, developers, or investors. * No hidden reserves or special privileges for any address. * Distribution should be free, requiring only network gas fees. * No post-deployment control: contract is immutable once deployed. * Open-source and verifiable by anyone.
(Note: ChatGPT recomendation considered, the updated code passed to Deepseek)
Deepseek - thinking
Okay, I need to check if this smart contract adheres to the Key Token Principles mentioned. Let me go through each principle one by one and see how the contract handles them.
First principle: No presale, ICO, IDO, or private sale. The constructor mints all tokens to the contract itself. There's no initial distribution to any addresses, and the only way to get tokens is through the claim function. That seems to align with no presale or sales events. So that's covered.
Second: No allocations for creators, developers, or investors. Looking at the constructor, all tokens are minted to the contract. There's no code that assigns any tokens to specific addresses, so the team or investors aren't getting any. That's good.
Third: No hidden reserves or special privileges. The contract uses mappings to track claims, but there's no admin functions or addresses with special access. The contract doesn't have an owner, and once deployed, it can't be modified. So no hidden reserves here.
Fourth: Distribution requires only gas fees. The claim function doesn't require any payment except for gas. The user needs to have a minimum ETH balance, which is 0.01 ether. Wait, that's a requirement for the user's balance, not a payment. So the user isn't paying the contract, just needing to have some ETH. That's okay, just gas fees.
Fifth: No post-deployment control; contract is immutable. The contract uses OpenZeppelin's ERC20 and ReentrancyGuard, which don't have upgradeability. There's no owner or admin functions, so once deployed, no changes can be made. That's correct.
Sixth: Open-source and verifiable. The code is provided here, and if deployed, the bytecode can be verified on a block explorer. The license is MIT, which allows open-source use. So that's covered.
Now, looking at the functions. The claim function has several checks. Users can claim tokens after a cooldown and interval. The unlocked supply depends on participants and claims. The getCurrentReward halves over time, which is like a Bitcoin-style halving. The available supply is based on unlocked chunks.
Wait, there's a MIN_ETH_REQUIRED of 0.01 ether. Does that mean users need to hold ETH in their wallet to claim? The require statement checks msg.sender.balance >= MIN_ETH_REQUIRED. That could be a barrier for some users. But the principle says distribution is free except gas, so this might be a problem. However, it's not taking ETH from the user, just requiring they have a balance. But maybe this is against the principle because it restricts who can claim. For example, users need to have at least 0.01 ETH in their wallet. That's a possible issue. The principle says "requiring only network gas fees", so this requirement might be an extra condition that isn't allowed. This could be a violation of the fourth principle.
Another point: The contract uses ReentrancyGuard, which is good for security. The mappings track claims per address, and the MAX_PER_ADDRESS is enforced. That's fair to prevent one address from claiming too much.
The getClaimDelay function uses a random element based on block variables. This might help prevent front-running by adding a random delay. But the calculation uses block.prevrandao, which is the RANDAO value. However, in Solidity, using such values can be manipulated by miners to some extent. But since it's combined with other variables, maybe it's sufficient for basic protection.
Looking at the unlockedSupply function, it takes the minimum of chunks unlocked by claims and by participants. This means that even if many claims are made, if there aren't enough unique participants, the supply won't unlock further. That enforces a growth based on both usage and community participation.
The verifyFairDistribution function returns true for all checks, but in reality, we need to ensure that those are actually true. For example, the contract has no owner (constructor doesn't set one), is not upgradeable (no proxy pattern), and no special allocations (all tokens minted to the contract). So those returns are accurate.
Potential issues found:
1. The MIN_ETH_REQUIRED check might conflict with the principle of requiring only gas fees. Users need to hold 0.01 ETH to claim, which could exclude those without it. The principle says distribution should be free except gas, but this adds an extra requirement. This is a possible violation.
2. The getClaimDelay function's randomness might not be fully secure. Since it's based on block variables, miners could influence it. However, given the context, maybe it's acceptable for the intended purpose.
3. The unlockedSupply function could have an off-by-one error. For example, when calculating chunkIndex in chunkAllowedByParticipants, the loop increments chunkIndex until the participants are less than required. But the logic returns chunkIndex -1. Need to check if that correctly calculates the allowed chunk.
4. The initial reward is 0.1 tokens (assuming 18 decimals), which is small. But that's part of the tokenomics, so maybe not an issue.
Another check: The contract's total supply is 21 million, matching the TOTAL_SUPPLY constant. All tokens are minted to the contract in the constructor, and distributed via claims. That's correct.
In the claim function, the line "if (uniqueClaimersCount > lastUnlockedChunk)" might be intended to unlock new chunks based on participants. But lastUnlockedChunk is a state variable tracking the highest chunk unlocked. The loop inside might process up to 5 chunks per transaction. That's a design choice to limit gas usage.
Testing edge cases: If uniqueClaimersCount is exactly at the threshold for a new chunk, does it unlock correctly? The getRequiredParticipants function calculates based on chunkIndex. The chunkAllowedByParticipants loops until the participants are insufficient, then returns the last valid chunkIndex. That seems correct.
Another thing: The BASE_INTERVAL is 72 hours, which reduces as more unique claimers join. The minimum interval is 24 hours. So over time, the cooldown between claims decreases, encouraging more participation. But the getCurrentInterval function reduces the interval by 1 hour per 1000 participants, capped at 48 hours reduction. Wait, the code says hoursToReduce is _uniqueClaimers / 1000. So for 1000 participants, it's 1 hour reduction. Up to 48 hours. So the interval can go down to 72 -48 =24 hours. That logic seems okay.
The claim function updates lastUnlockedChunk in a loop, but only if uniqueClaimersCount > lastUnlockedChunk. Wait, uniqueClaimersCount is the number of unique participants. The chunkAllowedByParticipants depends on that count. So as more participants join, more chunks unlock. The code here is trying to unlock chunks when uniqueClaimersCount increases beyond the previous allowed chunks.
But the logic here might be flawed. The code checks if uniqueClaimersCount > lastUnlockedChunk, but lastUnlockedChunk is the number of chunks unlocked, not the participants. Wait, lastUnlockedChunk is the chunk index. So for example, if chunk 5 requires 100 participants, and uniqueClaimersCount is 100, then the allowed chunk would be 5. The code is trying to process new chunks when uniqueClaimersCount increases. But the loop inside increments from lastUnlockedChunk +1 to actualChunks. However, this logic may not directly tie the uniqueClaimersCount to the required participants per chunk. It's possible that when uniqueClaimersCount increases, the allowed chunk increases, and the code needs to unlock those chunks.
But the code in the claim function might not correctly calculate the new allowed chunks. For example, chunkAllowedByParticipants(uniqueClaimersCount) would return the maximum chunk index that can be unlocked based on current participants. Then, the code compares that to lastUnlockedChunk and unlocks up to 5 chunks at a time. But if the allowed chunks are more than lastUnlockedChunk +5, it will only process 5 each time. So this could delay unlocking until multiple claims are made. However, this is a gas optimization to prevent unbounded loops. So this is acceptable but might slow down the unlocking process if many chunks are eligible.
Potential issue: The MIN_ETH_REQUIRED could be a problem as per the principles. Let me check the principle again: "Distribution should be free, requiring only network gas fees." The user must have 0.01 ETH balance. While they don't pay it to the contract, it's a requirement that their balance is above a certain amount. This might be against the principle because it adds a barrier to entry beyond just paying gas. For example, someone with 0.009 ETH can't claim even if they have gas. So this condition might violate the fourth principle. The contract should remove this requirement to fully comply.
Another thing: The initial code uses "unchecked" blocks for increments. This is safe because the variables are unlikely to overflow, but it's a gas optimization. Since the total supply is fixed, and MAX_PER_ADDRESS is enforced, these should be safe.
In summary, the main issue is the MIN_ETH_REQUIRED check. Other than that, the contract seems to adhere to the principles. The rest of the code enforces fair distribution, no special allocations, and immutability.
The provided smart contract largely adheres to the Key Token Principles, but there's one critical violation and a few areas that need attention:
Critical Issue:
MIN_ETH_REQUIRED Violation ❌ The contract requires users to hold ≥0.01 ETH to claim tokens (
msg.sender.balance >= MIN_ETH_REQUIRED
). This violates the principle stating distribution should only require gas fees. This creates an unfair barrier excluding users without ETH holdings. Remove this requirement to comply with the principles.
Other Observations:
Front-Running Protection ⚠️ The random delay in
getClaimDelay
usesblock.prevrandao
, which is miner-influenced. While not perfect, it's a reasonable effort for basic protection given the constraints.Chunk Unlocking Logic ✅ The participant-based unlocking mechanism enforces fair distribution growth. Gas limits on chunk processing (max 5 per tx) are a practical design choice.
Immutability & Transparency ✅ No ownership/admin functions and all tokens minted to the contract align with principles. The
verifyFairDistribution
function correctly reflects contract properties.
Recommendations:
Remove the
MIN_ETH_REQUIRED
check to ensure anyone can claim tokens with just gas fees.Clarify Documentation about the participant-based unlocking mechanism to ensure transparency.
Consider Alternative Sybil Resistance if needed (e.g., proof-of-humanity), but ensure it doesn't violate the "free distribution" principle.
Adherence Summary:
No presale/ICO/IDO
✅
No team/investor allocations
✅
No hidden reserves
✅
Free distribution (gas only)
❌ Fix
Immutable post-deployment
✅
Open-source & verifiable
✅
Conclusion:
After removing the MIN_ETH_REQUIRED
check, the contract would fully comply with all stated principles. All other mechanisms (halvings, participant thresholds, cooldowns) align with a fair, permissionless distribution model.
Last updated