Skip to main content

View Facade Cross-Contract Call Safety

Last updated: 16 August 2026

Archived — describes a contract that does not exist

This page is in the archive: it documents a Soroban contract surface with no implementation in any Grainlify repository, verified 16 August 2026. Kept as a design record, not as a description of working software.

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 registry — ViewFacade enforces MAX_REGISTRY_SIZE = 1000 to prevent storage exhaustion attacks.

  4. try_ pattern — EscrowViewFacade 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