Manager Contract

The Manager.sol contract serves as the central coordination layer for the entire Worldbook ecosystem. It implements sophisticated access control, fee management, orderbook registration, and emergency response capabilities.

Contract Overview

  • File: Manager.sol

  • Size: ~900 lines of code

  • Role: Central coordination and control layer

  • Security: Role-based access control with inheritance hierarchy

Key Responsibilities

1. Access Control System

  • Role Management: Hierarchical role system with inheritance

  • Permission Delegation: Centralized permission checking for all OrderBooks

  • Emergency Controls: Pause/unpause capabilities across the entire system

2. OrderBook Registry

  • Registration: Validates and registers new OrderBook contracts

  • Bytecode Verification: Ensures only legitimate contracts are registered

  • Pair Management: Manages unique trading pairs and prevents duplicates

3. Fee Management

  • 4-Tier Fee System: Sophisticated fee structure with customization options

  • Maker Rebates: Support for negative maker fees (rebates)

  • Fee Collection: Centralized fee collection and distribution

  • User Discounts: Percentage and absolute discount mechanisms

4. Token Whitelist and Standard Token Registry

  • Quote Token Control: Manages which tokens can be used as quote assets

  • Minimum Amounts: Sets minimum order sizes per token

  • Standard Tokens: Registry for exact-transfer ERC-20s and optional enforcement for BASE on registration

  • Dynamic Management: Add/remove tokens from whitelist

5. Self-Trade Prevention (STP)

  • User STP Modes: Configurable self-trade prevention behavior

  • Three Modes: NONE, EXPIRE_MAKER, and SKIP

  • Default Mode: EXPIRE_MAKER for new users

Access Control Architecture

Role Hierarchy

DEFAULT_ADMIN_ROLE (Role Management Only)


ADMIN_ROLE (Full Operational Control + PAUSER privileges)


PAUSER_ROLE (Emergency Response)

Important: ADMIN_ROLE automatically has PAUSER_ROLE privileges through the hasOrderBookRole function implementation. The Manager contract itself also holds PAUSER_ROLE to perform batch pause/unpause.

Role Definitions

// Role constants
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant FACTORY_ROLE = keccak256("FACTORY_ROLE");

// Role checking function used by all OrderBooks with inheritance
function hasOrderBookRole(bytes32 role, address account) external view returns (bool) {
    // Direct role check
    if (hasRole(role, account)) {
        return true;
    }
    
    // Implement role inheritance: ADMIN_ROLE includes PAUSER_ROLE privileges
    if (role == PAUSER_ROLE && hasRole(ADMIN_ROLE, account)) {
        return true;
    }
    
    return false;
}

Role Capabilities

DEFAULT_ADMIN_ROLE:

  • Grant and revoke ADMIN_ROLE

  • Grant and revoke PAUSER_ROLE

  • Role management only (no operational privileges)

ADMIN_ROLE:

  • All operational functions

  • Fee management

  • OrderBook registration control

  • Token whitelist management

  • System configuration

  • Inherits PAUSER_ROLE privileges

PAUSER_ROLE:

  • Emergency pause/unpause operations

  • Community managers and emergency responders

  • Limited to emergency response only

Fee Management System

4-Tier Fee Structure

Worldbook implements a sophisticated fee system with four layers:

Layer 1: Default Fees

int256 public override makerFee;    // Can be negative for rebates
uint256 public override takerFee;   // Always non-negative

Maker Rebates:

  • Maker fees can be negative to incentivize liquidity provision

  • Rebates are funded from taker fees

  • Maximum rebate: 0.05% (500 basis points, different from max fee of 0.5%)

Layer 2: OrderBook-Specific Overrides

mapping(address => int256) public orderbookMakerFeeOverrides;
mapping(address => uint256) public orderbookTakerFeeOverrides;

Layer 3: User Percentage Discounts

mapping(address => uint256) public userMakerFeeDiscountFactor;
mapping(address => uint256) public userTakerFeeDiscountFactor;

Layer 4: User Absolute Discounts

mapping(address => uint256) public userMakerFeeDiscountAbs;
mapping(address => uint256) public userTakerFeeDiscountAbs;

Fee Calculation Logic

// Fee calculation with rebate support
function _calculateMakerFeeRate(address orderbook, address user) internal view returns (int256) {
    // Step 1: Get base rate (orderbook override OR default)
    int256 baseRate = orderbookMakerFeeOverrides[orderbook] != 0 ? 
                      orderbookMakerFeeOverrides[orderbook] : 
                      makerFee;

    // Step 2: Apply percentage discount ONLY if base rate is positive
    int256 afterPercentageDiscount = baseRate;
    if (baseRate > 0) {
        uint256 discountFactor = userMakerFeeDiscountFactor[user];
        if (discountFactor > 0 && discountFactor < 10000000) {
            afterPercentageDiscount = int256(uint256(baseRate) * discountFactor / FEE_DENOM);
        }
    }

    // Step 3: Apply absolute discount (can turn positive fees into rebates)
    uint256 absoluteDiscount = userMakerFeeDiscountAbs[user];
    return afterPercentageDiscount - int256(absoluteDiscount);
}

Fee Collection

address public override feeCollector;

function setFeeCollector(address _feeCollector) external override onlyAdminRole {
    if (_feeCollector == address(0)) revert Manager__InvalidFeeCollector();
    if (_feeCollector != feeCollector) {
        address oldFeeCollector = feeCollector;
        feeCollector = _feeCollector;
        emit FeeCollectorUpdated(oldFeeCollector, _feeCollector);
    }
}

OrderBook Registration System

Registration Process

function registerOrderBook(
    address baseToken,
    address quoteToken,
    address orderBookAddress
) external payable virtual override

Validation Steps:

  1. Access Control: If allowAnyoneRegisterOrderBook is false, only ADMIN_ROLE may register. Callers with FACTORY_ROLE bypass bytecode checks.

  2. Registration Fee: Require payment of registrationFee (admins exempt)

  3. Input Validation: Verify token addresses and uniqueness

  4. Quote Token Check: Ensure quote token is whitelisted (if active)

  5. Bytecode Verification: Verify contract matches expected bytecode (exact or normalized hash)

  6. Parameter Verification: Validate OrderBook configuration

  7. Storage: Store registration and emit event

  8. Fee Transfer: Send fee to collector, refund excess

Registration Fee System

The Manager supports optional registration fees for OrderBook creation:

uint256 public registrationFee = 0.001 ether; // Default: 0.001 ETH

function setRegistrationFee(uint256 _fee) external onlyAdminRole

Key Features:

  • Admin role is exempt from registration fees

  • Fees are sent directly to the fee collector

  • Excess payment is automatically refunded

  • Can be set to 0 to disable fees

  • Prevents spam OrderBook creation

Bytecode Verification

bytes32 public override expectedBytecodeHash;

function setExpectedBytecodeHash(bytes32 _newHash) external override onlyAdminRole {
    if (_newHash == bytes32(0)) revert Manager__ZeroBytecodeHash();
    if (_newHash != expectedBytecodeHash) {
        bytes32 oldHash = expectedBytecodeHash;
        expectedBytecodeHash = _newHash;
        emit ExpectedBytecodeHashSet(oldHash, _newHash);
    }
}

Security Benefits:

  • Prevents registration of malicious contracts

  • Ensures only approved OrderBook versions are used

  • Maintains system integrity and user trust

Normalized Bytecode Verification

To support OrderBooks with different immutable constructor parameters (manager/base/quote addresses, decimals, scales), the Manager can verify a canonical normalized runtime hash:

bytes32 public override expectedNormalizedBytecodeHash;

function setExpectedNormalizedBytecodeHash(bytes32 _newHash) external onlyAdminRole {
    if (_newHash == bytes32(0)) revert Manager__ZeroBytecodeHash();
    if (_newHash != expectedNormalizedBytecodeHash) {
        bytes32 oldHash = expectedNormalizedBytecodeHash;
        expectedNormalizedBytecodeHash = _newHash;
        emit ExpectedNormalizedBytecodeHashSet(oldHash, _newHash);
    }
}

// Optional fast-path: zero-out configured [offset,length] windows before hashing
function setNormalizationMask(uint32[] calldata offsets, uint32[] calldata lengths) external onlyAdminRole;

// Tooling helper
function computeNormalizedRuntimeHash(address orderBook) external view returns (bytes32);

If the caller has FACTORY_ROLE, bytecode checks are skipped to allow trusted factories to register books.

Normalized Bytecode Verification

To support OrderBooks with different immutable constructor parameters (manager/base/quote addresses, decimals, scales), the Manager can verify a canonical normalized runtime hash:

bytes32 public override expectedNormalizedBytecodeHash;

function setExpectedNormalizedBytecodeHash(bytes32 _newHash) external onlyAdminRole {
    if (_newHash == bytes32(0)) revert Manager__ZeroBytecodeHash();
    if (_newHash != expectedNormalizedBytecodeHash) {
        bytes32 oldHash = expectedNormalizedBytecodeHash;
        expectedNormalizedBytecodeHash = _newHash;
        emit ExpectedNormalizedBytecodeHashSet(oldHash, _newHash);
    }
}

// Optional fast-path: zero-out configured [offset,length] windows before hashing
function setNormalizationMask(uint32[] calldata offsets, uint32[] calldata lengths) external onlyAdminRole;

// Tooling helper
function computeNormalizedRuntimeHash(address orderBook) external view returns (bytes32);

If the caller has FACTORY_ROLE, bytecode checks are skipped to allow trusted factories to register books.

Pair Management

mapping(bytes32 => address) public override orderBooks;
address[] public allOrderBooks;

function getPairHash(address baseToken, address quoteToken) public pure returns (bytes32) {
    // Deterministic hash regardless of token order
    (address token0, address token1) = baseToken < quoteToken ? 
        (baseToken, quoteToken) : (quoteToken, baseToken);
    return keccak256(abi.encodePacked(token0, token1));
}

// Append-only per-pair index (latest registered remains the canonical pointer above)
function getPairOrderBooks(address tokenA, address tokenB) external view returns (address[] memory);
 
// Append-only per-pair index (latest registered remains the canonical pointer above)
function getPairOrderBooks(address tokenA, address tokenB) external view returns (address[] memory);

Quote Token Whitelist System

Whitelist Control

bool public override isQuoteWhitelistActive;
mapping(address => bool) public override isQuoteTokenWhitelisted;
mapping(address => uint256) public override quoteTokenMinAmounts;

Whitelist Management

function addOrUpdateQuoteToken(address _token, uint256 _minQuoteAmount) external override onlyAdminRole {
    if (_token == address(0)) revert Manager__ZeroAddressToken();

    bool wasWhitelisted = isQuoteTokenWhitelisted[_token];
    uint256 oldMinAmount = quoteTokenMinAmounts[_token];

    if (!wasWhitelisted || oldMinAmount != _minQuoteAmount) {
        isQuoteTokenWhitelisted[_token] = true;
        quoteTokenMinAmounts[_token] = _minQuoteAmount;
        
        if (!wasWhitelisted) {
            allWhitelistedQuoteTokens.push(_token);
            quoteTokenToIndex[_token] = allWhitelistedQuoteTokens.length - 1;
            emit QuoteTokenWhitelisted(_token, _minQuoteAmount);
        } else {
            emit QuoteTokenMinAmountUpdated(_token, oldMinAmount, _minQuoteAmount);
        }
    }
}

Emergency Controls

System-Wide Pause

function pauseAllOrderBooks() external onlyPauserRole returns (uint256[] memory pausedIndices) {
    uint256 length = allOrderBooks.length;
    uint256[] memory tempIndices = new uint256[](length);
    uint256 pausedCount = 0;
    
    for (uint256 i = 0; i < length; i++) {
        try IOrderBook(allOrderBooks[i]).pause() {
            tempIndices[pausedCount] = i;
            pausedCount++;
        } catch {
            // Continue even if individual pause fails
        }
    }
    
    // Return successfully paused indices
    pausedIndices = new uint256[](pausedCount);
    for (uint256 i = 0; i < pausedCount; i++) {
        pausedIndices[i] = tempIndices[i];
    }
    
    emit AllOrderBooksPaused(msg.sender, pausedIndices);
    return pausedIndices;
}

System-Wide Unpause

function unpauseAllOrderBooks() external onlyPauserRole returns (uint256[] memory unpausedIndices) {
    // Similar implementation for unpausing all OrderBooks
}

Batch Pause/Unpause Operations

For more granular control and gas efficiency when dealing with many OrderBooks:

function pauseOrderBooksBatch(uint256 startIndex, uint256 count) 
    external 
    onlyPauserRole 
    returns (address[] memory pausedAddresses)

function unpauseOrderBooksBatch(uint256 startIndex, uint256 count) 
    external 
    onlyPauserRole 
    returns (address[] memory unpausedAddresses)

Batch Operation Benefits:

  • Avoids gas limits when pausing/unpausing many OrderBooks

  • Provides pagination for large-scale operations

  • Returns specific addresses that were successfully affected

  • Enables targeted emergency responses

  • Continues operation even if individual OrderBooks fail to pause/unpause

Usage Example:

// Pause OrderBooks 0-99
pauseOrderBooksBatch(0, 100);

// Pause OrderBooks 100-199
pauseOrderBooksBatch(100, 100);

// Returns array of successfully paused addresses

Pauser Management

Admins can manage who has emergency pause privileges:

function addPauser(address account) external {
    // Only ADMIN_ROLE can add pausers
    if (!this.hasOrderBookRole(ADMIN_ROLE, msg.sender)) {
        revert AccessControlUnauthorizedAccount(msg.sender, ADMIN_ROLE);
    }
    _grantRole(PAUSER_ROLE, account);
}

function removePauser(address account) external {
    // Only ADMIN_ROLE can remove pausers
    if (!this.hasOrderBookRole(ADMIN_ROLE, msg.sender)) {
        revert AccessControlUnauthorizedAccount(msg.sender, ADMIN_ROLE);
    }
    _revokeRole(PAUSER_ROLE, account);
}

Emergency Use Cases:

  • Critical security vulnerabilities discovered

  • Market manipulation attempts

  • Extreme market conditions

  • Regulatory compliance requirements

Configuration Functions

Fee Configuration

function setFees(int256 _makerFee, uint256 _takerFee) external override onlyAdminRole {
    // Validate taker fee
    if (_takerFee > MAX_FEE) revert Manager__FeeTooHigh();
    
    // Validate maker fee (different limits for fees vs rebates)
    if (_makerFee < 0) {
        // For rebates: limit to MAX_REBATE (0.05%)
        if (uint256(-_makerFee) > MAX_REBATE) revert Manager__FeeTooHigh();
    } else {
        // For positive fees: limit to MAX_FEE (0.5%)
        if (uint256(_makerFee) > MAX_FEE) revert Manager__FeeTooHigh();
    }
    
    if (makerFee != _makerFee || takerFee != _takerFee) {
        int256 oldMakerFee = makerFee;
        uint256 oldTakerFee = takerFee;
        makerFee = _makerFee;
        takerFee = _takerFee;
        emit FeesUpdated(oldMakerFee, _makerFee, oldTakerFee, _takerFee);
    }
}

Registration Control

function setAllowAnyoneRegisterOrderBook(bool _allowed) external override onlyAdminRole {
    if (_allowed != allowAnyoneRegisterOrderBook) {
        allowAnyoneRegisterOrderBook = _allowed;
        emit AllowAnyoneRegisterOrderBookSet(_allowed);
    }
}

Events

Registration Events

event OrderBookRegistered(
    bytes32 indexed pairHash,
    address indexed baseToken,
    address indexed quoteToken,
    address orderBookAddress
);

event ExpectedBytecodeHashSet(bytes32 oldHash, bytes32 newHash);
event ExpectedNormalizedBytecodeHashSet(bytes32 oldHash, bytes32 newHash);
event AllowAnyoneRegisterOrderBookSet(bool allowed);

Fee Events

event FeesUpdated(
    int256 oldMakerFee,
    int256 newMakerFee,
    uint256 oldTakerFee,
    uint256 newTakerFee
);

event FeeCollectorUpdated(
    address indexed oldCollector,
    address indexed newCollector
);

Emergency Events

event AllOrderBooksPaused(
    address indexed pauser,
    uint256[] pausedIndices
);

event AllOrderBooksUnpaused(
    address indexed unpauser,
    uint256[] unpausedIndices
);

event OrderBooksPausedBatch(
    address indexed pauser,
    uint256 startIndex,
    uint256 endIndex,
    address[] pausedAddresses
);

event OrderBooksUnpausedBatch(
    address indexed unpauser,
    uint256 startIndex,
    uint256 endIndex,
    address[] unpausedAddresses
);

Security Features

Input Validation

  • Zero address checks for all critical parameters

  • Fee rate bounds checking (MAX_FEE = 0.5%, MAX_REBATE = 0.05%)

  • Bytecode hash validation

  • Token pair uniqueness enforcement

Access Control

  • Role-based permissions with inheritance

  • Centralized role checking for all OrderBooks

  • Emergency response capabilities

Emergency Response

  • System-wide pause/unpause functionality

  • Graceful error handling (continues if individual OrderBook fails)

  • Event emission for transparency

Standard Token Registry and Token Denylist

Standard ERC-20 Registry

function setStandardToken(address token, bool isStandard) external onlyAdminRole;
function isStandardToken(address token) external view returns (bool);
function setEnforceStandardBaseOnRegister(bool enforce) external onlyAdminRole;
  • When enforcement is enabled, registerOrderBook requires the BASE token to be marked standard.

  • “Standard” means exact-transfer, non-rebasing ERC-20 for gas-fast paths.

Emergency Token Denylist

function setTokenDenied(address token, bool denied) external onlyAdminRole;
function isTokenDenied(address token) external view returns (bool);

Denied tokens cannot be used in new OrderBooks (applies to both base and quote), regardless of whitelist status.

Self-Trade Prevention (STP)

STP Modes

The Manager contract supports three STP modes for preventing self-trading:

enum STPMode {
    NONE,          // No self-trade prevention
    EXPIRE_MAKER,  // Cancel maker order when self-trade detected (default)
    SKIP           // Skip maker order without matching
}

STP Configuration

// Set user's STP mode
function setUserSTPMode(STPMode mode) external override {
    userSTPMode[msg.sender] = mode;
    _isStpModeExplicitlySet[msg.sender] = true;
}

// Get user's STP mode (defaults to EXPIRE_MAKER)
function getUserSTPMode(address user) external view override returns (STPMode) {
    if (!_isStpModeExplicitlySet[user]) {
        return STPMode.EXPIRE_MAKER;
    }
    return userSTPMode[user];
}

STP Behavior

NONE: Self-trades are allowed and executed normally

EXPIRE_MAKER: When self-trade is detected:

  • Maker order is automatically cancelled

  • Collateral is refunded to the maker

  • Taker order continues to match with other orders

SKIP: When self-trade is detected:

  • Maker order is skipped (remains active)

  • Taker order continues to match with other orders

Integration Points

OrderBook Integration

// OrderBooks call this for role checking
function hasOrderBookRole(bytes32 role, address account) external view returns (bool);

// OrderBooks call this for fee calculation
function getMakerFeeRate(address orderbook, address user) external view returns (int256);
function getTakerFeeRate(address orderbook, address user) external view returns (uint256);

// OrderBooks call this for STP mode checking
function getUserSTPMode(address user) external view returns (STPMode);

Frontend Integration

  • Event monitoring for system changes

  • Role-based UI elements

  • Real-time fee calculation

  • Emergency status indicators

The Manager contract provides the foundational infrastructure for secure, scalable, and maintainable decentralized exchange operations while maintaining flexibility for future enhancements and community governance.

Last updated