escrow: dead NotExpired/InsufficientBalance error variants suggest a missing defensive balance check in release()/refund()
Los mantenedores suelen responder en 1 día
Evaluación
Este issue todavía no se ha evaluado.
Descripción
Overview
contracts/escrow/src/error.rs defines two Error variants that are never constructed anywhere in contracts/escrow/src/lib.rs: NotExpired = 10 and InsufficientBalance = 11. Confirmed by cross-referencing every defined variant against every Error:: construction site in the file (grep -oE '^\s+[A-Za-z]+ = [0-9]+,' contracts/escrow/src/error.rs vs. grep -oE 'Error::[A-Za-z]+' contracts/escrow/src/lib.rs | sort -u): AlreadyInitialized, NotInitialized, EscrowNotFound, AlreadyFunded, AlreadyPaid, AlreadyRefunded, InvalidSplit, InvalidAmount, InvalidFee, InvalidDeadline, and (indirectly, via compute_split) the ones used there — but never NotExpired or InsufficientBalance.
docs/access-control-audit.md already documents that Unauthorized is dead code, and explains precisely why: Soroban's Address::require_auth() panics rather than returning a Result, so a failed auth check can never reach a point where the contract constructs Err(Error::Unauthorized). That explanation does not apply to NotExpired or InsufficientBalance — neither is auth-related, and both variant names describe exactly the kind of ordinary, Result-returnable validation failure this codebase handles correctly everywhere else (see AlreadyPaid/AlreadyRefunded/InvalidDeadline, all real, reachable, tested rejections). These two variants are neither explained by the audit doc nor accounted for anywhere else in this repository.
Their names are specific enough to suggest they're not arbitrary cruft:
NotExpiredreads exactly like the error a deadline-gated function would return when called before its deadline — i.e., precisely the shape of checkrefund's early-path used to need before it was refactored to the currentif now < escrow.deadline { admin required } else { permissionless }structure (contracts/escrow/src/lib.rs:161-166). It's plausible this variant is a relic from an earlier design where a non-admin caller attemptingrefundbefore expiry was rejected with a typedNotExpirederror rather than relying onrequire_auth()'s panic — worth confirming against git history whether such a code path ever existed and was removed, or whether this was scaffolded in error.rs ahead of lib.rs and never wired up.InsufficientBalanceis the exact same variant name thatmaintenance-pooluses for a real, live, load-bearing check (contracts/maintenance-pool/src/error.rs:14, constructed atcontracts/maintenance-pool/src/lib.rs:130-132whenamount > pool.balance). Escrow'srelease/refundperform no equivalent check anywhere — neither function verifies the contract's actual on-chain token balance is sufficient before transferring; both simply trust thatescrow.amount(recorded once, atfundtime) still accurately reflects available funds, and transfer that recorded amount unconditionally. Givenmaintenance-pooltreats "verify sufficient balance before paying out" as an explicit, tested, first-class check, the presence of an identically-named, unused variant inescrowis a plausible signal that an equivalent defensive check was intended for escrow too and never implemented — as opposed to being simply irrelevant scaffolding.
Requirements
- Determine definitively, via git blame/history if available, whether
NotExpiredandInsufficientBalanceare relics of removed validation logic or were always dead scaffolding that was never wired up. - If
InsufficientBalancerepresents a genuinely missing defensive check: add it toreleaseandrefund, verifyingtoken::Client::balance(&env.current_contract_address()) >= escrow.amount(or the specific amount about to be transferred) before transferring, returningError::InsufficientBalanceif not. This is a meaningful defense-in-depth addition given #18's solvency-invariant work is about proving balance always covers obligations — this check would make that invariant enforced at the point of payment rather than only verified after the fact by an external fuzzer. - If, after investigation, both variants are confirmed to be pure unused scaffolding with no missing check behind them, remove them and document the removal explicitly (contract error enums are part of the public ABI — removing/renumbering variants is a breaking change for any client code matching on specific error codes, so this needs the same care as any other public-interface change, not a silent cleanup).
Acceptance Criteria
- Investigation into
NotExpired/InsufficientBalance's origin documented (even if inconclusive, the reasoning should be recorded) - Either:
InsufficientBalancewired intorelease/refundas a genuine defensive balance check, with tests — or: both variants removed with an explicit, documented rationale and a note about the ABI-compatibility implications of removing acontracterrorvariant - If added:
test_release_rejects_if_contract_balance_insufficient/test_refund_rejects_if_contract_balance_insufficient(likely requiring a contrived setup — e.g. draining the contract's balance via a means outside normal accounting, or testing the check directly viaenv.as_contract) -
cargo test --workspacepasses
Additional Notes
- Precise references:
contracts/escrow/src/error.rs:16-17(the two dead variants,NotExpired = 10,InsufficientBalance = 11); confirmed dead viagrep -n "Error::NotExpired\|Error::InsufficientBalance" contracts/escrow/src/lib.rsreturning no matches. - Cross-references:
docs/access-control-audit.md's existing "Error::Unauthorizedis dead code (not a bug)" section — this issue explicitly does not claim the same conclusion for these two variants, since (unlikeUnauthorized) there's no structural reason (likerequire_auth()'s panic-not-Result behavior) that would make them permanently unreachable by design; #18 (on-chain solvency invariant fuzzer — complementary: that issue verifies the balance-covers-obligations invariant externally/statistically, this issue is about whether the contract should also assert it inline, at the moment of payment, using the very error variant that already exists for exactly that purpose);maintenance-pool::withdraw's realInsufficientBalancecheck (contracts/maintenance-pool/src/lib.rs:130-132) as the direct model to follow if the check is added to escrow.
- Lenguaje dominante
- Rust
- Estrellas
- 1
- Forks
- 102
- Merge medio
- 4 h 12 min
- PR fusionados (30 d)
- 30
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Sin guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de MergeFi/contracts
-
allocations map retains released issues — phantom ledger driftQuizá libre de nuevo @PINYOPATTANAWASANPORN la tomó hace 12 días y no hay ningún pull request abierto. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 25/100
MergeFi/contracts#608 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
escrow::contribute and milestones::contribute accept new funds after deadline + GRACE_PERIOD, when anyone can already refund or cancelPosiblemente ocupada @urielit1313-creator la tomó hace 29 días. Abiertobug
Dificultad 3/5 1-2 días Aptitud para principiantes 25/100
MergeFi/contracts#568 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Makefile deploy targets deploy the unoptimised .wasm — `stellar contract optimize` writes *.optimized.wasmPosiblemente ocupada @urielit1313-creator la tomó hace 29 días. Abiertobug good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 25/100
MergeFi/contracts#567 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
keep_alive never refreshes instance storage, so a contract kept alive only via keep_alive can still archive Admin/Oracle/TreasuryPosiblemente ocupada @urielit1313-creator la tomó hace 29 días. Abiertobug
Dificultad 4/5 3-5 días Aptitud para principiantes 25/100
MergeFi/contracts#566 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
[Regression on closed #254] milestones::deallocate still has no test asserting admin authorizationPosiblemente ocupada @urielit1313-creator la tomó hace 29 días. Abiertosecurity testing
Dificultad 2/5 1-3 horas Aptitud para principiantes 25/100
MergeFi/contracts#565 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de MergeFi/contracts
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
design-data-spec
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
adobe/spectrum-design-data#1744 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
USERID reads /etc/passwd directly instead of looking the user upPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertodefect
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día