About This Report
This is a v2 internal engineering audit of the MegaCharm protocol contracts. It covers the complete codebase as of March 7, 2026, including all security improvements made since the initial testnet deployment on November 27, 2025. A formal third-party audit by an independent security firm is recommended before accepting significant value on-chain.
Scope
- 📄
contracts/LotteryManager.sol— Core lottery logic (UUPS upgradeable) - 📄
contracts/MEGA_Token.sol— VIP utility token (ERC-20) - 📄Deployment scripts, test suite, and frontend integration (informational)
🔗 Deployed Contracts (BSC Testnet)
Chain ID 97 — BNB Smart Chain Testnet
| Contract | Address | Status |
|---|---|---|
| LotteryManager (UUPS Proxy) | 0x0a11E02fb9B365d5b24911367b88Af3B89b3041B | ✅ Live |
| MEGA Token (ERC-20) | 0x7F84e6c3c7C8761d3EF1D6065ed89301DcC2365f | ✅ Live |
| Mock USDT (testnet) | 0x3D4b1972472d70b09D1A2349544936530E89702B | ✅ Live |
| Mock USDC (testnet) | 0x283455CD72a3404500aEa6Bb6d6b712717C38062 | ✅ Live |
| Mock VRF Coordinator (testnet) | 0xdcC2136Ee3B2990F984260785051C66a4322DFc9 | ✅ Live |
Deployer: 0x78AD76089Ff188Ec17B5714aFa16914B97cb54e3 — Deployed: November 27, 2025
📊 Audit Metrics
🔎 Findings
All findings — sorted by severity
LotteryManager.sol — _generateTicketNumber()Ticket numbers (000000–999999) are assigned using
keccak256(blockhash, timestamp, msg.sender, nonce). A miner could theoretically influence blockhash to favour certain ticket numbers.Why accepted: Ticket numbers are used for assignment only — not for winner selection. The winning number always comes from Chainlink VRF, which is provably tamper-proof. Even a validator who biases ticket numbers still cannot predict the VRF output, so this does not affect jackpot fairness. The incremental nonce ensures uniqueness within multi-ticket purchases.
claimProtocolFees()Fees can only be claimed by
adminWallet, not necessarily the contract owner. These addresses can diverge after setAdminWallet() is called.Why accepted: Intentional. Protocol revenue should flow to the operational payout address (
adminWallet), not the upgrade/ownership key. Ensures clean separation of duties.
emergencyWithdraw(address token, uint256 amount)The owner can withdraw any token amount from the contract, including funds in the jackpot pool or pending winner withdrawals.
Why accepted: The protocol operates under a single-operator model (owner = admin). This function is a last-resort recovery tool for genuinely stuck balances. Normal operations use
claimProtocolFees(). Transparent on-chain via BSCScan.
addSupportedToken()supportedTokenList is unbounded. Iteration over it in _allocateWinnings could approach block gas limits if many tokens were added.Why accepted:
addSupportedToken is onlyOwner. In practice, only USDT and USDC are used. No realistic scenario reaches gas limits under the intended operating model.
vrfSubscriptionId is stored as uint256 for VRF v2.5 compatibility, then cast to uint64 when calling the VRFCoordinatorV2Interface. Safe for BSC Mainnet (VRF v2 IDs fit in uint64). No risk for current deployment.
pendingWithdrawals, claimDeadlines, pendingProtocolFees, and related constants). The remaining 43 slots provide sufficient room for future upgrades.
🔒 Security Analysis
Vector-by-vector review
Reentrancy
- ✅All payment functions protected by
ReentrancyGuardUpgradeable - ✅VRF callback (
fulfillRandomWords) performs no ERC-20 transfers — pull-payment only - ✅State zeroed before transfers in
claimWinningsandexpireUnclaimedWinnings
Front-Running
- ✅30-minute hard lockout window: sales close at midnight, draw at 12:30 AM, sales reopen at 1:00 AM
- ✅
rounds[currentRound].drawnflag immediately blocks ticket sales once VRF is requested — closes the gap between request and callback arrival
Randomness Manipulation
- ✅Winning number derived from Chainlink VRF v2 — cryptographically verifiable, tamper-proof
- ✅VRF callback restricted to the Chainlink VRF Coordinator via
VRFConsumerBaseV2 - ✅No party — including owner, validator, or Chainlink node — can predict or influence the output
Access Control
- ✅All admin functions protected by
onlyOwner - ✅
performUpkeepgated bywhenNotPaused+ time window checks - ✅UUPS
_authorizeUpgradeisonlyOwner - ✅
_disableInitializers()in constructor prevents implementation contract takeover
Integer Arithmetic
- ✅Solidity
^0.8.20— overflow/underflow reverts by default - ✅Integer division dust (
jackpotAmount % numWinners) routed topendingProtocolFees— no stranded wei
Token Safety
- ✅All ERC-20 interactions use OpenZeppelin
SafeERC20 - ✅Protocol only accepts pre-approved tokens (USDT, USDC)
Jackpot Accounting
- ✅90% of every ticket sale →
jackpotPoolandtokenContributionsimmediately - ✅10% →
pendingProtocolFeesimmediately — not co-mingled with jackpot - ✅On rollover: 100% of jackpot carries forward — no double fee deduction
- ✅Multi-winner splits are exact: dust goes to protocol, never lost
📈 Score Breakdown
| Category | Score | Weight | Contribution |
|---|---|---|---|
| Smart Contract Architecture | 9.8 / 10 | 30% | 2.94 |
| Security & Vulnerability Analysis | 9.5 / 10 | 30% | 2.85 |
| Economic Model Correctness | 9.5 / 10 | 15% | 1.43 |
| Chainlink Integration | 9.5 / 10 | 10% | 0.95 |
| Test Coverage | 9.0 / 10 | 10% | 0.90 |
| Documentation Alignment | 10.0 / 10 | 5% | 0.50 |
| TOTAL | A / 9.4/10 | 100% | 9.57 |
🔧 v2 Security Improvements
Changes made since initial testnet deployment (Nov 2025 → Mar 2026)
💰 Economics Verification
- ✅Ticket price: $5.00 base (
5 * 10^18 wei) - ✅Jackpot allocation: 90% of every ticket sold
- ✅Protocol fee: 10% — accrues immediately, claimable by admin at any time
- ✅Rollover: 100% of jackpot carries forward with no fee deduction on empty rounds
- ✅VIP discounts: 10% / 20% / 40% at 10k / 50k / 100k $MEGA — calculated correctly in basis points
- ✅Multi-winner jackpot split: equal per token, dust to protocol
🧪 Test Coverage
Hardhat + Chai — 101 passing, 2 pending (probabilistic), 0 failing
| Test Category | Count | Result |
|---|---|---|
| Deployment & initialization | 8 | ✅ Pass |
| Ticket purchase (valid & invalid) | 10 | ✅ Pass |
| VIP discount calculation | 6 | ✅ Pass |
| Sales time windows | 6 | ✅ Pass |
| VRF draw — with winner | 5 | ✅ Pass |
| VRF draw — rollover (no winner) | 4 | ✅ Pass |
| Pull-payment claim flow | 5 | ✅ Pass |
| Protocol fee collection | 4 | ✅ Pass |
| Jackpot accumulation (Powerball-style) | 3 | ✅ Pass |
| Multi-winner split (collision) | 2 | ⏳ Pending |
| Unclaimed winnings expiry | 7 | ✅ Pass |
| Stuck round recovery | 3 | ✅ Pass |
| Chainlink Automation (check/perform upkeep) | 6 | ✅ Pass |
| Pause / Unpause | 4 | ✅ Pass |
| Upgrade — storage preservation | 3 | ✅ Pass |
| Admin functions | 8 | ✅ Pass |
| MEGA Token tiers & discounts | 19 | ✅ Pass |
⏳ The 2 pending tests require a random ticket number collision in a small set — they gracefully skip when no collision occurs. Core multi-winner math is verified by direct unit tests.
✅ Final Verdict
What makes MegaCharm production-ready
- ✅Provably fair randomness — Chainlink VRF, verifiable on-chain
- ✅Trustless daily draws — Chainlink Automation at 12:30 AM UTC
- ✅Gas-safe payouts — pull-payment pattern, no transfers in VRF callback
- ✅Front-running prevention — dual lockout (time window + drawn flag)
- ✅Zero external exploit surface — no critical or high findings
- ✅Emergency controls — pause, stuck-round recovery, unclaimed expiry
- ✅Upgradeable — UUPS with correct storage gap and owner-only authorization
- ✅Battle-tested dependencies — OpenZeppelin v5, Chainlink v1.4
- ✅101 tests passing with comprehensive edge-case coverage