Skip to content

Deep base-layer audit: verified findings (#19 fixed, #22/#14/#4-7/#15 tracked) #629

@joshuakrueger-dfx

Description

@joshuakrueger-dfx

Deep base-layer audit — verified findings

Read-only audit of the app foundation (money/decimal conversion, state/async, error handling, null-safety), run against a fresh pinned clone of develop @ fff5035.

Method (why these are trustworthy, not noise)

  • Full CI-equivalent run first: generate_localization + generate_release_info + build_runnerflutter analyze (0 issues) → flutter test (2380 pass, 1 fail). The single failure is the known home_golden_test macOS pixel drift (0.24 %), not a logic bug.
  • 25 hypotheses were generated, then each was turned into a reproducing unit test. Only findings with a red test (or behaviour-confirming test) are listed as bugs. 15 of 25 hypotheses were refuted by guards already present in the code and are deliberately not listed as bugs.

A. Verified, user-visible

# Location Symptom Severity
19 lib/screens/legal/subpages/legal_document_page.dart:62 On asset-load failure the catch sets _markdownContent = '', which build treats as loadedblank legal page, no error message, no retry. Medium
4–7 dfx_price_service.dart:35/38/64/66, price_chart.dart:69/113, portfolio_chart.dart:104, real_unit_account_service.dart:38 BigInt.from(double * 100) truncates toward zero (e.g. 0.29 * 100 = 28.999… → 28) → price/chart labels can read one cent low. Low (cosmetic)
22 lib/styles/currency.dart:10 via buy/sell payment-info DTOs (real_unit_*_payment_info_dto.dart) Currency.fromCode uses firstWhere without orElseStateError on an unsupported backend currency. Not a crash: both payment cubits catch it and emit PaymentInfoError.unknown, so the user sees a generic error and the log shows a cryptic Bad state: No element. Robustness/clarity hardening. Low

B. Verified, internal (not directly user-visible)

# Location Symptom Severity
14 lib/screens/kyc/cubits/kyc/kyc_cubit.dart:48 Future.timeout does not cancel the underlying getKycStatus/getUser work. Rapid retries stack multiple concurrent in-flight requests (test observed 3). State stays correct (generation guard), but it leaks requests/connections. Medium

C. Candidate — needs verification before any fix

# Location Hypothesis
15 lib/setup/lifecycle_initializer.dart:61 unawaited(lockCurrentWallet()) on the hidden lifecycle event: if the lock fails/times out, the seed may stay unlocked in memory while the app is backgrounded. Security-relevant if confirmed. Not reproducible at cubit-mock level — needs a lifecycle/widget test with a mocked WalletService whose lockCurrentWallet() errors. Listed as a candidate, not a confirmed bug.

Plan

Reviewed and deliberately not pursued

  • Decimal fiat-input rounding in buy/sell (double.parse(amount).round()) — the amount pipeline is integer-only by API contract; treated as by-design.
  • 15 refuted hypotheses, where a guard already exists: EIP-7702 wei mismatch guard (expectedWei != actualWei), balances.isEmpty guard, isClosed/sequence guards before every emit in the converter/bitbox cubits, the id != null / pdfUrls != null null-guards, the empty-array balance guard, the deliberately nullable rate/?? 0 mappings, and the documented graceful-degradation paths in the converters / KYC registration.

Findings were verified with throwaway probe tests; no product code was changed during the audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions