TimeCurve: replace startSale() with owner-only startSaleAt(uint256 epoch) #114
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#114
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?
Summary
Replace
TimeCurve.startSale()withstartSaleAt(uint256 epoch):onlyOwner(todaystartSale()isexternalwith noonlyOwner— lock this down as part of the change).saleStart == 0/ equivalent invariant; document the exact error string).saleStart = epoch(Unix seconds — align naming withTimeCurve.sol:saleStartis seconds).deadline: derive consistently from the chosen start instant (today:deadline = block.timestamp + initialTimerSec). With a scheduled start, the natural analogue isdeadline = epoch + initialTimerSecso the initial window length does not depend on when the owner’s tx is mined. Call out in implementation if a different rule is chosen.SaleStartedwith the same event shape if possible; the first argument should matchsaleStart(today it isblock.timestamp, which equalssaleStartat call time).Product / rationale
Operators need to announce a fixed chain-time start independent of mempool / block production at “go.” Frontend and indexer already treat
saleStartas authoritative for phase UX; scheduling it explicitly avoids drifting “start now” semantics.Open acceptance questions (implementer)
epochvalidation (e.g. must be future vsblock.timestamp, tolerance for slight past timestamps for same-block ordering, alignment with indexer “chain now”).epoch == saleStartand interaction withinitialTimerSec/ extensions after first buy (unchanged; only anchor changes).Repo touch-points (everything currently tied to
startSaleor sale bootstrap)Onchain (
contracts/)contracts/src/TimeCurve.solstartSaleAt,saleStartwrite,deadline,SaleStartedpayload,onlyOwner, insufficient-launched-token checks unchanged.contracts/src/TimeCurveBuyRouter.solstartSalecall — onlysaleStart()reads; unchanged unless semantics ofsaleStart/ phase change.Interfaces / scripts (
contracts/script/)contracts/script/DeployDev.s.soltc.startSale()after funding — switch tostartSaleAt(...)with an explicit timestamp (e.g. current block time for local/dev parity, or env-driven for staging).contracts/script/AnvilSameBlockDrill.s.soltc.startSale()→startSaleAt.contracts/script/SimulateAnvilRichState.s.solITimeCurvedoes not declarestartSaletoday; addstartSaleAtonly if this script ever needs to start a sale (likely not).Tests (
contracts/test/)All
tc.startSale()/t.startSale()call sites need updating; non-exhaustive list from current tree:contracts/test/TimeCurve.t.sol— includestest_startSale,test_startSale_reverts_twice,test_startSale_insufficient_launched_tokens_revertsand many fixtures.contracts/test/TimeCurveBuyRouter.t.solcontracts/test/TimeCurveWarBowCl8yBurns.t.solcontracts/test/TimeCurveReferral.t.solcontracts/test/DevStackIntegration.t.sol— assertssaleStart > 0/ deadline ordering.contracts/test/TimeCurveInvariant.t.solcontracts/test/NonStandardERC20.t.solcontracts/test/VerifyTimeCurveBuyRouterAnvil.t.sol— readssaleStart()only.New / extended Forge tests should cover
onlyOwner, double-start revert,epochvsdeadlinemath, and (if enforced)epochnot too far in the past.Indexer (
indexer/)SaleStartedABI inindexer/src/decoder.rs: signature unchanged if the event is kept; verify decodedstartTimestampstill matches storedsaleStartafter emission change.indexer/src/persist.rs,indexer/tests/integration_stage2.rs— regression if event args or persisted fields change.Frontend (
frontend/)saleStart()only (frontend/src/lib/abis.tstimeCurveReadAbi). NostartSaleintimeCurveWriteAbitoday; addstartSaleAt(uint256)if operator UI / admin flows need typed calldata later.startSale(string in TS — behavior change is indirect viasaleStartvalues and countdown UX (docs/frontend/timecurve-views.md, issue #48). Update docs if scheduling introduces saleStartPending windows that are visibly long.Bots (
bots/)bots/timecurve/src/timecurve_bot/abis/timecurve.jsonincludessaleStartread; regenerate or appendstartSaleAtif bots ever invoke it.Docs / runbooks / skills
docs/testing/invariants-and-business-logic.md— Sale start row andstartSaletest map.launchplan-timecurve.md— bootstrap step referencingstartSale.docs/operations/stage2-run-log.md.docs/qa/YO-TimeCurve-QA-Checklist.md.skills/play-timecurve-doubloon/SKILL.md—SaleStartedmention.Shell / tooling
scripts/delegate deploy toDeployDev(scripts/lib/anvil_deploy_dev.sh,scripts/start-local-anvil-stack.sh); no literalstartSalein shell — behavior followsDeployDevchanges.Definition of done
TimeCurve:startSaleAt(uint256 epoch)withonlyOwner, single transition,saleStart,deadline,SaleStarted, tests.SaleStartedemission changes.startSaleAtand operator flow.bots/timecurveABI updated if the bot ever needs the new function.Related guardrails
saleStart— issue #48,docs/frontend/timecurve-views.md.mentioned in commit
5edd13263fImplemented on
main(merge commit06ffd0c) — #114Summary
TimeCurve: Removed publicstartSale(); added ****startSaleAt(uint256 epoch):saleStart = epoch,deadline = epoch + initialTimerSec,SaleStarted(epoch, deadline, totalTokensForSale)(ABI shape unchanged).epoch != 0,epoch >= block.timestamp, double-start (**`already started``), insufficient launched tokens (unchanged strings).buy/buyFor/ WarBow CL8Y paths /claimWarBowFlagrequireblock.timestamp >= saleStart→ revert *TimeCurve: sale not live* if scheduled in the future.currentMinBuyAmount,currentMaxBuyAmount,currentCharmBoundsWad,currentPricePerCharmWaduse elapsed = 0 untilblock.timestamp >= saleStart.DeployDev/AnvilSameBlockDrillcall **startSaleAt(block.timestamp)**.timeCurveWriteAbi+ bottimecurve.jsonincludestartSaleAt; docs / invariants / play + contributor skills cross-linked (INV:#timecurve-startsaleat-issue-114).CI / verification run by implementer
forge test(210 tests)cargo testinindexer/(20 tests incl. integration)npm ci+vitest runinfrontend/(254 tests)/cc @brouie — please sanity-check behaviour on a staging or local deploy.
Verification checklist for @brouie
DeployDev**: after fund +startSaleAt(block.timestamp),saleStart()equals that timestamp anddeadline() == saleStart() + initialTimerSec().startSaleAtreverts (OpenZpelinOwnableUnauthorizedAccount).buy* reverts *sale not live* until warp/chain reachessaleStart; then buys succeed.epoch == 0→ **invalid epoch*;epoch < block.timestamp→ **epoch in past*.startSaleAtafter start → **already started**.SaleStartedand persistsstartTimestamp == saleStart(shape unchanged).saleStartPending) stays consistent with indexer hero clock vssaleStart(`timecurve-views.md`` scheduled starts anchor).Follow-up — the previous automation note mangled some markdown (shell interpreted backticks).
Merge SHA (correct):
06ffd0con main (typo fix: not 06ffc0).mentioned in issue #115
marked this issue as related to #115
verified on
06ffd0cend-to-end with live deploy walk. forge: 210/210 pass. cargo test in indexer/: 19 unit + 1 integration pass,roundtrip_sale_startedok (item 6, ABI shape unchanged). vitest in frontend/: 258/258 pass. item 1: fresh deploy →saleStart=1777852330, deadline=1777938730, initialTimerSec=86400→1777852330+86400 == 1777938730exact match per source line 330. item 2: anvil acct #1 callingstartSaleAt→OwnableUnauthorizedAccount(0x70997970...79C8)per OZ Ownable. item 3 first half: redeployed with 600s future epoch, chain time 594s pre-start, owner buy attempt →Error("TimeCurve: sale not live")with hex54696d6543757276653a2073616c65206e6f74206c697665matching ASCII. item 3 second half: anvil_increaseTime 600s past saleStart → buy returnsstatus 1 (success)gasUsed 644668. item 5: owner doublestartSaleAt→Error("TimeCurve: already started")(saleStart==0 guard at line 322 fires before items 4 guards which is why item 4 cant reach live without a 3rd fresh contract — covered by forge unitstest_startSale_reverts_twiceetc + source review of lines 323-324). item 7: spun up vite against the pre-start deploy,/timecurveshowed PRE-LAUNCH badge + "TimeCurve Opens In" copy + 9:46 countdown + Buy CHARM card disabled ("Coming soon"). Afteranvil_increaseTime600s + browser refresh, transitioned to SALE LIVE badge + "Time Left" copy + 23:51 deadline countdown + Buy CHARM enabled with live 1.000530 CL8Y/CHARM ticker. phase swap clean end-to-end against indexer-anchored chain clock. note for future runs: had to comment outwarp_to_timecurve_sale_start "${TC}"at line 234 ofscripts/start-local-anvil-stack.shto get a pre-start window for item 3a, since the script always lands chain time at saleStart by deploy completion andanvil_setNextBlockTimestamprejects backward time moves. consider an opt-out env likeYIELDOMEGA_DEV_SKIP_SALE_START_WARP=1if you want this verification path repeatable without local script edits. all 7 items pass.mentioned in issue #96
mentioned in issue #118
mentioned in issue #124
mentioned in commit
dadc16b3cdmentioned in issue #136
mentioned in commit
6a4f054fe8mentioned in issue #207
mentioned in issue #232