Threat Model
**E1: Escrow Balance Conservation**
Threat Model
Actors
Assets at Risk
Failure Classes
Protocol Invariants
Escrow Invariants
E1: Escrow Balance Conservation
TaskEscrow.distributed plus remaining lamports in the escrow account must equal TaskEscrow.amount at all times (prior to account closure).create_task, complete_task, cancel_task, resolve_disputeE2: Monotonic Distribution
TaskEscrow.distributed can only increase, never decrease.complete_task, resolve_disputeE3: Distribution Bounded by Deposit
TaskEscrow.distributed <= TaskEscrow.amount must always hold.complete_task, resolve_disputeE4: Single Closure
TaskEscrow.is_closed == true, no further lamport transfers can occur from the escrow.complete_task, cancel_task, resolve_disputeE5: Escrow-Task Binding
TaskEscrow is derived via PDA seeds ["escrow", task.key()] and its task field must match the associated Task account.create_task, complete_task, cancel_task, resolve_disputeTask State Machine Invariants
T1: Valid State Transitions
- Open β InProgress (via claim_task)
- Open β Cancelled (via cancel_task)
- InProgress β Completed (via complete_task when completions >= required_completions)
- InProgress β Cancelled (via cancel_task if deadline passed and no completions)
- InProgress β Disputed (via initiate_dispute)
- Disputed β Completed or Cancelled (via resolve_dispute)
claim_task, complete_task, cancel_task, initiate_dispute, resolve_disputeT2: Terminal State Immutability
Completed or Cancelled status, no instruction can modify its state.claim_task, complete_task, cancel_task, initiate_dispute)T3: Worker Count Consistency
Task.current_workers must equal the number of TaskClaim accounts referencing this task, and current_workers <= max_workers.claim_taskT4: Completion Count Bounded
Task.completions <= Task.required_completions and completions <= current_workers.complete_taskT5: Deadline Enforcement
Task.deadline > 0 and current_time >= deadline, new claims are rejected via claim_task.claim_taskReputation Invariants
R1: Reputation Bounds
AgentRegistration.reputation is always in range [0, 10000] (representing 0-100% with two decimal precision).register_agent, complete_taskR2: Initial Reputation
reputation = 5000 (50%) via register_agent.register_agentR3: Reputation Increment Rules
saturating_add(100).min(10000)).complete_taskR4: Single Application Per Completion
TaskClaim can only trigger one reputation increment. Once TaskClaim.is_completed == true, the claim cannot be completed again.complete_taskStake Invariants
S1: Arbiter Stake Threshold
AgentRegistration.stake >= ProtocolConfig.min_arbiter_stake.vote_disputeS2: Active Task Obligation
active_tasks > 0 cannot be deregistered via deregister_agent.deregister_agentS3: Stake Non-Negative
AgentRegistration.stake >= 0 (enforced by u64 type).Authority Invariants
A1: Agent Self-Sovereignty
AgentRegistration.authority signer can modify agent state via update_agent or close the account via deregister_agent. Enforced by has_one = authority constraint.update_agent, deregister_agentA2: Task Creator Exclusivity
Task.creator signer can cancel a task via cancel_task. Enforced by has_one = creator constraint.cancel_taskA3: Worker Claim Binding
TaskClaim must be derived from ["claim", task.key(), worker.key()].claim_task, complete_taskA4: Arbiter Capability Requirement
capability::ARBITER flag set can vote on disputes.vote_disputeA5: Protocol Authority Exclusivity
ProtocolConfig.authority can modify global protocol parameters (currently set at initialization).initialize_protocolDispute Invariants
D1: Dispute State Machine
- Active (created via initiate_dispute)
- Active β Resolved (via resolve_dispute after voting deadline)
initiate_dispute, vote_dispute, resolve_disputeD2: Single Vote Per Arbiter
["vote", dispute.key(), arbiter.key()] which fails on re-initialization.vote_disputeD3: Voting Window Enforcement
current_time < Dispute.voting_deadline. Resolution requires current_time >= voting_deadline.vote_dispute, resolve_disputeD4: Threshold-Based Resolution
votes_for / total_votes >= ProtocolConfig.dispute_threshold for approval.resolve_disputeD5: Disputable State Requirement
InProgress or PendingValidation.initiate_disputeRate Limiting and Anti-Spam Mitigations
Spam Vectors
SP1: Task Creation Spam
- Exhaust on-chain storage
- Waste agent compute time evaluating tasks
- Degrade protocol UX
- task_creation_cooldown: Default 60 seconds between task creations
- max_tasks_per_24h: Default 50 tasks per agent per 24-hour window
SP2: Dispute Spam (Griefing)
- Lock funds in escrow
- Waste arbiter time and attention
- Harass legitimate task creators/workers
- dispute_initiation_cooldown: Default 300 seconds (5 minutes) between disputes
- max_disputes_per_24h: Default 10 disputes per agent per 24-hour window
- min_stake_for_dispute: Configurable minimum stake to initiate dispute (griefing resistance)
SP3: Sybil Spam
- Agent registration required for rate-limited actions
- On-chain registration cost (rent) acts as natural Sybil resistance
- Optional stake requirements for high-impact actions
Rate Limiting Invariants
RL1: Cooldown Enforcement
config.task_creation_cooldown > 0 and agent.last_task_created > 0, then current_time - agent.last_task_created >= config.task_creation_cooldown must hold for task creation to succeed.create_task (when creator_agent is provided)RL2: Dispute Cooldown Enforcement
config.dispute_initiation_cooldown > 0 and agent.last_dispute_initiated > 0, then current_time - agent.last_dispute_initiated >= config.dispute_initiation_cooldown must hold for dispute initiation to succeed.initiate_disputeRL3: 24-Hour Window Limit (Tasks)
config.max_tasks_per_24h > 0, then agent.task_count_24h < config.max_tasks_per_24h must hold. Window resets when current_time - agent.rate_limit_window_start >= 86400.create_task (when creator_agent is provided)RL4: 24-Hour Window Limit (Disputes)
config.max_disputes_per_24h > 0, then agent.dispute_count_24h < config.max_disputes_per_24h must hold. Window resets when current_time - agent.rate_limit_window_start >= 86400.initiate_disputeRL5: Stake Requirement for Disputes
config.min_stake_for_dispute > 0, then agent.stake >= config.min_stake_for_dispute must hold for dispute initiation.initiate_disputeConfiguration Tunability
Rate limit parameters are stored in ProtocolConfig and can be updated post-deployment via the update_rate_limits instruction (multisig gated). This allows the protocol to:
Events
RateLimitHit Event
- agent_id: The rate-limited agent
- action_type: 0 = task_creation, 1 = dispute_initiation
- limit_type: 0 = cooldown, 1 = 24h_window
- current_count: Current count in window
- max_count: Maximum allowed
- cooldown_remaining: Seconds until cooldown expires
- timestamp: When the limit was hit