QA: live contract-state checklist — MegaETH mainnet (Arena v2, chain 4326) #357
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#357
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Read-only checklist for walking the live Arena v2 deploy on MegaETH mainnet (chainId 4326) and confirming the on-chain state matches what
DeployProduction.s.sol+ the production*.t.soltests intend. Everything here iscast call/cast storageonly -- no keys, no broadcasts. Tick a box when the on-chain value matches the expected; for the read-only ones, tick when you've eyeballed it and it's sane.I scripted every line below into a one-shot sweep that prints PASS/FAIL/REVIEW + a summary, so this can run automated or get walked by hand. Ran it against the live deploy and got 77 PASS / 0 FAIL / 47 REVIEW -- all the deterministic wiring/config matched; the REVIEW lines are the non-deterministic ones (ownership/TWAP/balances/lifecycle).
How to run
Role hashes (constant on every AccessControl contract):
MINTER_ROLE=0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6DEFAULT_ADMIN_ROLE=0x00000000000000000000000000000000000000000000000000000000000000000x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc(read withcast storage)cast prints big uints with a scientific hint (
86400 [8.64e4]) -- the leading integer is the real value. Addresses come back checksummed, compare case-insensitive. A revert or RPC error means the call failed -- treat that as a red flag, not a pass.Registry under test
indexer/address-registry.megaeth-mainnet.json(gitCommit99e3478b, deployBlock 19853582)0xc3654b4f879937b767afbb64b7c230ff436d23420xfBAa45A537cF07dC768c469FfaC4e88208B0098D0x998fb7989425ed0c14545ce27a5cc3a9d9349ac10x7abb3c243a938d1b809b07b8b3f1d60044b466980xc729ee049b18669e3c4ebfebcac676da7992a2460xba39cea0e5ef6808d8cb926c722877480049e0ee0x3151c335224f5bbfde174b15dba9523a0694d5820xd5f3eff25caa075f1f33dbf9ca6883c741d408a20xcd4eb82cfc16d5785b4f7e3bfc255e735e79f39c0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e0x42000000000000000000000000000000000000060xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7Same DOUB must show up from TimeArena, PodiumVaults, ReferralRegistry and the BuyRouter -- both ends of each wiring pair are kept on purpose.
TimeArena --
0xba39cea0e5ef6808d8cb926c722877480049e0eeUUPS proxy, Ownable2Step. All calls hit the proxy.
owner()-> admin0xcd4e..f39c(deployer if handoff not yet accepted)pendingOwner()->0x0(nonzero = handoff started, not accepted)doub()-> DOUB0xc365..2342podiumVaults()->0x7abb..6698(cross-check vs PodiumVaults.arena())referralRegistry()->0xc729..a246(cross-check vs ReferralRegistry.timeArena())playCred()->0x998f..9ac1(cross-check vs PlayCred MINTER_ROLE grant)timeArenaBuyRouter()->0x3151..d582charmPriceWad()/epochCharmAnchorWad()/effectiveCharmPriceWad()-> all > 0 (TWAP-derived ~$1/CHARM)charmAnchorKumbayaRouter()->0x0(spot oracle stays unset on prod)buyChargeIntervalSec()= 300,buyCooldownSec()= 300,maxBuyCharges()= 5,burstBuyCooldownSec()= 15podiumTimerExtensionSec[0..3]-> 120, 60, 90, 300podiumInitialTimerSec[0..3]-> 86400, 43200, 64800, 172800podiumTimerCapSec[0..3]-> 345600, 172800, 259200, 691200podiumResetBelowRemainingSec[0..3]-> 780, 240, 510, 3300podiumResetToRemainingSec[0..3]-> 900, 300, 600, 3600paused()-> falsearenaStart()epochAnchorTimestamp()deadline()lastBuyEpoch()podiumTimerArmed[0..3]-- all 0/false on an un-started arenacast storage 0xba39cea0e5ef6808d8cb926c722877480049e0ee 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url $RPCPodiumVaults --
0x7abb3c243a938d1b809b07b8b3f1d60044b46698Plain Ownable (no pendingOwner, not a proxy -- no impl slot). Pools commingled.
doub()-> DOUB0xc365..2342arena()-> TimeArena0xba39..e0ee(cross-check vs TimeArena.podiumVaults())owner()-> admin0xcd4e..f39c(nonzero)activePools/seedPools/futurePools[0..3]-> all == PodiumVaults self0x7abb..6698(commingled default)activePoolBalance/seedPoolBalance/futurePoolBalance[0..3]-> read (0 until funded)ReferralRegistry --
0xc729ee049b18669e3c4ebfebcac676da7992a246UUPS proxy, Ownable2Step. doubToken()/registrationBurnAmount() route through the arena, so a clean return also proves the arena link is live.
timeArena()-> TimeArena0xba39..e0eedoubToken()andcl8yToken()(legacy alias) -> DOUB0xc365..2342registrationBurnAmount()-> > 0 (tracks charmPriceWad by design)owner()-> admin0xcd4e..f39c,pendingOwner()->0x0BURN_ADDRESS()->0x..dEaDcast storage)PlayCred --
0x998fb7989425ed0c14545ce27a5cc3a9d9349ac1ERC20 + AccessControl. NOT a proxy (no impl slot), NOT Enumerable. Custody = whoever holds DEFAULT_ADMIN_ROLE (no owner()).
hasRole(MINTER_ROLE, TimeArena)-> true (only the arena mints/burns CRED -- the core wiring proof)hasRole(DEFAULT_ADMIN_ROLE, deployer)-> false (deployer renounced after handoff)hasRole(DEFAULT_ADMIN_ROLE, admin 0xcd4e..f39c)-> truegetRoleAdmin(MINTER_ROLE)-> DEFAULT_ADMIN_ROLE (0x000..0)name()= "Play CRED",symbol()= "CRED",decimals()= 18supportsInterface(0x7965db0b)-> true (AccessControl)totalSupply()-> read (0 valid if arena hasn't minted CRED yet)TimeArenaBuyRouter --
0x3151c335224f5bbfde174b15dba9523a0694d582Plain Ownable2Step, not a proxy. The seven address/token fields are constructor immutables.
timeArena()-> TimeArena0xba39..e0eekumbayaRouter()->0xE5Bb..470edoub()-> DOUB,cl8y()->0xfBAa..098D,weth()->0x4200..0006,stableToken()-> USDm0xFAfD..79E7doubSurplusRecipient()-> nonzero (currently deployer0xd5f3..08a2; fee sink, not a role)owner()-> admin0xcd4e..f39c,pendingOwner()->0x0PAY_ETH/PAY_STABLE/PAY_CL8Y-> 0 / 1 / 2Doubloon (DOUB) --
0xc3654b4f879937b767afbb64b7c230ff436d2342ERC20Burnable + AccessControl. Reused launch token (not redeployed here) -- role holders belong to the original launcher, so the hasRole lines are read-and-report, not asserts.
name()= "Doubloon",symbol()= "DOUB",decimals()= 18totalSupply()-> > 0 (pre-existing supply w/ Kumbaya liquidity)MINTER_ROLE/DEFAULT_ADMIN_ROLEconstants match (bytecode sanity)hasRole(MINTER_ROLE, TimeArena)-> read (expected false -- arena spends/burns DOUB, doesn't mint it)hasRole(DEFAULT_ADMIN_ROLE, deployer)/hasRole(MINTER_ROLE, deployer)-> read & reportWhat the last sweep showed
All wiring, config and ownership lined up -- 0 fails. Ownership is fully handed to the CL8Y manager
0xcd4e..f39con every contract (pendingOwner cleared, PlayCred admin moved off the deployer; deployer key holds nothing but the buy-router surplus sink). Per-podium timers match the product table, charm price is TWAP-derived ~$1/CHARM.One thing to call out: the arena is deployed + fully wired + ownership-secured but not started --
arenaStart,deadline,lastBuyEpochall 0, podium timers unarmed, pools unfunded, CRED supply 0, not paused. Reads as intentional pre-launch; flagging so it's on record. Re-running the checklist afterstartArena()should flip the lifecycle lines and keep everything else green.@PlasticDigits filing this as the live-contract-state verification record per your ask -- can confirm the not-started state is just pre-launch when you get a sec.
changed the description
mentioned in issue #358
Should do as many checks as possible before start Arena is called (at scheduled time on yieldomega.com)
Handover should have occurred for owner, if not needs to be flagged
Re-swept the live 4326 state to answer both things you flagged.
Handover — done, and it's the finalized state, nothing to flag.
Ownership sits fully with the CL8Y manager
0xcd4e..f39c(the address documented in contracts/README.md) on every contract, and it's accepted, not a mid-flight transfer:owner()all =0xcd4e..f39cpendingOwner()=0x0-- the Ownable2StepacceptOwnership()has actually been called. The chain is a step past what DeployProductionOwnership.t.sol snapshots (that test asserts owner=deployer / pendingOwner=admin, i.e. handoff in flight). Live is one step further: finalized.0xd5f3..08a2retains nothing but the buy-router DOUB surplus sink, which is a fee recipient, not a roleSo the "if handover hasn't happened, flag it" branch doesn't trigger -- it's clean.
Pre-start checks -- everything runnable before startArena is green.
Full read-only sweep (cast only) against mainnet.megaeth.com/rpc at block 20089546: 77 PASS / 0 FAIL / 47 REVIEW, byte-identical to the baseline I filed at block 20046419 -- no drift across the ~43k blocks between. Arena is deployed, fully wired, ownership-secured, and still dormant:
Didn't just trust my own sweep script -- cross-checked three ways: the script, a raw cast dump with no comparison logic in the path, and an independent re-derivation of the intended values straight from DeployProduction.s.sol + DeployProductionBuyRouter.s.sol + the three DeployProduction*.t.sol tests (48 invariants, all matched). All three agree.
What's left is inherently post-start: the lifecycle lines (arenaStart / deadline / timers / CRED supply) only flip once startArena fires from the site. When you kick that off at the scheduled time, ping me and I'll re-run the sweep to confirm those flip correctly and everything else holds. @PlasticDigits
Everything verifiable pre-launch is verified on my side.