Repository navigation
feat(predictify-hybrid): time-gated update_market_question for market creators (closes #004) - #336
Merged
dultimateade merged 2 commits intoOct 24, 2026
Conversation
Market creators can correct a typo in a market question only while no stake has been committed, the market is still Active, and its end_time has not passed. The metadata commitment is refreshed so cached metadata fails verification, and the existing MarketDescriptionUpdated event is emitted. Closes dultimateade#4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Closes #4.
Market creators could not correct a typo in a published question. The only edit path on
mainisupdate_event_description, which is admin-only, so a creator who mistypes "Wil BTC close above 100k?" has to find the admin, and the admin has to trust that the request is cosmetic. This PR adds a creator-facing, time-gatedupdate_market_questionthat is valid only while nobody has committed anything to the market.total_staked == 0is necessary but not sufficient, so the function also refuses edits when:market.admin;Active(e.g.Ended,Resolved,Cancelled);env.ledger().timestamp() >= market.end_time— a market that has stopped taking bets must stop accepting edits too;votesorstakesare non-empty even iftotal_stakedwas never updated (defensive: the gate must not depend on one bookkeeping field staying consistent).Once the first stake lands, the question is frozen. Anyone who has placed money has placed it against specific text, and resolution criteria must not move afterwards.
Root cause
Not a bug in existing code — a missing capability, plus a design problem in the place people would naturally extend.
PredictifyHybrid::update_event_description(contract entrypoint) andMarketStateManager(state layer) already exist and already do the right thing mechanically: they replacequestion, callMarket::refresh_metadata_commitment, invalidate the read cache, and emitMarketDescriptionUpdatedEvent. But the entrypoint is gated onrequire_primary_admin, so the only actor who can fix a creator's typo is the admin.The tempting "fix" — loosening
update_event_descriptionto also accept the market creator — was rejected because it would silently widen an admin entrypoint's authority for every existing integration (and for every market, including ones the caller does not own). Instead the creator gets its own entrypoint with its own authorization rule, and the shared state logic is factored intoMarketStateManager::update_questionso the two paths cannot drift in what they store.There is a second reason the creator path cannot simply reuse the admin one: the admin path's
end_timebehaviour is not defined for creators, and a creator editing an expired market would change metadata that a pending oracle/resolution flow may already have read.The fix, and why this shape
MarketStateManager::update_question(markets.rs) — one place that owns the mutation:InvalidQuestionwhen empty,QuestionTooLongaboveMAX_QUESTION_LENGTH = 500, matching the documented 1–500 character API);MarketNotFound), then enforces creator (Unauthorized), state (InvalidState),end_time(MarketClosed) and the stake/vote gate (InvalidState);question, callsrefresh_metadata_commitment, persists throughupdate_market(which also bumps the TTL and invalidatesMarketReadCache), then emitsMarketDescriptionUpdatedEventwith the old and new text.PredictifyHybrid::update_market_question(lib.rs) — a thin entrypoint:creator.require_auth()then delegate. Keeping the entrypoint thin matches how the rest oflib.rsis organised (entrypoints authorize + delegate,markets.rsowns state transitions).verify_market_metadata(market_id, expected)compares against the stored commitment, so a client holding the old commitment now fails verification instead of silently rendering stale text. A test asserts the stored commitment equalsMarket::compute_metadata_commitment(question, outcomes, oracle_config)after the edit.Error::InvalidQuestion,QuestionTooLong,InvalidState,MarketClosed,UnauthorizedandMarketNotFoundalready describe every rejection. Adding variants would have expanded an enum that is already too large for the XDR spec (see follow-ups) for no benefit.src/tests/update_market_question_tests.rs, registered insrc/tests/mod.rs) — 9 cases: happy path with commitment assertion, exact-length boundary, rejection after a stake, after a vote, by a non-creator, afterend_time, on a non-active market, for an empty question, for an over-long question (asserting the stored question was not modified).CHANGELOG.md— one entry underUnreleased / Added, as required by the repo's changelog check.How it was tested
Honest status: CI cannot be green on this repo, and I could not execute the tests here.
mainat230856fdoes not compile at all:This is pre-existing, unrelated to this PR, and bigger than this issue (a July commit deleted ~56 modules; see follow-ups). What I could verify:
cargo check -p predictify-hybridfails with exactly the same 20 parse-stage errors before and after this branch (git stash→ check →git stash pop). No new diagnostics are introduced by the change. Because parsing aborts early, type checking is not reached, so this is not proof that the new code type-checks — it is proof it adds no new failure.rustfmt --edition 2021 --checkparses every touched file (lib.rs,markets.rs,tests/mod.rs, the new test file) without syntax errors.mainfor signature and semantics:Market::refresh_metadata_commitment,Market::compute_metadata_commitment,MarketStateManager::update_market(persists + bumps TTL + invalidates cache),EventEmitter::emit_market_description_updated(env, market_id, old, new, admin), and the sixErrorvariants used.PredictifyHybridClient, using the sametry_*/assert_contract_erroridiom as the existing suites, so they run unchanged as soon as the crate builds.I would rather tell you this than claim a green run. To review the behaviour end to end,
cargo test -p predictify-hybrid -- update_market_question_testsmust be run after the crate is restored to a compiling state.Follow-ups worth filing separately
maindoes not compile. 56+ modules referenced bylib.rs(utils,validation,versioning,gas,config,balances,audit,governance,metadata_limits,audit_trail,rate_limiter,circuit_breaker, …) are missing, andvoting.rs/disputes.rscarry orphan-brace corruption from-X theirsmerges. This blocks all verification for every PR in the repo.#[contracterror] Errorhas 132 variants. The XDR spec caps a contract error enum at 50, sosoroban-sdk-macrospanics while expanding the enum; the crate cannot compile even after the missing modules are restored. It needs to be split into domain enums (markets/fees/disputes/oracles) plus a thin top-level error.create_markettakes 13 arguments. Soroban caps contract arguments at 10, so the entrypoint cannot be exported as written; it needs aCreateMarketParamsstruct.update_event_outcomesis admin-only today and could take a creator variant with the same gates.update_event_descriptiondoes not checkend_time. The admin path checks bets/votes only; adding the same time gate there would make the two edit paths consistent.Checklist
update_market_question,total_staked == 0gate, creator authorization)CHANGELOG.mdupdatedcargo test/ CI — blocked by the pre-existing non-compilingmain(documented above)Closes #4