Skip to main content

Overview

The OrchestratorRegistry is an owner-managed allowlist that controls which orchestrator contracts are authorized to call EscrowV2 functions. It provides a security layer ensuring only vetted and approved orchestrators can manage escrow deposits and intents. Contract Location: contracts/registries/OrchestratorRegistry.sol

Purpose

The OrchestratorRegistry serves as a critical security control:
  • Access Control: Restricts which contracts can interact with escrow funds
  • Orchestrator Allowlist: Maintains a list of authorized orchestrator implementations
  • Security Layer: Prevents unauthorized contracts from managing deposits
  • Upgrade Management: Controls rollout of new orchestrator versions

State Variables

mapping(address => bool) public override isOrchestrator;
  • isOrchestrator: Maps orchestrator addresses to their authorization status

Core Functions

Adding Orchestrators

function addOrchestrator(address _orchestrator) external override onlyOwner
Authorizes an orchestrator contract to interact with escrow. Parameters:
  • _orchestrator: Address of the orchestrator contract to authorize
Requirements:
  • Orchestrator address cannot be zero
  • Orchestrator must not already be added
  • Only callable by owner
Emits: OrchestratorAdded(address indexed orchestrator) Reverts:
  • ZeroAddress(): If _orchestrator is the zero address
  • OrchestratorAlreadyAdded(address orchestrator): If orchestrator is already authorized
Reference: contracts/registries/OrchestratorRegistry.sol:35

Removing Orchestrators

function removeOrchestrator(address _orchestrator) external override onlyOwner
Revokes authorization for an orchestrator contract. Parameters:
  • _orchestrator: Address of the orchestrator contract to de-authorize
Requirements:
  • Orchestrator address cannot be zero
  • Orchestrator must currently be authorized
  • Only callable by owner
Emits: OrchestratorRemoved(address indexed orchestrator) Reverts:
  • ZeroAddress(): If _orchestrator is the zero address
  • OrchestratorNotFound(address orchestrator): If orchestrator is not currently authorized
Reference: contracts/registries/OrchestratorRegistry.sol:48

View Functions

Check Orchestrator Authorization

mapping(address => bool) public isOrchestrator;
Public mapping to check if an address is an authorized orchestrator:
bool authorized = orchestratorRegistry.isOrchestrator(orchestratorAddress);

Integration with Core Contracts

EscrowV2

EscrowV2 is the primary consumer of the OrchestratorRegistry. It uses the registry to validate that callers are authorized orchestrators:
// In EscrowV2 constructor
orchestratorRegistry = IOrchestratorRegistry(_orchestratorRegistry);

// Validation in EscrowV2 functions
modifier onlyOrchestrator() {
    require(
        orchestratorRegistry.isOrchestrator(msg.sender),
        "Caller is not an authorized orchestrator"
    );
    _;
}
Functions Protected:
  • Deposit creation on behalf of users
  • Intent signaling and locking
  • Payment releases
  • Deposit withdrawals
Reference: contracts/EscrowV2.sol:127

Payment Verifiers

Payment verifiers (like UnifiedPaymentVerifier) check the OrchestratorRegistry to ensure they’re being called by authorized orchestrators:
orchestratorRegistry = IOrchestratorRegistry(_orchestratorRegistry);

// Validation before processing proofs
require(
    orchestratorRegistry.isOrchestrator(msg.sender),
    "Caller not authorized orchestrator"
);
Reference: contracts/unifiedVerifier/BaseUnifiedPaymentVerifier.sol:36

Hooks

Intent hooks validate orchestrator authorization:
  • WhitelistPreIntentHook: Checks orchestrator authorization before allowing whitelisted operations
  • SignatureGatingPreIntentHook: Validates orchestrator before processing gated intents
  • AcrossBridgeHookV2: Ensures only authorized orchestrators can trigger bridge operations
Reference: contracts/hooks/WhitelistPreIntentHook.sol:33

Access Control

Custom Errors

error ZeroAddress();
error OrchestratorAlreadyAdded(address orchestrator);
error OrchestratorNotFound(address orchestrator);
Custom errors provide gas-efficient and informative error handling. Reference: contracts/registries/OrchestratorRegistry.sol:15

Owner Functions

Only the contract owner can manage orchestrator authorization:
  • addOrchestrator(): Authorize new orchestrators
  • removeOrchestrator(): Revoke orchestrator authorization
Access Control Pattern: OpenZeppelin’s Ownable with onlyOwner modifier

Public View

The isOrchestrator mapping is publicly readable, allowing any contract or user to verify orchestrator authorization status.

Events

event OrchestratorAdded(address indexed orchestrator);
event OrchestratorRemoved(address indexed orchestrator);
These events enable:
  • Tracking orchestrator authorization changes
  • Off-chain monitoring of authorized orchestrators
  • Audit trails for governance actions

Security Model

Authorization Security

  1. Centralized Control: Only owner can modify orchestrator list
  2. Explicit Authorization: Orchestrators must be explicitly added
  3. Revocability: Authorization can be revoked at any time
  4. Zero Address Protection: Prevents authorization of invalid addresses

Integration Security

Proper integration requires:
  1. Pre-Call Validation: Contracts must check isOrchestrator before granting access
  2. Modifier Usage: Use onlyOrchestrator modifier for protected functions
  3. Event Monitoring: Watch for orchestrator changes to update off-chain systems

Upgrade Considerations

  • Removing an orchestrator doesn’t affect its existing operations (deposits, intents)
  • New orchestrators can be added without disrupting existing ones
  • Registry contract itself can be upgraded by updating reference in EscrowV2

Usage Pattern

Deployment and Setup

// 1. Deploy OrchestratorRegistry
OrchestratorRegistry registry = new OrchestratorRegistry();

// 2. Authorize orchestrators
registry.addOrchestrator(orchestratorV1Address);
registry.addOrchestrator(orchestratorV2Address);

// 3. Configure EscrowV2 with registry
escrowV2.setOrchestratorRegistry(address(registry));

Upgrading Orchestrators

// Deploy new orchestrator version
OrchestratorV3 newOrchestrator = new OrchestratorV3(...);

// Authorize new version
orchestratorRegistry.addOrchestrator(address(newOrchestrator));

// Optionally, remove old version
orchestratorRegistry.removeOrchestrator(oldOrchestratorAddress);

Validation in Protected Contracts

contract ProtectedContract {
    IOrchestratorRegistry public orchestratorRegistry;
    
    modifier onlyAuthorizedOrchestrator() {
        require(
            orchestratorRegistry.isOrchestrator(msg.sender),
            "Not authorized"
        );
        _;
    }
    
    function protectedFunction() external onlyAuthorizedOrchestrator {
        // Protected logic
    }
}

Deployment Considerations

  1. Single Registry: Use one OrchestratorRegistry per chain for all orchestrator versions
  2. Initial Authorization: Authorize initial orchestrator(s) immediately after deployment
  3. Registry Updates: EscrowV2 allows updating registry reference via setOrchestratorRegistry()
  4. Multi-Version Support: Multiple orchestrator versions can be authorized simultaneously

Build docs developers (and LLMs) love