Skip to main content

View Facade Cross-Contract Call Safety

Overview

This document is the security audit for cross-contract call safety in contracts/escrow-view-facade/ and contracts/view-facade/ (issue #1288).

Both facades act as read-only aggregation layers. They must never:

  • Call auth-gated (state-mutating) functions on underlying contracts
  • Forward or escalate caller auth to underlying contracts
  • Modify state in any contract other than their own registry

Audit: escrow-view-facade

Cross-contract calls made

Function called on underlyingTypeAuth required?State mutated?
try_get_escrow_info(id)view❌ No❌ No
try_get_metadata(id)view❌ No❌ No
try_get_pause_flags()view❌ No❌ No
try_query_escrows_by_depositor(user, offset, limit)view❌ No❌ No

All calls use the try_ prefix — they return Result instead of panicking, so a missing or erroring underlying contract causes graceful degradation (None / empty vec) rather than a trap.

Auth forwarding analysis

The facade does not call require_auth() on behalf of the caller at any point. The facade's own entrypoints (get_escrow_summary, get_escrow_summaries, get_user_portfolio) take no caller: Address parameter and perform no auth checks — any address can call them.

State mutation analysis

The facade writes no state to the underlying escrow contract. It only reads from instance storage of the escrow contract via view functions.

The facade itself has no instance storage — it is a pure pass-through.

Security verdict: ✅ SAFE


Audit: view-facade

Cross-contract calls made

ViewFacade makes no cross-contract calls. It only reads and writes its own instance storage (DataKey::Admin, DataKey::Registry).

Auth model

EntrypointAuth requiredNotes
init(admin)None (first-caller)Admin stored immutably; double-init rejected
register(addr, kind, ver)Adminadmin.require_auth() enforced
deregister(addr)Adminadmin.require_auth() enforced
list_contracts(offset, limit)NonePure read
list_contracts_all()NonePure read
contract_count()NonePure read
get_contract(addr)NonePure read
get_admin()NonePure read

State mutation analysis

  • register and deregister write only to DataKey::Registry in the facade's own instance storage. They do not touch any external contract.
  • All view functions are pure reads with no side effects.

Security verdict: ✅ SAFE


Security Assumptions

  1. Admin key security — the admin address is immutable after init. A compromised admin key can modify the registry but cannot affect underlying escrow contracts.

  2. No fund custody — neither facade holds tokens or transfers funds.

  3. Bounded registryViewFacade enforces MAX_REGISTRY_SIZE = 1000 to prevent storage exhaustion attacks.

  4. try_ patternEscrowViewFacade uses try_ calls so a malicious or broken underlying contract cannot cause the facade to trap.

  5. No auth escalation — calling a view function on either facade does not grant the caller any elevated permissions on the underlying contracts.


Test Coverage (issue #1288)

escrow-view-facade/src/test_cross_contract_safety.rs

TestProperty verified
test_get_escrow_summary_is_read_onlyNo mutating functions called
test_get_escrow_summaries_batch_is_read_onlyBatch also read-only
test_get_user_portfolio_is_read_onlyPortfolio also read-only
test_unprivileged_caller_can_query_facadeNo auth required to query
test_two_different_callers_get_identical_resultsNo per-caller state
test_facade_does_not_require_caller_authNo auth check on caller
test_missing_escrow_returns_noneGraceful degradation
test_batch_with_missing_escrows_returns_empty_vecGraceful degradation
test_user_portfolio_with_missing_contract_returns_emptyGraceful degradation
test_escrow_summary_fields_match_underlying_dataCorrect data mapping
test_paused_contract_reflected_in_summaryPause state read correctly
test_batch_and_single_return_consistent_dataBatch/single consistency
test_empty_batch_returns_empty_vecEdge case: empty input

view-facade/src/test_cross_contract_safety.rs

TestProperty verified
test_list_contracts_requires_no_authView is public
test_get_contract_requires_no_authView is public
test_contract_count_requires_no_authView is public
test_list_contracts_all_requires_no_authView is public
test_get_admin_requires_no_authView is public
test_register_requires_admin_authMutation is admin-gated
test_deregister_requires_admin_authMutation is admin-gated
test_view_call_does_not_grant_register_accessNo auth escalation
test_double_init_is_rejectedAdmin immutability
test_admin_cannot_be_replaced_after_initAdmin immutability
test_registry_state_consistent_across_readsRegistry isolation
test_unprivileged_caller_sees_same_registry_as_adminNo per-caller state
test_paginated_list_requires_no_authPagination is public
test_invalid_pagination_returns_error_not_panicGraceful error handling
test_registry_full_error_is_returned_not_panicBounded storage
test_deregister_nonexistent_is_noopIdempotent deregister
test_get_contract_returns_none_for_unknown_addressSafe miss handling
test_get_contract_returns_correct_entry_after_registerCorrect data