The Callora Vault implements role-based access control for deposit operations to ensure only authorized parties can increase the vault balance.
- Owner: Set during contract initialization. Exclusive authority to manage allowed depositors, withdraw funds, and propose revenue pool changes.
- Allowed Depositor: Addresses approved by the owner to handle automated deposits.
- Authorized Caller: Optional address permitted to trigger
deductoperations. - Pending Owner: Nominee awaiting acceptance of the owner role.
- Pending Admin: Nominee awaiting acceptance of the admin role.
- Admin / Multisig Admin: Current admin address. If this address is a Stellar multisig account, native thresholds and signer weights are enforced by
require_auth. - Pending Revenue Pool: Proposed revenue pool address awaiting acceptance.
| Function | Owner | Allowed Depositor | Authorized Caller | Pending Owner | Others |
|---|---|---|---|---|---|
deposit |
✅ | ✅ | ❌ | ❌ | ❌ |
withdraw |
✅ | ❌ | ❌ | ❌ | ❌ |
withdraw_to |
✅ | ❌ | ❌ | ❌ | ❌ |
deduct |
❌ | ❌ | ✅ | ❌ | ❌ |
batch_deduct |
❌ | ❌ | ✅ | ❌ | ❌ |
set_allowed_depositor |
✅ | ❌ | ❌ | ❌ | ❌ |
clear_allowed_depositors |
✅ | ❌ | ❌ | ❌ | ❌ |
set_authorized_caller |
✅ | ❌ | ❌ | ❌ | ❌ |
transfer_ownership |
✅ | ❌ | ❌ | ❌ | ❌ |
accept_ownership |
❌ | ❌ | ❌ | ✅ | ❌ |
cancel_ownership_transfer |
✅ | ❌ | ❌ | ❌ | ❌ |
set_admin |
✅ | ❌ | ❌ | ❌ | ❌ |
accept_admin |
❌ | ❌ | ❌ | ❌ | ✅ |
cancel_admin_transfer |
❌ | ❌ | ❌ | ❌ | ✅ |
propose_revenue_pool |
✅ | ❌ | ❌ | ❌ | ❌ |
accept_revenue_pool |
❌ | ❌ | ❌ | ❌ | ✅ |
cancel_revenue_pool |
✅ | ❌ | ❌ | ❌ | ❌ |
pause |
✅ | ❌ | ❌ | ❌ | ❌ |
nuclear_pause |
❌ | ❌ | ❌ | ❌ | Admin only |
unpause |
✅ | ❌ | ❌ | ❌ | ❌ |
- Two-Step Owner Rotation: Prevents accidental loss of control by requiring the nominee to explicitly accept the role.
- Two-Step Admin Rotation: Prevents accidental loss of control by requiring the nominee to explicitly accept the role.
- Cancellation Safety: Provides
cancel_ownership_transferandcancel_admin_transferfunctions to abort mistaken nominations before acceptance. - Restricted Depositors: Only owner and explicitly allowed depositors can increase vault balance.
- Multisig-Guarded Nuclear Pause:
nuclear_pause(caller)accepts only the current admin address and callscaller.require_auth(), so deployments that set admin to a Stellar multisig account get native threshold enforcement for the emergency pause. - Nonce-Bound Authorized-Caller Rotation:
set_authorized_callerrequires the caller to supply the current monotonic nonce (see below), preventing a leaked owner signature from being replayed to reinstate a staleauthorized_caller.
set_authorized_caller maintains a monotonic u64 nonce stored under
StorageKey::AuthorizedCallerNonce in instance storage.
| Step | Who | Action |
|---|---|---|
| 1 | Integrator | Call get_authorized_caller_nonce() to read the current nonce (defaults to 0). |
| 2 | Owner | Call set_authorized_caller(new_caller, expected_nonce) with the value from step 1. |
| 3 | Contract | Verifies expected_nonce == stored_nonce; rejects with VaultError::StaleNonce if not. |
| 4 | Contract | Increments the stored nonce (wrapping_add(1)) and emits it in the event payload. |
Replay resistance: a captured owner signature contains a fixed expected_nonce.
After one successful rotation the stored nonce advances, so the captured signature is
permanently invalid.
Event payload: the set_authorized_caller event now carries
(old_caller, new_caller, consumed_nonce) as data, allowing off-chain indexers to
detect nonce gaps.
Nonce wrap: the nonce wraps to 0 after u64::MAX rotations (2^64 calls) — a
practical impossibility, but handled safely by wrapping_add.
Allows the current owner to cancel a pending ownership transfer before the nominee accepts it. This provides a safety mechanism to abort mistaken nominations.
Access Control: Only the current owner can call this function. Behavior:
- Removes the
PendingOwnerfrom storage - Emits
ownership_cancelledevent with current owner and cancelled nominee - Panics with "no ownership transfer pending" if no transfer is pending
Allows the current admin to cancel a pending admin transfer before the nominee accepts it. This provides a safety mechanism to abort mistaken nominations.
Access Control: Only the current admin can call this function. Behavior:
- Removes the
PendingAdminfrom storage - Emits
admin_cancelledevent with current admin and cancelled nominee - Panics with "no admin transfer pending" if no transfer is pending
The Callora Settlement contract tracks individual developer balances and global protocol revenue. It enforces strict access control for incoming payments and administrative updates.
Developer address compliance recoveries use propose_balance_migration and
execute_balance_migration. Both calls require authorization by the current
admin address, including its native Stellar multisig thresholds, and execution
is delayed by 24 hours. See Admin developer balance migration.
- Admin: Primary authority over contract configuration and sensitive data.
- Vault: The registered vault contract authorized to send payments.
- Pending Admin: Nominee awaiting acceptance of the admin role.
- Pending Vault: Proposed vault awaiting acceptance.
| Entrypoint | Allowed Callers | Rationale |
|---|---|---|
init |
Admin | Initial contract setup and role assignment |
record_deduction |
Vault | Accounting-only deduction logging from the vault |
receive_payment |
Vault + Admin | Forwarding settlement payment from vault, or manual reconciliation by admin |
batch_receive_payment |
Vault + Admin | Forwarding batch settlement payments from vault, or manual reconciliation by admin |
set_developer_min_balance (and alias set_minimum_balance) |
Admin | Setting minimum balance floor per developer |
propose_balance_migration |
Admin | Proposing developer balance migration |
execute_balance_migration |
Admin | Executing matured developer balance migration |
set_usdc_token |
Admin | Configuring settlement USDC token contract address |
withdraw_developer_balance |
Developer | Developer claiming/withdrawing earned credits as USDC |
set_developer_claim_window |
Admin | Restricting developer withdrawal time window |
clear_developer_claim_window |
Admin | Removing developer withdrawal claim window |
set_daily_withdraw_cap |
Admin | Configuring developer daily withdrawal limit |
force_credit_developer |
Admin | Direct admin developer credit without moving tokens (manual reconciliation / dispute resolution) |
get_all_developer_balances |
Admin | Restricted batch query of developer balances |
get_developer_balances_page |
Admin | Restricted paginated query of developer balances |
get_developer_balances_cursor |
Admin | Restricted cursor-paginated query of developer balances |
set_admin |
Admin | Nominating new admin address (step 1 of two-step transfer) |
accept_admin |
Pending Admin | Accepting admin nomination (step 2 of two-step transfer) |
cancel_admin_transfer |
Admin | Cancelling pending admin nomination |
propose_vault (and alias set_vault) |
Admin | Proposing new vault address (step 1 of two-step rotation) |
accept_vault |
Proposed Vault + Admin | Finalizing pending vault rotation (step 2 of two-step rotation) |
broadcast |
Admin | Broadcasting operator/emergency message |
upgrade |
Admin | Upgrading contract code WASM |
migrate_developer_balance (and alias migrate_single_dev_v2) |
Admin | Storage schema migration for developer balance |
migrate_v1_to_v2 / migrate_v1_to_v2_page |
Admin | Storage schema migration |
batch_withdraw_balance_cursor |
Anyone | Compatibility placeholder for cursor batch withdrawals |
batch_settle |
Anyone | Delegates to settlement batch helper |
freeze_developer |
Admin | Freezing developer withdrawals |
unfreeze_developer |
Admin | Unfreezing developer withdrawals |
set_price / remove_price |
Admin | Price registry management |
Note
Read-only view entrypoints (get_admin, get_vault, get_global_pool, get_total_received, get_developer_balance, get_developer_min_balance, get_minimum_balance, get_developer_claim_window, get_daily_withdraw_cap, get_withdrawal_today, get_pending_admin, get_balance_migration, get_usdc_token, get_version, version, migration_storage_version, is_developer_frozen, get_price, simulate_claim) do not require authentication and are accessible by any caller.
Important
Dual-Caller Security Risk:
The dual-caller authorization model for receive_payment and batch_receive_payment allows both the registered Vault and the contract Admin to credit developer balances. Because the Admin can call these entrypoints directly (in addition to force_credit_developer), the admin key has the technical capability to mint developer credits directly.
Operator Guidance: Operators sizing admin key protections must recognize that a compromised or misconfigured admin key can credit developer balances directly. Admin keys should be protected using strong security configurations (e.g., Stellar native multisig thresholds, multi-party signing policies, hardware security modules, and strict key management controls).
- Two-Step Admin Rotation: Prevents accidental loss of control by requiring the nominee to explicitly accept the role.
- Two-Step Vault Rotation: Prevents accidentally misrouting settlement credits by requiring the proposed vault to accept (or the admin to finalize).
- Per-Developer Claim Windows: Admins may configure inclusive ledger timestamp windows that restrict when each developer can claim accrued settlement balance. Developers without a configured window remain unrestricted.
- Restricted Views: Sensitive batch queries like
get_all_developer_balancesare restricted to the admin to prevent unnecessary exposure of the full ledger via the contract interface. - Cancellation Safety: The admin can invoke
cancel_admin_transferto clear a mistaken nomination.
The Callora Revenue Pool contract processes USDC distribution to developer wallets. Like Settlement and Vault, it implements standard administrative roles and rotation procedures.
- Admin: Handles revenue distributions and nominates administrative successions.
- Pending Admin: A nominated account that has to explicitly accept the role to become the Admin.
- Pause Guardian: Optional emergency role that may pause the revenue pool without receiving any distribution, unpause, upgrade, or admin-management authority.
Issue #730 audit (GrantFox FWC26 Stellar Wave, 2026-07-28): every state-changing entrypoint below was verified to call
require_authon its actingAddressbefore touching storage or emitting events. No gaps were found. Focused per-entrypoint tests are maintained incontracts/tests/src/grant_fox_fwc26_auth_matrix.rs(revenue_pool_auditmodule) andcontracts/revenue_pool/tests/auth_snap.rs.
| Function | require_auth on |
Admin | Pending Admin | Pause Guardian | Others |
|---|---|---|---|---|---|
set_admin |
caller |
✅ | ❌ | ❌ | ❌ |
accept_admin |
caller |
❌ | ✅ | ❌ | ❌ |
claim_admin (alias of accept_admin) |
caller |
❌ | ✅ | ❌ | ❌ |
cancel_admin_transfer |
caller |
✅ | ❌ | ❌ | ❌ |
set_pause_guardian |
caller |
✅ | ❌ | ❌ | ❌ |
clear_pause_guardian |
caller |
✅ | ❌ | ❌ | ❌ |
pause |
caller |
✅ | ❌ | ✅ | ❌ |
unpause |
caller |
✅ | ❌ | ❌ | ❌ |
receive_payment |
caller |
✅ | ❌ | ❌ | ❌ |
deposit_yield |
treasury |
✅ (treasury == admin) | ❌ | ❌ | ❌ |
set_max_distribute |
caller |
✅ | ❌ | ❌ | ❌ |
distribute |
caller |
✅ | ❌ | ❌ | ❌ |
batch_distribute |
caller |
✅ | ❌ | ❌ | ❌ |
upgrade |
caller |
✅ | ❌ | ❌ | ❌ |
broadcast |
caller |
✅ | ❌ | ❌ | ❌ |
propose_emergency_drain |
caller |
✅ | ❌ | ❌ | ❌ |
execute_emergency_drain |
caller |
✅ | ❌ | ❌ | ❌ |
cancel_emergency_drain |
caller |
✅ | ❌ | ❌ | ❌ |
Read-only entrypoints (get_admin, get_usdc_token, get_pending_admin,
get_pause_guardian, is_paused, get_cumulative_yield_deposited,
get_max_distribute, balance, get_version, version, get_ttl_policy,
get_pending_emergency_drain, chunk_iter) do not require auth and are
callable by anyone.
The current admin can call cancel_admin_transfer to abort a pending admin nomination.
The current admin can call set_pause_guardian to delegate emergency pause authority to a narrow role, and clear_pause_guardian to remove that role. The pause guardian can only call pause; it cannot unpause, distribute funds, rotate admin, change caps, clear or replace itself, or upgrade the contract.
| Date | Issue | Scope | Outcome |
|---|---|---|---|
| 2026-07-28 | #730 | All 18 state-changing entrypoints on callora-revenue-pool |
✅ All entrypoints call require_auth; no gaps found. Focused tests added to revenue_pool_audit module in grant_fox_fwc26_auth_matrix.rs. |
The CalloraYieldLimits contract caps how many open bets, open positions and
active subscriptions one account may hold. It has no fund custody: the only
things at stake are the counters, the configured caps and the WASM version.
- Admin: installs itself through
init, sets defaults and per-account caps, manages the operator registry and upgrades the contract. - Pending Admin: a nominated account that must
accept_adminbefore it gains authority. - Operator: a backend address registered by the admin that reports real bets, positions and subscriptions by mutating per-account counters.
- Account: the address whose counters are mutated. It needs no auth of its own; the cap is enforced on it.
| Function | require_auth on |
Admin | Operator | Others |
|---|---|---|---|---|
init |
admin |
✅ (first call only) | ❌ | ❌ |
set_admin |
caller |
✅ | ❌ | ❌ |
accept_admin |
caller |
❌ (pending admin) | ❌ | ❌ |
cancel_admin_transfer |
caller |
✅ | ❌ | ❌ |
upgrade |
caller |
✅ | ❌ | ❌ |
set_default_limits |
caller |
✅ | ❌ | ❌ |
set_account_limits |
caller |
✅ | ❌ | ❌ |
clear_account_limits |
caller |
✅ | ❌ | ❌ |
set_operator |
caller |
✅ | ❌ | ❌ |
remove_operator |
caller |
✅ | ❌ | ❌ |
place_bet / clear_bet |
operator |
✅ (if registered) | ✅ (if registered) | ❌ |
open_position / close_position |
operator |
✅ (if registered) | ✅ (if registered) | ❌ |
subscribe / unsubscribe |
operator |
✅ (if registered) | ✅ (if registered) | ❌ |
Read-only entrypoints (get_admin, get_default_limits,
get_account_limits, get_account_state, get_operator, get_version,
can_place_bet, can_open_position, can_subscribe) do not require auth.
- Init front-running.
initcallsadmin.require_auth()before it reads or writes storage, so nobody can claim an uninitialized deployment by callinginitwith their own address. An unsignedinitfails with an auth error and stores nothing, so the legitimate admin can still initialize afterwards. - Self-reset of caps. A capped account used to call
clear_bet(its own counter) and thenplace_betagain to bypass the cap indefinitely. Counter mutations now name the account but only a registered operator may sign, so the account cannot touch its own counters.clear_*only decrements, so it cannot be used to manufacture headroom either. - Error precedence. A counter mutation checks the operator's signature,
then its registration (
Unauthorized), then the cap (BetsAtCap/PositionsAtCap/SubscriptionsAtCap), then underflow. An unregistered caller therefore never learns an account's remaining headroom.
set_operator(caller, operator) (admin only) writes
StorageKey::Operator(operator) = true in persistent storage and emits
operator_set. remove_operator clears the flag and emits operator_removed;
removing an unknown address is a no-op. Registered operators can be revoked at
any time — the next counter mutation from that address fails with
Unauthorized, while the counters it already wrote stay untouched. The flag's
TTL is refreshed on every write and on every authorized read by a mutation.
The implementation includes comprehensive tests covering:
- ✅ Issue #730 — Revenue Pool auth audit (FWC26 Stellar Wave): every state-changing
entrypoint audited; focused three-variant tests (
_no_auth,_wrong_role,_authorized) added tocontracts/tests/src/grant_fox_fwc26_auth_matrix.rs(revenue_pool_auditmodule). Existing per-entrypoint coverage incontracts/revenue_pool/tests/auth_snap.rsandcontracts/revenue_pool/tests/emergency_auth_snap.rsconfirmed complete. - ✅
set_authorized_callerdefault nonce is0before first rotation - ✅ First rotation with nonce
0succeeds and advances stored nonce to1 - ✅ Replaying a consumed nonce is rejected with
VaultError::StaleNonce - ✅ Supplying a future nonce is rejected with
VaultError::StaleNonce - ✅ Three sequential rotations each advance the nonce correctly
- ✅ Nonce wraps at
u64::MAXviawrapping_add - ✅ Failed rotations do not advance the stored nonce
- ✅ Successful rotation emits
(old, new, consumed_nonce)in the event payload - ✅ Vault self-address is rejected as
new_caller - ✅ Admin and Vault can call
receive_payment - ✅ Unauthorized callers are rejected from
receive_payment - ✅ Only Admin can call
set_adminandpropose_vault(and theset_vaultalias) - ✅ Only Admin or Pending Vault can call
accept_vault - ✅ Only Pending Admin can call
accept_admin - ✅ Only Admin can call
get_all_developer_balances - ✅ All rotation and update logic preserves state integrity
- ✅ Only current owner can call
cancel_ownership_transfer - ✅ Only current admin can call
cancel_admin_transferin Vault, Settlement, and Revenue Pool - ✅ Cancel functions clear pending state and emit events
- ✅ Cancel functions fail when no transfer is pending
- ✅ Cancel functions fail for unauthorized callers
- ✅ After cancellation, new nominations can be made
Run tests with:
cargo build --workspace --release --target=wasm32-unknown-unknown-| set_allowed_depositor | ✅ | ❌ | ❌ | ❌ | ❌ |
-| clear_allowed_depositors | ✅ | ❌ | ❌ | ❌ | ❌ |
+| add_address | ✅ | ❌ | ❌ | ❌ | ❌ |
+| clear_all | ✅ | ❌ | ❌ | ❌ | ❌ |
+| get_allowlist (view) | ✅ | ✅ | ✅ | ✅ | ✅ |
+| is_authorized_depositor (view) | ✅ | ✅ | ✅ | ✅ | ✅ |