All notable changes to this project are documented here.
Format: Keep a Changelog
Versioning: Semantic Versioning
- Contributor docs:
docs/DEPLOYMENT.md,docs/STORAGE.md,docs/RANDOMNESS.md, anddocs/FAQ.md. - Architecture documentation with factory -> instance -> oracle flow and state-machine diagrams (
docs/ARCHITECTURE.md). - Comprehensive rustdoc comments for all public
raffle-sharedenums, structs, fields, constants, and functions. - Pull request template requiring changelog updates for non-trivial changes.
OracleSeedDeliveredevent documenting per-oracle quorum seed submissions;OracleNotRegisteredandDuplicateOracleSubmissionerror codes (#856).- Deterministic event-doc generator (
scripts/generate_event_docs.py) with a CI sync check fordocs/EVENTS.md(#856). - Rust line-coverage ratchet (
scripts/check_coverage_ratchet.py) withcoverage/coverage-ratchet.jsonbaseline, acoverageCI job, and oracle coverage artifact upload (#830).
- README documentation section now links to architecture docs.
Error::ExceedsMaxTicketsPerAddressrenumbered from65to67(was colliding withCancelTimelockActive);OracleNotRegistered(68) andDuplicateOracleSubmission(69) added (#605, #856).WinnerDrawn.ticket_idnow publishes the 1-based ticket ID (index + 1) instead of the 0-based pool position;docs/EVENTS.mddocuments the index-vs-ID convention (#856).TicketPurchased.effective_ticket_priceis now populated (was an undefined variable that broke the build);TicketGiftedsets it consistently (#856).ContractPaused/ContractUnpausedevent structs defined once inraffle-sharedand re-exported, removing the duplicate E0252 definitions in the factory and instance crates (#856)..github/CODEOWNERSnow uses real maintainer usernames instead of the nonexistent@maintainersteam (#848).generate_error_docs.pyregenerates the wholedocs/ERRORS.mddeterministically from both the instance and factory error enums (#856).
- Standardized event emission model and event catalog (
docs/EVENTS.md). - All
#[contractevent]structs and fields now carry///doc comments with explicit index-vs-ID semantics;max_tickets_per_addressdoc entries ininit.rsandtickets.rsupdated to reflect that the cap is now validated and enforced (#605, #856). - Lifecycle/admin event coverage and event publishing patterns from the previous implementation summary.
- Admin key migration was recorded as a historical note (source file existed but contained no additional details).
- Two-step admin transfer (
transfer_factory_admin/accept_factory_admin) withAdminTransferProposedandAdminTransferAcceptedevents (#87, #339). - Five tests for factory admin two-step transfer flow: propose+accept, rejects duplicate proposals, wrong address rejection, self-proposal clears pending entry, only pending admin can accept (#453, #545).
- Timelocked admin-config operations (
set_config/execute_config_change/cancel_config_change) using a 48-hourTIMELOCK_DELAY_SECONDSconstant andAdminOpenum (#515). - Per-creator raffle index (
DataKey::CreatorRaffles) andget_raffles_by_creatorpaginated query (#533). - Per-category raffle index (
DataKey::CategoryRaffles) andget_raffles_by_categorypaginated query (#439, #653). get_raffle_by_idandget_next_raffle_idO(1) stable-ID lookups replacing the formerVec-based raffle list.get_protocol_statsreturning total raffles created, fee, pause status, and unique participants.set_creation_delay/set_whitelist_statusfor creation rate-limiting and partner whitelisting (#447).CreationRateLimitedevent emitted when non-whitelisted creator hits the cooldown window (#447).- Periodic
StateCheckpointsnapshots everyCHECKPOINT_INTERVAL(1000) raffles withget_checkpointandget_latest_checkpoint_indexaccessors. rescue_tokenson the factory to recover accidentally sent tokens (#534).upgradefunction withFactoryUpgradedevent for on-chain WASM upgrades.- Factory relay helpers
sync_admin,pause_instance, andunpause_instanceto propagate settings to instances (#87). - Re-initialization guard (
DataKey::Initialized); repeatinit_factorycalls are rejected (#288). - Zero-address and self-reference validation for admin and treasury in
init_factoryandset_config(#500). DataKey::InvalidTreasuryerror variant; treasury must be an account address, not a contract address (#241).get_total_volume/record_volumefor per-asset volume tracking across all raffles.- Regression test confirming
FairnessDataoutput format is stable (#532). - Boundary tests for
get_raffles_pagepagination edge cases (#533).
NftTicketTraitcross-contract interface inraffle-shared; raffle instance callsminton a configured NFT contract after each successful ticket purchase (#83).- Early-bird pricing: optional
early_bird_ticket_percentageandearly_bird_discount_bpbasis-point fields onRaffleConfig; discounted price applied while the early-bird quota has not been exhausted (#519). - Optional raffle category/tag field (
category: Option<String>) onRaffleConfig; validated to ≤ 32 bytes, ASCII alphanumerics and hyphens only (#439). PendingPrizestatus: raffle starts inPendingPrizeand transitions toActiveonly after prize is deposited (#225).RaffleFailedevent andFailureReasonenum (ZeroTicketsSold,MinTicketsNotMet) to distinguish failures from explicit cancellations (#232).batch_refund_ticketsfor efficient mass refunds on cancelled raffles (#512).submit_commitfor commit-reveal randomness path; commits keyed by ticket ID (not owner address) so they survive ticket transfers (#311, #387).TicketNftMintedevent emitted after each successfulmintcross-contract call.OwnerTickets(Address)storage key for O(1) per-owner ticket-ID index.DrawingLockreentrancy guard on finalization to prevent concurrent draw corruption (#396).PendingAdminCancelstorage key and 48-hour timelock for admin-initiated cancellations of raffles with tickets sold; immediate cancellation still allowed when zero tickets are sold (#406, #543).execute_admin_cancelto run the delayed admin cancel after the timelock elapses.get_pending_cancelgetter exposing the scheduled cancel timestamp.CancelScheduledevent emitted when an admin cancel is timelocked.ticket_sales_pausedflag withpause_ticket_sales/resume_ticket_salesfunctions and corresponding events (#515).no_deadlineflag inRaffleConfig; raffles without a hard end timestamp stay open until all tickets sell (#357).- Configurable
swap_deadline_seconds(default 300 s, max 3600 s) replacing the hardcoded 300-second swap deadline (#272, #386, #516). set_swap_deadlineadmin function withSwapDeadlineUpdatedevent.- Per-wallet ticket purchase limit (
max_tickets_per_tx) enforced inbuy_tickets(#266, #542). - Per-wallet ownership cap enforced across concurrent purchases (#280).
prize_tokenfield onRafflestruct; defaults topayment_tokenbut can be overridden (#344).- Minimum ticket price constant
MIN_TICKET_PRICE(10 000 stroops) to prevent dust raffles. - Maximum prize amount constant
MAX_PRIZE_AMOUNT(1 × 10²¹) to prevent overflow (#376). MAX_PRIZES(100) cap on prize-tier count (#372).update_oracle_addressadmin function withOracleAddressUpdatedevent for rotating the oracle on active raffles (#195).- Oracle address validated at
inittime forExternalRandomnessraffles: rejects the zero address, self-reference, and non-external configs (#276). - Oracle pending-flag stored in persistent (not instance) storage (#353).
- Minimum ledger delay (
RANDOMNESS_MIN_DELAY_LEDGERS = 10) before oracle randomness can be fulfilled, preventing same-ledger manipulation (#514). trigger_randomness_fallbackwithFallbackTooEarlyguard;do_refund=falsepath finalizes via internal PRNG,do_refund=truecancels with full refunds (#443, #539).- VRF proof bound to raffle contract address and request context to prevent cross-raffle signature replay (#410, #523).
CommitRevealEntry { committer, hash }struct keyed byDataKey::CommitEntry(u32)(ticket ID).AccumulatedFeesinstance storage key;withdraw_feesrequiresFinalizedorClaimedstatus (#367).withdraw_feesfinalized-state guard (#367).emergency_withdrawwith 90-day delay post-finalization; respectsno_deadlineflag by usingRandomnessRequestLedgerinstead ofend_timefor the delay (#368, #407).rescue_tokenson the instance to recover accidentally sent non-prize tokens.wipe_storageclears all per-ticket and per-owner keys after a terminal state, cleaning upPendingAdminCancelas well.TicketBuyerspersistent key tracking all buyers for efficient storage wipe.require_valid_role_addresszero-address + self-reference validation inset_admin(#195).- Seven domain submodules (
admin,claim,draw,helpers,init,tickets,views) extracted from the monolithiclib.rs; each submodule is < 300 lines (#536). - Three oracle timeout-fallback tests: fallback-with-refund cancels, fallback-without-refund uses internal seed, fallback-too-early is rejected (#443, #539).
- Full lifecycle happy-path test: init → deposit → 3 buys → finalize → claim, asserting balances, status transitions, treasury fees, and
total_distributed == prize_amount(#440). buy_ticketsbudget test asserting final 100-ticket batch stays within Soroban CPU/memory limits (#449).- Commit-reveal randomness path coverage (#302).
- Raffle invariants and
DrawingLockcoverage tests; oracle startup config validation (#422, #442, #444, #446).
NftTicketTraitcontract client trait (NftTicketClient) for cross-contract NFT minting.FailureReasonenum (ZeroTicketsSold,MinTicketsNotMet).FairnessDatastruct withseed,randomness_source,ticket_ids,winning_ticket_indices,draw_timestamp,draw_sequence.PaginationParams,PageResultRaffles,PageResultTicketstypes for paginated queries.AdminOpenum (SetConfig,UpdateWasmHash) for timelocked operations.RandomnessTypeenum (Prng,Vrf,Fallback).impl_require_admin!andimpl_require_not_paused!macros shared across factory and instance.- Protocol-wide constants module (
raffle_shared::constants):ORACLE_TIMEOUT_LEDGERS,MAX_DESCRIPTION_LENGTH,MAX_TICKETS_LIMIT,MAX_PRIZES,MAX_CATEGORY_LENGTH,MIN_TICKET_PRICE,MAX_PRIZE_AMOUNT,DEFAULT_CLAIM_LOCKUP_SECONDS,MAX_CLAIM_LOCKUP_SECONDS,DEFAULT_SWAP_DEADLINE_SECONDS,MAX_SWAP_DEADLINE_SECONDS,EMERGENCY_WITHDRAW_DELAY_SECONDS,TIMELOCK_DELAY_SECONDS,CHECKPOINT_INTERVAL,MAX_PROTOCOL_FEE_BP,DEFAULT_PAGE_LIMIT,MAX_PAGE_LIMIT. - Comprehensive rustdoc comments for all public enums, structs, fields, constants, and functions.
MAX_CATEGORY_LENGTH(32) constant for raffle category validation (#439).
TxSubmitterServicewithSorobanRpc, exponential-backoff retry, sequence management, and confirmation polling (#416).KeyServicewith startup validation, KMS/Vault adapter stubs, and secret zeroization on shutdown (#415).- Event listener polling
RandomnessRequestedevents from the Soroban RPC; stores ledger checkpoint; enqueues oracle work (#418). - ESLint and Prettier added to the oracle service (#522).
- VRF proof message bound to raffle address and request context to prevent cross-raffle proof replay (#410).
- Automated release pipeline (
.github/workflows/release.yml) triggered onv*.*.*semver tags; buildsraffle.wasmandraffle_instance.wasmand attaches them to the GitHub release (#467, #518). - WASM binary size check in CI failing the build if any artifact exceeds the 128 KB Soroban limit (#465, #505).
cargo denyfor license compliance and dependency vetting (#469, #506).CODEOWNERSfile assigning review responsibilities (#665).docs/README.mdindex linking to all documentation files (#665).workflow_dispatchtrigger on CI workflow to allow manual runs on fork branches.
- Crate renamed from
raffletoraffle-factoryto match its actual role (#541). Contractstruct inraffle-instancerenamed toRaffleInstancefor clarity (#540).get_rafflesrenamed toget_raffles_pagefor consistency with paginated query naming conventions (#373).- Raffle storage migrated from
Vec-based list to O(1) stable-ID map (DataKey::RaffleById(u32)+DataKey::NextRaffleId) so read and write costs are constant regardless of raffle count. RandomnessSeedstorage tier changed to persistent (was instance) to survive ledger entry expiry (#383).- Protocol fee charged at ticket purchase only; removed duplicate deduction at
claim_prize(#411). finalize_raffleskipstransition_to_drawingwhen status is alreadyDrawing(set bybuy_ticketson sell-out); removed the redundant top-levelDrawingLockguard that blocked legitimate finalizations.DrawingLockreentrancy guard is now a separateDataKey::DrawingLockinstance-storage flag rather than inline logic.- Commit-reveal entries keyed by ticket ID (
DataKey::CommitEntry(u32)) instead of owner address so commits survive ticket transfers (#311). PrngWinnerSelectionandOracleSeedWinnerSelectionextracted as implementations of theWinnerSelectionStrategytrait (#537).- Internal PRNG seed construction uses four base inputs in
build_internal_seed;PrngWinnerSelectionadds tickets sold in a second hash. require_valid_role_addressuses an XDR-based zero-contract check (WASM-compatible) instead ofaddress.exists()(#520)..gitignoreupdated to excludetarget/,*.wasm,Cargo.lock,oracle/node_modules/,oracle/dist/,.stellar/,.soroban/,deployments/mainnet.json,.env,.DS_Store(#499, #544).Cargo.tomlfiles for all crates now includelicense,description,repository,authors,keywords, andcategoriesmetadata (#493, #525).rust-version(MSRV) added to workspaceCargo.toml(#309, #388).actions/checkoutupgraded from v3 to v4 across all workflow files (#464, #517, #520).- Project-wide
.editorconfigadded for consistent editor settings (#542).
set_adminnow rejects zero address and self-assignment to prevent admin lockout (#394, #520).init_factorynow validates admin and treasury addresses against zero and self-reference (#500).finalize_raffleDrawingLock reentry bug fixed: lock is now set correctly before and cleared after the draw (#396, #520).refund_ticketdouble reentrancy guard removed; singleGuardRAII wrapper used after status check (#397).record_volumeguarded withchecked_addto prevent total-volume overflow (#362).claim_prizededucts fee only at purchase, not again at claim (#411).- Oracle
request_idcollision prevented by including raffle address in the hash (#262). sync_adminnow usestry_invoke_contractso errors propagate rather than panic (#261).emergency_withdrawrespectsno_deadlineflag in theDrawingpath by usingRandomnessRequestLedgerfor the delay (#407).- Oracle verification payload uses XDR context (raffle address + request ID) to prevent signature replay attacks (#410, #523).
- Oracle pending-flag stored in persistent storage, preventing it from expiring during a long oracle wait (#353).
refund_ticketcorrectly decrements the buyer ticket count (#282, #324).prize_net_amountsubtraction guarded against underflow (#281, #327).withdraw_feesrequiresFinalizedorClaimedstatus (#367).buy_ticketsenforcesmax_ticketsandallow_multipleunder concurrent purchases (#177, #208).- Ticket counter kept consistent with a persistent
NextTicketIdstorage key (#319). TicketPurchasedevent now includes the correctraffle_idfield (#341).RaffleFinalizedevent emission added toprovide_randomnesspath where it was previously missing (#347).- Raffle creation rejected when
end_timeis in the past (#274, #390). RandomnessSeedchanged to persistent storage so it survives ledger-entry expiry (#383).- Minimum ledger delay enforced before oracle randomness can be fulfilled (#514).
- Protocol fee upper bound check added to
set_config(#301, #374). propose_adminrejects burnt/unowned addresses via zero-address validation (#371).admin-key-changehistorical event recorded inAdmin-key-change.md(no contract changes).RaffleStatus::Finalizingdead enum variant removed; Drawing-to-Finalized transition is atomic (#403, #526).require_registered_raffledead factory helper removed; incorrect auth semantics documented inMIGRATION-426.md(#412, #526).- Instance
rescue_tokensprevents withdrawing the prize token while prize is deposited. TxSubmitterServiceTypeScript compile errors resolved (#416).- Error enum spacing made consistent across
Errorvariants (#524).
docs/DEPLOYMENT.md: step-by-step deploy flow usingscripts/for testnet and mainnet (#569, #656).docs/STORAGE.md: storage key layout, tier selection rationale, and TTL bump policies for both contracts (#570, #656).docs/RANDOMNESS.md: tradeoffs between Internal, External, and CommitReveal modes with threat model (#571, #656).docs/FAQ.md: contributor setup troubleshooting, common build errors, and development environment tips (#572, #656).docs/ARCHITECTURE.md: factory → instance → oracle flow and raffle lifecycle state-machine diagrams (#513).docs/COMMIT_REVEAL.md: multi-phase commit-reveal protocol specification with code examples (#521).docs/EVENTS.md: complete on-chain event catalog for all 44+ events, enum value tables, indexer notes, and emission conditions (#504).docs/FEE_MODEL.md: two-stage fee collection model and revenue distribution (#530).docs/MIGRATION-426.md: storage layout migration guide for PR #426, including removal of dead code (#526).docs/README.md: documentation index linking to all guides (#665).CODEOWNERS: review responsibility assignments (#665).README.mdupdated with current crate names, documentation links, andmetadata_hashusage guide.- Comprehensive rustdoc on all public
raffle-sharedtypes, constants, and functions.
- Commit-reveal randomness source.
- Max tickets per transaction cap.
- Claim lockup delay configuration.
- Drawing/finalization guard state.
- Admin zero-address validation in
set_admin. - Duplicate winner selection in oracle finalization path.