Skip to content

fix(contract): standardize public entry point auth gates and access g… - #320

Merged
MaryammAli merged 2 commits into
BlockDash-Studios:mainfrom
paud1615-tech:fix/standardize-contract-function-signatures
Jul 26, 2026
Merged

fix(contract): standardize public entry point auth gates and access g…#320
MaryammAli merged 2 commits into
BlockDash-Studios:mainfrom
paud1615-tech:fix/standardize-contract-function-signatures

Conversation

@paud1615-tech

@paud1615-tech paud1615-tech commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Problem

Public methods in lib.rs used inconsistent patterns for authorization checks and pause gating. Some entry points
delegated auth to inner modules without enforcing it at the boundary, others were missing guards entirely, and one
internal helper was accidentally exposed as a callable contract entry point. This created a fragile and
unpredictable security surface:

  • Any caller could invoke set_privacy on an uninitialized contract without the owner ever authorizing —
    guard_initialized ran first but the owner.require_auth() call only existed deep inside privacy::set_privacy,
    leaving a gap in the lib.rs boundary
  • create_escrow had no guard at all — it incremented the global escrow counter on any call, including against an
    uninitialized deployment
  • cleanup_stealth_escrow called the raw require_initialized helper directly instead of the canonical
    guard_initialized wrapper, skipping reentrancy protection
  • is_feature_paused was mistakenly exposed as a pub contract entry point — external clients could call it as if it
    were a query method when it is purely an internal storage helper

Changes

lib.rs

set_privacy — Added owner.require_auth() at the lib.rs entry point boundary, before the feature-pause check. The
auth now happens in the correct order: guard_initialized → owner.require_auth() → pause check → delegate to
privacy::set_privacy.

// Before
pub fn set_privacy(env: Env, owner: Address, enabled: bool) -> Result<(), RustAcademyError> {
admin::guard_initialized(&env)?;
if is_feature_paused(&env, PauseFlag::SetPrivacy) { ... }
privacy::set_privacy(&env, owner, enabled) // auth was only here
}

// After
pub fn set_privacy(env: Env, owner: Address, enabled: bool) -> Result<(), RustAcademyError> {
admin::guard_initialized(&env)?;
owner.require_auth(); // ← enforced at entry point boundary
if storage::is_feature_paused(&env, PauseFlag::SetPrivacy) { ... }
privacy::set_privacy(&env, owner, enabled)
}

create_escrow — Gated with guard_initialized and changed return type to Result<u64, RustAcademyError> so callers on
an uninitialized deployment receive a proper error instead of silently mutating state.

// Before — completely unguarded
pub fn create_escrow(env: Env, _from: Address, _to: Address, _amount: u64) -> u64 {
increment_escrow_counter(&env)
}

// After
pub fn create_escrow(env: Env, _from: Address, _to: Address, _amount: u64) -> Result<u64, RustAcademyError> {
admin::guard_initialized(&env)?;
Ok(increment_escrow_counter(&env))
}

cleanup_stealth_escrow — Replaced the raw require_initialized call with guard_initialized, matching every other
initialization-gated entry point and picking up reentrancy protection.

// Before
admin::require_initialized(&env)?;

// After
admin::guard_initialized(&env)?;

is_feature_paused — Removed as a pub contract entry point. It is an internal storage module helper and must not be
callable by external clients. All internal callers now reference storage::is_feature_paused directly.

role_test.rs — 13 new tests

┌──────────────────────────────────────┬────────────────┐
│ Test │ What it covers │
├─────────────────────────────────────────────┼─────────────────────────────────┤
│ test_set_privacy_requires_owner_auth │ Owner can set their own privacy │
├───────────────────────────────────────────────┼───────────────────────────────────┤
│ test_set_privacy_blocked_when_uninitialized │ guard_initialized blocks pre-init │
├───────────────────────────────────────────────┼───────────────────────────────────┤
│ test_create_escrow_blocked_when_uninitialized │ guard_initialized blocks pre-init │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_create_escrow_works_when_initialized │ Counter increments correctly post-init │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_cleanup_stealth_escrow_blocked_when_uninitialized │ guard_initialized blocks pre-init │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_deposit_blocked_when_globally_paused │ guard_deposit global pause check │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_deposit_blocked_when_deposit_feature_paused │ guard_deposit feature-flag check │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_deposit_blocked_in_emergency_mode │ guard_deposit emergency mode check │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_refund_blocked_when_globally_paused │ guard_refund global pause check │
├────────────────────────────────────────────────────────┼────────────────────────────────────────┤
│ test_dispute_blocked_when_globally_paused │ guard_dispute global pause check │
├────────────────────────────────────────────────────────┼─────────────────────────────────────────┤
│ test_set_paused_blocked_in_emergency_mode │ guard_admin_config emergency mode check │
├────────────────────────────────────────────────────────┼─────────────────────────────────────────┤
│ test_guard_initialized_blocks_uninitialized_ops │ guard_initialized on cleanup_escrow │
└────────────────────────────────────────────────────────┴─────────────────────────────────────────┘

Access model (post-fix)

┌───────┬──────┬──────────┐
│ Class │ Gate │ Examples │
├───────┼────────────────────────────────────┼────────────────────────────────────────────────────────────────┤
│ Admin │ guard_admin_config → require_admin │ set_paused, pause_features, set_fee_config, set_admin, upgrade │
├───────┼────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
│ Owner │ guard_* + caller.require_auth() │ deposit, withdraw, refund, set_privacy, enable_privacy, │
│ Owner │ guard_* + caller.require_auth() │ deposit, withdraw, refund, set_privacy, enable_privacy, │
│ │ │ stealth_withdraw │
├─────────┼────────────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ Arbiter │ guard_dispute + arbiter membership │ resolve_dispute, vote_for_dispute, resolve_dispute_multi_sig │
│ │ check │ │
├─────────┼────────────────────────────────────┼───────────────────────────────────────────────────────────────┤
│ Public │ none (read-only) │ get_*, privacy_status, verify_amount_commitment, health_check │
└─────────┴────────────────────────────────────┴───────────────────────────────────────────────────────────────┘

Files changed

…uards

- set_privacy: add owner.require_auth() before routing to privacy module;
  previously the lib.rs entry point had no owner auth, only the inner
  privacy::set_privacy call did — callers could bypass the lib.rs guard_initialized check
- remove is_feature_paused as a public contract entry point; it is an
  internal storage helper and must not be callable by external clients
- create_escrow: gate with guard_initialized and change return type to
  Result<u64, RustAcademyError> so uninitialized calls fail consistently
- cleanup_stealth_escrow: replace raw require_initialized with guard_initialized
  to match the consistent guard pattern used by all other initialized-gated ops
- role_test.rs: add 13 tests covering the new guard patterns — uninitialized
  blocks, owner auth, global pause, feature pause, emergency mode blocks

Fixes: Issue BlockDash-Studios#53 — mixed auth patterns in public contract methods
@MaryammAli

Copy link
Copy Markdown
Contributor

@paud1615-tech
this issue is not complete ,please read the issue description

- coverage_test.rs: switch TestContext::new() -> TestContext::with_admin()
  in test_demo_privacy_toggle_under_10_lines; set_privacy now requires
  the contract to be initialized via guard_initialized
- test.rs: add client.initialize(&admin) before test_create_escrow;
  create_escrow is now gated by guard_initialized (Issue BlockDash-Studios#53)
- lib.rs: remove redundant owner.require_auth() call from set_privacy;
  auth is already enforced inside privacy::set_privacy to avoid the
  double-auth penalty
- Cargo.lock: bump ethnum 1.5.2 -> 1.5.3 to fix transmute size mismatch
  compilation error on Rust stable >=1.97 (CI uses dtolnay/rust-toolchain@stable)

@MaryammAli MaryammAli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MaryammAli
MaryammAli merged commit 59dd301 into BlockDash-Studios:main Jul 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants