Legend: 🆕 New (unassigned)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that expire_match correctly refunds only player1 when player1 deposited but player2 never did, and the timeout has elapsed.
Tasks:
- Create match, player1 deposits, advance ledger past timeout
- Call
expire_match - Assert player1 balance is restored to initial amount
- Assert player2 balance is unchanged
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test confirms that expire_match returns Error::MatchNotExpired when called before the configured timeout has elapsed.
Tasks:
- Create match, player1 deposits
- Call
try_expire_matchwithout advancing the ledger - Assert
Err(Ok(Error::MatchNotExpired))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
expire_match should only work on Pending matches. No test verifies it returns Error::InvalidState when called on an Active match.
Tasks:
- Create match, both players deposit (match becomes Active)
- Advance ledger past timeout
- Call
try_expire_match - Assert
Err(Ok(Error::InvalidState))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
expire_match emits a ("match", "expired") event but no test verifies this event is emitted with the correct match_id.
Tasks:
- Create match, advance ledger past timeout
- Call
expire_match - Assert event with topics
("match", "expired")and datamatch_idis present
Status: Open — unassigned
Labels: bug, security
Priority: High
Estimated Time: 30 minutes
Description:
update_oracle sets the new oracle address without checking it is not the escrow contract's own address. Setting the oracle to the contract itself would allow anyone to satisfy oracle.require_auth() trivially.
Tasks:
- Add
if new_oracle == env.current_contract_address() { return Err(Error::InvalidAddress) }guard - Add test asserting self-address is rejected
Status: Open — unassigned
Labels: testing, security
Priority: High
Estimated Time: 15 minutes
Description:
No test verifies that update_oracle can only be called by the admin. A non-admin caller should be rejected.
**Tasks:**t
- Call
try_update_oraclefrom a non-admin address - Assert auth failure
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
update_oracle emits an ("admin", "oracle_up") event with the old and new oracle addresses, but no test verifies the event data is correct.
Tasks:
- Call
update_oraclewith a new oracle address - Assert event topics are
("admin", "oracle_up") - Assert event data contains
(old_oracle, new_oracle)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that calling deposit on a Cancelled match returns Error::InvalidState. The guard exists but is untested for this specific state.
Tasks:
- Create match, player1 cancels it
- Call
try_depositfor player2 - Assert
Err(Ok(Error::InvalidState))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_match returns Error::MatchNotFound for an ID that was never created.
Tasks:
- Call
try_get_match(9999)on a fresh contract - Assert
Err(Ok(Error::MatchNotFound))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test explicitly checks that is_funded returns false after only player1 deposits. The existing test only checks the final true state.
Tasks:
- Create match, player1 deposits
- Assert
is_fundedreturnsfalse - Player2 deposits
- Assert
is_fundedreturnstrue
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description: No test verifies that player2 can cancel a pending match and that both players receive their deposits back when both have deposited.
Tasks:
- Create match, both players deposit
- Wait — match is now Active, so create a separate scenario: player1 deposits, player2 cancels
- Assert player1 is refunded their stake
- Assert player2 (who hadn't deposited) has unchanged balance
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
unpause() emits an ("admin", "unpaused") event, but pause() emits nothing. Off-chain monitors cannot detect when the contract is paused without polling is_paused.
Tasks:
- Add
env.events().publish(("admin", "paused"), ())insidepause() - Add test asserting the event is emitted when
pause()is called
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
cancel_match sets the match state to Cancelled and writes it back to persistent storage, but the TTL extension call is present. Verify the TTL is correctly extended and add a test confirming it.
Tasks:
- Add test reading TTL after
cancel_match - Assert TTL equals
MATCH_TTL_LEDGERS
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that submit_result returns Error::MatchNotFound when called with an unknown match_id.
Tasks:
- Call
try_submit_result(9999, Winner::Player1)on a fresh contract - Assert
Err(Ok(Error::MatchNotFound))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that submit_result returns Error::NotFunded when called on a match where neither player has deposited.
Tasks:
- Create match (state = Pending, no deposits)
- Call
try_submit_result - Assert
Err(Ok(Error::NotFunded))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_escrow_balance returns exactly stake_amount (not 2 * stake_amount) after only one player has deposited.
Tasks:
- Create match with
stake_amount = 100 - Player1 deposits
- Assert
get_escrow_balancereturns100
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_escrow_balance returns 0 after a match is cancelled and deposits are refunded.
Tasks:
- Create match, player1 deposits, player1 cancels
- Assert
get_escrow_balancereturns0
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description:
OracleContract::initialize sets the admin silently with no on-chain event. Off-chain monitors cannot detect when the oracle is deployed and initialized.
Tasks:
- Add
env.events().publish(("oracle", "initialized"), admin)insideinitialize - Add test asserting the event is emitted
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description:
EscrowContract::initialize sets oracle and admin silently. Off-chain monitors cannot detect contract initialization.
Tasks:
- Add
env.events().publish(("escrow", "initialized"), (oracle, admin))insideinitialize - Add test asserting the event is emitted
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
OracleContract::unpause does not emit an event (unlike the escrow contract's unpause). A test should document whether this is intentional or an oversight.
Tasks:
- Call
unpause()on oracle contract - Assert no events are emitted (or add an event if the omission is unintentional)
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description:
DataKey::MatchTimeout is used internally by expire_match but there is no public getter. Frontends cannot display the configured timeout to users.
Tasks:
- Add
pub fn get_match_timeout(env: Env) -> u32returningDataKey::MatchTimeoutorDEFAULT_MATCH_TIMEOUT_LEDGERS - Add test asserting it returns the default value before any admin sets it
Status: Open — unassigned
Labels: enhancement
Priority: Medium
Estimated Time: 30 minutes
Description:
DataKey::MatchTimeout is read by expire_match but there is no admin function to set it. The timeout is permanently fixed at DEFAULT_MATCH_TIMEOUT_LEDGERS with no way to adjust it post-deployment.
Tasks:
- Add
pub fn set_match_timeout(env: Env, ledgers: u32) -> Result<(), Error>requiring admin auth - Store value under
DataKey::MatchTimeout - Add test asserting
expire_matchrespects the updated timeout
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that get_escrow_balance for match A is unaffected by deposits into match B.
Tasks:
- Create match A and match B with the same players
- Deposit into match A only
- Assert
get_escrow_balance(A)equalsstake_amount - Assert
get_escrow_balance(B)equals0
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that calling cancel_match on an already-cancelled match returns an error rather than silently succeeding.
Tasks:
- Create match, cancel it
- Call
try_cancel_matchagain - Assert
Err(Ok(Error::MatchAlreadyActive))orErr(Ok(Error::InvalidState))
Status: Open — unassigned
Labels: bug
Priority: Medium
Estimated Time: 30 minutes
Description:
OracleContract::submit_result accepts any String as game_id including an empty string. An empty game_id makes the result entry meaningless and harder to audit.
Tasks:
- Add
if game_id.len() == 0 { return Err(Error::InvalidGameId) }guard - Add
InvalidGameIderror variant to oracleerrors.rs - Add test asserting empty
game_idis rejected
Status: Open — unassigned
Labels: bug
Priority: Medium
Estimated Time: 30 minutes
Description:
create_match accepts an empty game_id string. A match with no game ID cannot be linked to a real chess game and the oracle cannot meaningfully verify it.
Tasks:
- Add
if game_id.len() == 0 { return Err(Error::InvalidGameId) }guard - Add
InvalidGameIderror variant to escrowerrors.rs - Add test asserting empty
game_idis rejected
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test explicitly calls has_result(0) on a fresh oracle contract before any submission to confirm the default is false.
Tasks:
- Initialize oracle contract
- Assert
has_result(0)returnsfalse
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
test_submit_and_get_result only asserts entry.result. No test verifies that entry.game_id matches the value passed to submit_result.
Tasks:
- Submit result with
game_id = "chess_game_42" - Call
get_result - Assert
entry.game_id == "chess_game_42"
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 30 minutes
Description:
Once a result is submitted to the oracle, it cannot be removed even if it was submitted in error. An admin-only delete_result function would allow correcting mistakes before the escrow contract reads the result.
Tasks:
- Add
pub fn delete_result(env: Env, match_id: u64) -> Result<(), Error>requiring admin auth - Remove
DataKey::Result(match_id)from persistent storage - Add test asserting
has_resultreturnsfalseafter deletion
Status: Open — unassigned
Labels: enhancement, security
Priority: High
Estimated Time: 1 hour
Description:
The escrow admin address is set at initialize and cannot be changed. If the admin key is lost or compromised, there is no recovery path.
Tasks:
- Add
pub fn transfer_admin(env: Env, new_admin: Address) -> Result<(), Error>requiring current admin auth - Update
DataKey::Admin - Emit
("admin", "transferred")event - Add test for successful transfer and that old admin is rejected afterward
Status: Open — unassigned
Labels: enhancement, security
Priority: High
Estimated Time: 1 hour
Description:
The oracle admin address is set at initialize and cannot be changed. If the oracle service key is rotated, there is no way to update the admin without redeploying.
Tasks:
- Add
pub fn transfer_admin(env: Env, new_admin: Address) -> Result<(), Error>requiring current admin auth - Update
DataKey::Admin - Emit
("admin", "transferred")event - Add test for successful transfer
Status: Open — unassigned
Labels: testing, security
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that a third-party address (not player1 or player2) calling deposit returns Error::Unauthorized.
Tasks:
- Create match for player1 vs player2
- Call
try_depositwith a random third address - Assert
Err(Ok(Error::Unauthorized))
Status: Open — unassigned
Labels: testing, security
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that a third-party address calling cancel_match returns Error::Unauthorized.
Tasks:
- Create match for player1 vs player2
- Call
try_cancel_matchwith a random third address - Assert
Err(Ok(Error::Unauthorized))
Status: Open — unassigned
Labels: testing, security
Priority: High
Estimated Time: 15 minutes
Description:
No test verifies that calling submit_result from an address that is not the registered oracle returns Error::Unauthorized.
Tasks:
- Create and fund a match
- Call
try_submit_resultfrom a non-oracle address (without mocking oracle auth) - Assert auth failure
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
get_escrow_balance reads from persistent storage but does not extend the TTL. Repeated reads near expiry could cause the match entry to expire between reads.
Tasks:
- Add
extend_ttlcall insideget_escrow_balanceafter the read - Add test verifying TTL is extended after calling
get_escrow_balance
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
is_funded reads from persistent storage without extending TTL, same issue as get_escrow_balance.
Tasks:
- Add
extend_ttlcall insideis_fundedafter the read - Add test verifying TTL is extended after calling
is_funded
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
get_match reads from persistent storage without extending TTL. Frequently-read matches near expiry could expire between reads.
Tasks:
- Add
extend_ttlcall insideget_matchafter the read - Add test verifying TTL is extended after calling
get_match
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that player1 calling deposit twice on the same match returns Error::AlreadyFunded.
Tasks:
- Create match, player1 deposits
- Call
try_depositfor player1 again - Assert
Err(Ok(Error::AlreadyFunded))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that player2 calling deposit twice returns Error::AlreadyFunded.
Tasks:
- Create match, both players deposit
- Call
try_depositfor player2 again - Assert
Err(Ok(Error::AlreadyFunded))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that create_match with stake_amount = 0 returns Error::InvalidAmount.
Tasks:
- Call
try_create_matchwithstake_amount = 0 - Assert
Err(Ok(Error::InvalidAmount))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that create_match with a negative stake_amount returns Error::InvalidAmount.
Tasks:
- Call
try_create_matchwithstake_amount = -100 - Assert
Err(Ok(Error::InvalidAmount))
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
The contract enforces stake_amount > 0 but there is no documentation on what the practical minimum stake is given token decimal precision (e.g., 1 stroop for XLM).
Tasks:
- Add inline doc comment to
create_matchexplaining the minimum stake constraint - Update
docs/oracle.mdorREADME.mdwith stake amount guidance
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies that submit_result is blocked when the escrow contract is paused.
Tasks:
- Create and fund a match
- Admin calls
pause() - Call
try_submit_result - Assert
Err(Ok(Error::ContractPaused))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
Verify that create_match is blocked when the contract is paused. (Mirrors the existing test but ensures it is present in this batch for completeness.)
Tasks:
- Admin calls
pause() - Call
try_create_match - Assert
Err(Ok(Error::ContractPaused))
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description: There is no public function to check whether the escrow contract has been initialized. Deployment scripts and frontends must attempt a call and catch a panic to detect uninitialized state.
Tasks:
- Add
pub fn is_initialized(env: Env) -> boolreturningenv.storage().instance().has(&DataKey::Oracle) - Add test asserting
falsebeforeinitializeandtrueafter
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description: Same gap as #149 but for the oracle contract.
Tasks:
- Add
pub fn is_initialized(env: Env) -> boolreturningenv.storage().instance().has(&DataKey::Admin) - Add test asserting
falsebeforeinitializeandtrueafter
Status: Open — unassigned
Labels: bug
Priority: Low
Estimated Time: 15 minutes
Description:
The oracle contract accepts any u64 as match_id with no upper bound check. Submitting results for arbitrarily large IDs wastes storage and could confuse integrators.
Tasks:
- Document that
match_idshould correspond to a valid escrow match - Consider adding a cross-contract call to verify the match exists before storing the result
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
Verify the full pause/unpause cycle on the oracle contract: pause blocks submit_result, unpause restores it.
Tasks:
- Submit result for match 0 (succeeds)
- Pause oracle
- Try submit result for match 1 — assert
Error::ContractPaused - Unpause oracle
- Submit result for match 1 — assert success
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
Verify that submitting a result for the same match_id twice returns Error::AlreadySubmitted using try_submit_result (not #[should_panic]).
Tasks:
- Submit result for match 0
- Call
try_submit_resultfor match 0 again - Assert
Err(Ok(Error::AlreadySubmitted))
Status: Open — unassigned
Labels: testing
Priority: High
Estimated Time: 1 hour
Description: No test exercises the full flow where the oracle contract stores a result and the escrow contract reads it to trigger a payout. The two contracts are tested in isolation only.
Tasks:
- Deploy both oracle and escrow contracts in the same test environment
- Oracle admin submits result for match 0
- Escrow oracle address calls
submit_resulton escrow - Assert payout is executed and match is
Completed
Status: Open — unassigned
Labels: enhancement
Priority: Medium
Estimated Time: 30 minutes
Description:
After submit_result completes, the Match struct state is set to Completed but the winner is not stored. Querying get_match after payout gives no information about who won.
Tasks:
- Add
winner: Option<Winner>field toMatchstruct - Set it in
submit_resultbefore writing back to storage - Add test asserting
get_matchreturns the correct winner after payout
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 30 minutes
Description:
The Match struct records created_ledger but not the ledger at which the match was completed. This makes it impossible to calculate match duration on-chain or off-chain.
Tasks:
- Add
completed_ledger: Option<u32>field toMatchstruct - Set it in
submit_resultviaenv.ledger().sequence() - Add test asserting
completed_ledgeris set after payout
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that Platform::ChessDotCom is stored and returned correctly by get_match. Existing tests only use Platform::Lichess.
Tasks:
- Create match with
Platform::ChessDotCom - Call
get_match - Assert
m.platform == Platform::ChessDotCom
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that the ("match", "created") event emitted by create_match contains the correct match_id, player1, player2, and stake_amount.
Tasks:
- Create a match
- Read
env.events().all() - Assert event topics are
("match", "created") - Assert event data contains correct
(id, player1, player2, stake_amount)
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that the ("match", "cancelled") event contains the correct match_id.
Tasks:
- Create and cancel a match
- Assert event topics are
("match", "cancelled") - Assert event data is the correct
match_id
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that the ("match", "completed") event contains the correct match_id and winner value.
Tasks:
- Create, fund, and complete a match with
Winner::Player1 - Assert event topics are
("match", "completed") - Assert event data is
(match_id, Winner::Player1)
Status: Open — unassigned
Labels: testing
Priority: High
Estimated Time: 30 minutes
Description:
No test verifies that after update_oracle, the new oracle address is accepted for submit_result and the old oracle address is rejected.
Tasks:
- Initialize with oracle_old
- Call
update_oraclewith oracle_new - Create and fund a match
- Call
submit_resultfrom oracle_new — assert success - Call
submit_resultfrom oracle_old — assert auth failure
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 30 minutes
Description:
errors.rs defines error codes as integers (e.g., MatchNotFound = 1) but there is no documentation explaining what each code means or when it is returned.
Tasks:
- Add doc comments to each error variant in
errors.rs - Reference the error codes in the relevant function doc comments
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
Same gap as #162 for the oracle contract's errors.rs.
Tasks:
- Add doc comments to each error variant in oracle
errors.rs
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
The workspace and contract Cargo.toml files are missing description, repository, and license fields. These are required for publishing and improve discoverability.
Tasks:
- Add
description,repository = "https://github.com/...", andlicense = "MIT"to both contractCargo.tomlfiles
Status: Open — unassigned
Labels: ci, enhancement
Priority: Medium
Estimated Time: 30 minutes
Description:
The .github/workflows/ci.yml runs cargo test but does not run cargo clippy. Lint warnings can accumulate silently and hide real issues.
Tasks:
- Add a
clippystep toci.yml:cargo clippy -- -D warnings - Fix any existing clippy warnings
Status: Open — unassigned
Labels: ci, enhancement
Priority: Low
Estimated Time: 15 minutes
Description: The CI pipeline does not enforce code formatting. Contributors can merge unformatted code without any automated check.
Tasks:
- Add
cargo fmt --checkstep toci.yml - Ensure all existing code passes
cargo fmt
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description:
scripts/build.sh does not use set -e or check exit codes. A failed build step will not stop the script, silently producing incomplete artifacts.
Tasks:
- Add
set -euo pipefailat the top ofbuild.sh - Add
set -euo pipefailtotest.shas well
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 15 minutes
Description:
Same issue as #167 for test.sh.
Tasks:
- Add
set -euo pipefailtotest.sh
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that expire_match refunds both players when both have deposited but the match is still Pending (edge case: both deposited but state not yet Active due to a hypothetical bug).
Tasks:
- Manually set up a match with both
player1_depositedandplayer2_depositedtrue but state stillPending - Advance ledger past timeout
- Call
expire_match - Assert both players are refunded
Status: Open — unassigned
Labels: testing, security
Priority: High
Estimated Time: 15 minutes
Description:
initialize rejects oracle == env.current_contract_address() with Error::InvalidAddress, but no test verifies this guard.
Tasks:
- Call
try_initializewithoracle = contract_id - Assert
Err(Ok(Error::InvalidAddress))
Status: Open — unassigned
Labels: testing, security
Priority: High
Estimated Time: 15 minutes
Description:
update_oracle should reject new_oracle == env.current_contract_address() (see #109), but even before that fix, a test should document the expected behaviour.
Tasks:
- Call
try_update_oraclewithnew_oracle = contract_id - Assert
Err(Ok(Error::InvalidAddress))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_oracle returns the exact oracle address passed to initialize.
Tasks:
- Initialize with a known oracle address
- Call
get_oracle - Assert returned address equals the oracle address
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_oracle reflects the new oracle address after update_oracle is called.
Tasks:
- Initialize, call
update_oraclewith a new address - Call
get_oracle - Assert returned address equals the new oracle address
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that calling pause() twice does not cause an error or unexpected state change.
Tasks:
- Call
pause()twice - Assert contract is still paused and no error is returned
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that calling unpause() on an already-unpaused contract does not cause an error.
Tasks:
- Call
unpause()without callingpause()first - Assert no error is returned and contract remains unpaused
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 30 minutes
Description:
All fields of the Match struct are pub. While this is common in Soroban contracts, it means any consumer can read internal state flags like player1_deposited directly. Consider documenting which fields are stable API vs internal.
Tasks:
- Add doc comments to each field in
Matchstruct indicating whether it is stable public API or internal state - Update
docs/architecture.mdwith the stable API surface
Status: Open — unassigned
Labels: documentation
Priority: Medium
Estimated Time: 1 hour
Description:
There is no diagram or written description of the MatchState state machine transitions (Pending → Active → Completed, Pending → Cancelled). New contributors must read the code to understand valid transitions.
Tasks:
- Add a state machine diagram (ASCII or Mermaid) to
docs/architecture.md - Document which functions trigger each transition and under what conditions
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 30 minutes
Description:
docs/oracle.md describes the oracle flow at a high level but does not document the ResultEntry struct fields (game_id, result) or the MatchResult enum variants.
Tasks:
- Add a section to
docs/oracle.mddocumentingResultEntryandMatchResult - Include example values
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
The README references ./scripts/deploy_testnet.sh but this file does not exist in the repository. Running the Quick Start guide will fail.
Tasks:
- Create
scripts/deploy_testnet.shwith a basic deployment script - Or update the README to reflect the actual available scripts
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
.env.example includes CHESSDOTCOM_API_KEY but the Chess.com oracle integration is listed as a v1.1 roadmap item and is not yet implemented. This misleads contributors.
Tasks:
- Add a comment in
.env.examplenoting thatCHESSDOTCOM_API_KEYis for a future release - Or remove it until the feature is implemented
Status: Open — unassigned
Labels: documentation
Priority: Medium
Estimated Time: 1 hour
Description:
The README references CONTRIBUTING.md but the file does not exist in the repository. Contributors following the README will hit a 404.
Tasks:
- Create
CONTRIBUTING.mdwith setup instructions, coding standards, and PR guidelines - Link it correctly from the README
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 30 minutes
Description:
The README references CODE_OF_CONDUCT.md but the file does not exist.
Tasks:
- Create
CODE_OF_CONDUCT.md(Contributor Covenant is a standard choice)
Status: Open — unassigned
Labels: documentation
Priority: Medium
Estimated Time: 1 hour
Description:
The README references demo/demo-script.md as a step-by-step guide but the file does not exist.
Tasks:
- Create
demo/demo-script.mdwith a walkthrough of creating a match, depositing, and triggering a payout on testnet
Status: Open — unassigned
Labels: documentation, security
Priority: High
Estimated Time: 2 hours
Description:
The README references docs/security.md (Threat Model & Security) but the file does not exist.
Tasks:
- Create
docs/security.mdcovering: oracle trust assumptions, admin key risks, re-initialization protection, pause mechanism, and known limitations
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 30 minutes
Description:
The README references docs/roadmap.md but the file does not exist.
Tasks:
- Create
docs/roadmap.mdwith the v1.0–v4.0 roadmap items from the README expanded with more detail
Status: Open — unassigned
Labels: documentation
Priority: Medium
Estimated Time: 1 hour
Description:
The README references docs/wave-guide.md for the Drips Wave contributor program but the file does not exist.
Tasks:
- Create
docs/wave-guide.mdexplaining how to claim wave-ready issues, point values, and submission process
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test explicitly asserts that a newly created match has MatchState::Pending. The existing test_create_match checks m.state == MatchState::Pending but this should be a dedicated, clearly named test.
Tasks:
- Create a match
- Assert
m.state == MatchState::Pending - Assert
m.player1_deposited == false - Assert
m.player2_deposited == false
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No dedicated test asserts that m.state == MatchState::Active after both players deposit.
Tasks:
- Create match, both players deposit
- Call
get_match - Assert
m.state == MatchState::Active
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No dedicated test asserts that m.state == MatchState::Completed after submit_result.
Tasks:
- Create, fund, and complete a match
- Call
get_match - Assert
m.state == MatchState::Completed
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No dedicated test asserts that m.state == MatchState::Cancelled after cancel_match.
Tasks:
- Create match, cancel it
- Call
get_match - Assert
m.state == MatchState::Cancelled
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that MatchCount continues to increment correctly even after some matches are cancelled. Cancelled matches should not reset or affect the counter.
Tasks:
- Create 3 matches, cancel match 1
- Create 2 more matches
- Assert IDs are 0, 1, 2, 3, 4 (no gaps or resets)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies behaviour when stake_amount is very large (near i128::MAX / 2). The pot = stake_amount * 2 calculation could overflow for extreme values.
Tasks:
- Add a check in
submit_resultthatstake_amount * 2does not overflow usingchecked_mul - Add
Error::Overflowreturn for this case - Add test with a large stake amount
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 30 minutes
Description:
create_match uses checked_add(1) to guard against overflow, but no test verifies this guard fires correctly when MatchCount is at u64::MAX.
Tasks:
- Manually set
MatchCounttou64::MAXin storage - Call
try_create_match - Assert
Err(Ok(Error::Overflow))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies what happens when a player calls deposit but does not have enough token balance. The token transfer call will panic, but the error is not caught gracefully.
Tasks:
- Create match with
stake_amount = 1000 - Mint only
500tokens to player1 - Call
try_depositfor player1 - Assert the call fails (token transfer panic)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies what happens if the contract's token balance is somehow zero when submit_result tries to pay out. This is a defensive test for state inconsistency.
Tasks:
- Create and fund a match
- Drain the contract's token balance externally (if possible in test env)
- Call
try_submit_result - Assert the call fails gracefully
Status: Open — unassigned
Labels: documentation
Priority: Low
Estimated Time: 15 minutes
Description:
environments.toml defines network configurations but there are no comments explaining each field or how to add a custom network.
Tasks:
- Add inline comments to
environments.tomlexplaining each field - Add a section to
docs/deployment.mdreferencing the file
Status: Open — unassigned
Labels: documentation
Priority: Medium
Estimated Time: 1 hour
Description:
docs/deployment.md covers testnet deployment but does not include mainnet deployment steps, security considerations, or key management guidance.
Tasks:
- Add a mainnet deployment section to
docs/deployment.md - Include key management best practices and pre-deployment checklist
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
test_get_result_extends_ttl exists in the oracle tests but verifies TTL equals MATCH_TTL_LEDGERS immediately after submit. A separate test should verify TTL is re-extended on a subsequent get_result call after some ledgers have passed.
Tasks:
- Submit result, advance ledger by 1000
- Call
get_result - Assert TTL is reset to
MATCH_TTL_LEDGERS(notMATCH_TTL_LEDGERS - 1000)
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
If get_match is updated to extend TTL on read (see #141), a test should verify the TTL is reset after some ledgers have passed.
Tasks:
- Create match, advance ledger by 1000
- Call
get_match - Assert TTL is reset to
MATCH_TTL_LEDGERS
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
Instance storage entries (Oracle, Admin, MatchCount, Paused) have no TTL extension calls. If the contract is inactive for a long period, instance storage could expire.
Tasks:
- Investigate whether instance storage requires TTL extension in Soroban
- Add
extend_ttlcalls for instance storage if required - Add test verifying instance storage TTL is maintained
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
Same gap as #200 for the oracle contract's instance storage (Admin, Paused).
Tasks:
- Investigate instance storage TTL requirements for oracle contract
- Add
extend_ttlcalls if required - Add test
Status: Open — unassigned
Labels: enhancement
Priority: Medium
Estimated Time: 1 hour
Description:
The escrow contract defines Winner (Player1, Player2, Draw) and the oracle contract defines MatchResult (Player1Wins, Player2Wins, Draw). These represent the same concept with different naming, causing confusion and duplication.
Tasks:
- Decide on a canonical enum name and variant names
- Consider a shared types crate or document the intentional separation
- Update all references and tests
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that Platform::Lichess and Platform::ChessDotCom survive a storage write/read round-trip correctly.
Tasks:
- Create match with each platform variant
- Read back with
get_match - Assert platform matches the input
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that Winner::Draw is correctly serialized and deserialized from event data.
Tasks:
- Complete a match with
Winner::Draw - Read the
("match", "completed")event - Deserialize and assert
winner == Winner::Draw
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that MatchResult::Draw is correctly emitted in the oracle ("oracle", "result") event.
Tasks:
- Submit result with
MatchResult::Draw - Read the event
- Assert event data contains
MatchResult::Draw
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies what happens when create_match is called on an uninitialized contract (before initialize). The Paused check reads instance storage with unwrap_or(false), so it would not panic, but MatchCount would also default to 0 silently.
Tasks:
- Register contract without calling
initialize - Call
try_create_match - Document and assert the expected behaviour (panic or error)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies what happens when submit_result is called on an uninitialized escrow contract. The oracle address lookup returns None, which maps to Error::Unauthorized.
Tasks:
- Register contract without calling
initialize - Call
try_submit_result - Assert
Err(Ok(Error::Unauthorized))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
No test verifies what happens when OracleContract::submit_result is called before initialize. The admin lookup returns None, mapping to Error::Unauthorized.
Tasks:
- Register oracle contract without calling
initialize - Call
try_submit_result - Assert
Err(Ok(Error::Unauthorized))
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that calling pause() on an uninitialized contract returns Error::Unauthorized rather than panicking.
Tasks:
- Register contract without calling
initialize - Call
try_pause() - Assert
Err(Ok(Error::Unauthorized))
Status: Open — unassigned
Labels: testing, enhancement
Priority: Medium
Estimated Time: 2 hours
Description:
All tests use fixed inputs. Property-based or fuzz testing with random stake_amount, match_id, and player addresses would catch edge cases that hand-written tests miss.
Tasks:
- Evaluate
proptestorquickcheckcompatibility with Soroban test environment - Add at least one property test for
create_matchwith random valid inputs - Document findings if fuzz testing is not feasible in the Soroban test harness
Status: Open — unassigned
Labels: enhancement
Priority: Low
Estimated Time: 2 hours
Description: There are no benchmarks measuring CPU instruction counts or memory usage for key contract functions. Soroban charges fees based on resource usage, so benchmarks help optimize costs.
Tasks:
- Add benchmark tests using
env.budget()to measure instruction counts forcreate_match,deposit, andsubmit_result - Document baseline resource usage in
docs/deployment.md
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
No test verifies that two deposits submitted in the same ledger (simulated sequentially in tests) both succeed and the match transitions to Active correctly.
Tasks:
- Create match
- Call
depositfor player1 and player2 back-to-back without ledger advancement - Assert match is
Activeandis_fundedreturnstrue
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that cancel_match succeeds and does not attempt any token transfers when neither player has deposited.
Tasks:
- Create match, immediately cancel it (no deposits)
- Assert match state is
Cancelled - Assert no token transfer events are emitted
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that expire_match succeeds without attempting any token transfers when neither player has deposited.
Tasks:
- Create match, advance ledger past timeout, call
expire_match - Assert match state is
Cancelled - Assert no token transfer events are emitted
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_match returns MatchState::Cancelled after expire_match is called.
Tasks:
- Create match, advance ledger, call
expire_match - Call
get_match - Assert
m.state == MatchState::Cancelled
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that is_funded returns false after a match is cancelled (even if both players had deposited before cancellation).
Tasks:
- Create match, player1 deposits, cancel match
- Assert
is_fundedreturnsfalse
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that is_funded returns true (or documents expected behaviour) after a match is Completed. The is_funded function checks deposit flags, not state, so it would return true even after payout.
Tasks:
- Complete a match
- Call
is_funded - Assert and document the expected return value
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
get_escrow_balance returns 0 for Completed and Cancelled matches. No test verifies the Completed case specifically.
Tasks:
- Create, fund, and complete a match
- Assert
get_escrow_balancereturns0
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
No test verifies that get_escrow_balance returns 0 for a Cancelled match where no deposits were made.
Tasks:
- Create match, cancel immediately
- Assert
get_escrow_balancereturns0
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
If delete_result is implemented (see #133), a test should verify that has_result returns false after deletion and get_result returns Error::ResultNotFound.
Tasks:
- Submit result, delete it
- Assert
has_resultreturnsfalse - Assert
try_get_resultreturnsErr(Ok(Error::ResultNotFound))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If transfer_admin is implemented on the oracle contract (see #135), tests should verify the old admin is rejected and the new admin is accepted.
Tasks:
- Transfer admin to new_admin
- Call
submit_resultfrom old_admin — assert auth failure - Call
submit_resultfrom new_admin — assert success
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If transfer_admin is implemented on the escrow contract (see #134), tests should verify the old admin is rejected and the new admin is accepted.
Tasks:
- Transfer admin to new_admin
- Call
pause()from old_admin — assert auth failure - Call
pause()from new_admin — assert success
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If get_player_matches is implemented (see #91), tests should verify the index is populated correctly and returns all match IDs for a player.
Tasks:
- Create 3 matches for player1 (as player1 or player2)
- Call
get_player_matches(player1) - Assert all 3 match IDs are returned
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If get_active_matches is implemented (see #95), tests should verify the index is updated correctly through the match lifecycle.
Tasks:
- Create 3 matches
- Cancel match 1, complete match 2
- Call
get_active_matches - Assert only match 0 and match 2 are returned (or only match 0 if completed matches are removed)
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description: If a token allowlist is implemented (see #90), tests should verify that disallowed tokens are rejected and allowed tokens are accepted.
Tasks:
- Try
create_matchwith a non-allowlisted token — assertError::InvalidToken - Add token to allowlist
- Try
create_matchagain — assert success
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
If the self-match guard is implemented (see #87), a test should verify that create_match with player1 == player2 returns Error::InvalidPlayers.
Tasks:
- Call
try_create_matchwithplayer1 == player2 - Assert
Err(Ok(Error::InvalidPlayers))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 15 minutes
Description:
If the duplicate game_id guard is implemented (see #88), a test should verify that creating two matches with the same game_id returns Error::AlreadyExists on the second call.
Tasks:
- Create match with
game_id = "game1" - Call
try_create_matchwithgame_id = "game1"again - Assert
Err(Ok(Error::AlreadyExists))
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If two-step admin transfer is implemented (see #86), tests should verify that the transfer is not complete until the new admin calls accept_admin.
Tasks:
- Call
propose_admin(new_admin) - Assert old admin is still active (can call
pause) - Call
accept_adminfrom new_admin - Assert new admin is now active and old admin is rejected
Status: Open — unassigned
Labels: testing
Priority: Low
Estimated Time: 15 minutes
Description:
If is_paused is implemented (see #83), tests should verify it returns false initially, true after pause(), and false after unpause().
Tasks:
- Assert
is_paused()returnsfalseafter initialize - Call
pause(), assertis_paused()returnstrue - Call
unpause(), assertis_paused()returnsfalse
Status: Open — unassigned
Labels: testing
Priority: Medium
Estimated Time: 30 minutes
Description:
If set_match_timeout is implemented (see #126), tests should verify that expire_match respects the updated timeout value.
Tasks:
- Set timeout to 100 ledgers
- Create match, advance ledger by 100
- Call
expire_match— assert success - Create another match, advance ledger by 99
- Call
try_expire_match— assertError::MatchNotExpired