Skip to main content

Delegate Authorization — Program Escrow RBAC

This document describes the delegate permission model for the program-escrow contract, including normal delegation, permission bitmasks, and the emergency revocation fast-path.


Overview

The program-escrow contract uses a two-tier access control model:

RoleSet byCapabilities
Admininitialize_contractAll operations, including emergency revocation
Authorized Payout Keyinit_programDay-to-day payouts, delegate management
Delegateset_program_delegateSubset of operations granted via bitmask

Permission Bitmask

Delegate permissions are stored as a u32 bitmask. The following bits are defined:

ConstantBitValueDescription
DELEGATE_PERMISSION_RELEASE00x1May trigger scheduled releases
DELEGATE_PERMISSION_REFUND10x2May initiate refunds
DELEGATE_PERMISSION_UPDATE_META20x4May update program metadata
DELEGATE_PERMISSION_MASK0x7All valid bits combined

Combining bits grants multiple permissions, e.g. RELEASE | UPDATE_META = 0x5.

Any bits outside DELEGATE_PERMISSION_MASK are rejected with a panic: "Unsupported delegate permissions".


Setting a Delegate

client.set_program_delegate(
&program_id,
&caller, // must be authorized_payout_key or admin
&delegate, // must differ from authorized_payout_key
&permissions, // bitmask, must be within DELEGATE_PERMISSION_MASK
);

Emits: ProgramDelegateSetEvent

topics : (PrgDlgS, program_id)
data : ProgramDelegateSetEvent {
version: u32, // EVENT_VERSION_V2 = 2
program_id: String,
delegate: Address,
permissions: u32, // granted bitmask
updated_by: Address,
timestamp: u64,
}

Normal Revocation

The payout key owner or admin can revoke a delegate at any time:

client.revoke_program_delegate(&program_id, &caller);

Emits: ProgramDelegateRevokedEvent with emergency: false


Emergency Revocation

Motivation

revoke_program_delegate requires the caller to be either the authorized payout key or the admin. If the payout key itself is compromised or unresponsive, a fast-path is needed that only requires the admin.

emergency_revoke_delegate addresses this gap:

  • Callable only by the contract admin (set via initialize_contract).
  • Immediately zeros all delegate permissions in the same ledger as the call.
  • No grace period — the revocation is atomic and instantaneous.
  • Idempotent — safe to call even when no delegate is currently set.
  • Emits ProgramDelegateRevokedEvent with emergency: true to distinguish it from normal revocation in indexers and monitoring systems.

Usage

client.emergency_revoke_delegate(
&program_id,
&delegate, // address of the compromised delegate
);

Authorization Matrix

Callerrevoke_program_delegateemergency_revoke_delegate
Contract admin
Authorized payout key
Delegate
Arbitrary third party

Event Schema

topics : (PrgDlgR, program_id)
data : ProgramDelegateRevokedEvent {
version: u32, // EVENT_VERSION_V2 = 2
program_id: String,
delegate: Address, // the address whose permissions were zeroed
revoked_by: Address, // admin that performed the revocation
timestamp: u64,
emergency: bool, // true = emergency_revoke_delegate
// false = revoke_program_delegate
}

Indexers must check the emergency field to distinguish the two revocation paths. A monitoring system should alert on any event where emergency: true.

Error Codes

ConditionBehaviour
Admin key not initializedpanic "Not initialized"
Caller is not adminpanic "Unauthorized"
program_id does not existpanic "Program not found"
No delegate currently setNo-op; event still emitted

Security Assumptions

  1. Admin key compromise is out of scope — if the admin key is compromised the entire contract must be treated as compromised. Protect it with a hardware security module (HSM) or multi-sig.
  2. Delegate cannot self-revoke — a delegate cannot call emergency_revoke_delegate on itself; this would allow a malicious delegate to prevent its own future liability.
  3. Permissions are zeroed atomically — there is no window between the auth check and the storage write where a delegate could act with stale permissions.
  4. Idempotency — repeated calls with the same arguments have no additional effect beyond the first; this prevents griefing via repeated event emission.

Error Codeu32Meaning
ContractError::Unauthorized1Caller lacks required role
ContractError::DelegateNotSet102Operation requires a delegate that is absent
ContractError::DelegatePermissionsInsufficient103Delegate lacks required permission

See Also