Skip to main content

Overview

The CTF Exchange implements a dual-role access control system with Admin and Operator roles. This separation of concerns ensures that critical configuration changes require admin privileges while routine operations can be delegated to operators.

Role System

The exchange uses a mapping-based role system defined in Auth.sol:8-83:
mapping(address => uint256) public admins;
mapping(address => uint256) public operators;
Roles are stored as uint256 values where 1 indicates the role is active and 0 indicates it’s inactive.

Admin Role

Admins have full control over the protocol and can:
  • Pause and unpause trading
  • Add and remove other admins and operators
  • Configure proxy and safe factories
  • Register new tokens for trading
  • Renounce their own admin role

Operator Role

Operators can:
  • Execute trades via fillOrder(), fillOrders(), and matchOrders()
  • Renounce their own operator role
Operators cannot pause trading or modify protocol configuration. Only admins have these privileges.

Admin Functions

Pausability Controls

Admins can halt and resume trading in emergency situations.

pauseTrading

function pauseTrading() external onlyAdmin
Location: CTFExchange.sol:45 Description: Pauses all trading on the exchange. When paused, all trading functions will revert with a Paused() error. Effects:
  • Sets paused state to true
  • Emits TradingPaused(msg.sender) event
  • All functions with notPaused modifier will revert
Example:
// Pause trading in case of emergency
await exchange.pauseTrading();
When trading is paused, users cannot fill orders or match orders. Use this function only in emergency situations.

unpauseTrading

function unpauseTrading() external onlyAdmin
Location: CTFExchange.sol:50 Description: Resumes trading on the exchange after it has been paused. Effects:
  • Sets paused state to false
  • Emits TradingUnpaused(msg.sender) event
  • Trading functions become callable again
Example:
// Resume trading after resolving the issue
await exchange.unpauseTrading();

Factory Configuration

Admins can update the factory contracts used for signature verification.

setProxyFactory

function setProxyFactory(address _newProxyFactory) external onlyAdmin
Location: CTFExchange.sol:97 Description: Updates the Polymarket Proxy Wallet factory address used for verifying proxy wallet signatures. Parameters:
  • _newProxyFactory: Address of the new proxy factory contract
Effects:
  • Updates proxyFactory state variable
  • Emits ProxyFactoryUpdated(oldProxyFactory, newProxyFactory) event
Example:
const newProxyFactory = "0x...";
await exchange.setProxyFactory(newProxyFactory);
Changing the proxy factory affects how proxy wallet signatures are verified. Ensure the new factory is compatible before making this change.

setSafeFactory

function setSafeFactory(address _newSafeFactory) external onlyAdmin
Location: CTFExchange.sol:103 Description: Updates the Gnosis Safe factory address used for verifying Safe wallet signatures. Parameters:
  • _newSafeFactory: Address of the new Safe factory contract
Effects:
  • Updates safeFactory state variable
  • Emits SafeFactoryUpdated(oldSafeFactory, newSafeFactory) event
Example:
const newSafeFactory = "0x...";
await exchange.setSafeFactory(newSafeFactory);

Token Registration

registerToken

function registerToken(
    uint256 token,
    uint256 complement,
    bytes32 conditionId
) external onlyAdmin
Location: CTFExchange.sol:111 Description: Registers a token pair (outcome tokens) and their associated condition ID for trading on the exchange. Parameters:
  • token: The token ID to register
  • complement: The complement token ID (the opposite outcome)
  • conditionId: The CTF condition ID from the Conditional Tokens Framework
Validation (Registry.sol:41-43):
  • token and complement must be different
  • Neither token nor complement can be zero
  • Neither token can already be registered
Effects:
  • Registers both tokens in the registry mapping
  • Associates both tokens with the condition ID
  • Emits TokenRegistered(token, complement, conditionId) event for both tokens
Example:
const token0 = "0x123...";
const token1 = "0x456...";
const conditionId = "0xabc...";

await exchange.registerToken(token0, token1, conditionId);
Once a token is registered, it cannot be unregistered or re-registered. Ensure the token IDs and condition ID are correct before calling this function.

Role Management Functions

Adding Roles

addAdmin

function addAdmin(address admin_) external onlyAdmin
Location: Auth.sol:41 Description: Grants admin privileges to a new address. Effects:
  • Sets admins[admin_] = 1
  • Emits NewAdmin(admin_, msg.sender) event

addOperator

function addOperator(address operator_) external onlyAdmin
Location: Auth.sol:49 Description: Grants operator privileges to a new address. Effects:
  • Sets operators[operator_] = 1
  • Emits NewOperator(operator_, msg.sender) event

Removing Roles

removeAdmin

function removeAdmin(address admin) external onlyAdmin
Location: Auth.sol:57 Description: Revokes admin privileges from an address. Effects:
  • Sets admins[admin] = 0
  • Emits RemovedAdmin(admin, msg.sender) event
Admins can remove other admins. Ensure at least one admin remains to maintain protocol control.

removeOperator

function removeOperator(address operator) external onlyAdmin
Location: Auth.sol:65 Description: Revokes operator privileges from an address. Effects:
  • Sets operators[operator] = 0
  • Emits RemovedOperator(operator, msg.sender) event

Renouncing Roles

renounceAdminRole

function renounceAdminRole() external onlyAdmin
Location: Auth.sol:72 Description: Allows an admin to voluntarily give up their admin privileges. Effects:
  • Sets admins[msg.sender] = 0
  • Emits RemovedAdmin(msg.sender, msg.sender) event
This action is irreversible unless another admin grants the role back. Ensure other admins exist before renouncing.

renounceOperatorRole

function renounceOperatorRole() external onlyOperator
Location: Auth.sol:79 Description: Allows an operator to voluntarily give up their operator privileges. Effects:
  • Sets operators[msg.sender] = 0
  • Emits RemovedOperator(msg.sender, msg.sender) event

View Functions

Role Checking

function isAdmin(address usr) external view returns (bool)
function isOperator(address usr) external view returns (bool)
Location: Auth.sol:30-36 Description: Check if an address has admin or operator privileges. Example:
const isAdmin = await exchange.isAdmin(address);
const isOperator = await exchange.isOperator(address);

Factory Information

function getProxyFactory() public view returns (address)
function getSafeFactory() public view returns (address)
function getPolyProxyFactoryImplementation() public view returns (address)
function getSafeFactoryImplementation() public view returns (address)
Location: PolyFactoryHelper.sol:31-48 Description: Retrieve the current factory addresses and their implementations.

Pause State

function paused() public view returns (bool)
Location: Pausable.sol:7 Description: Returns true if trading is currently paused, false otherwise.

Events

Access Control Events

event NewAdmin(address indexed newAdminAddress, address indexed admin)
event NewOperator(address indexed newOperatorAddress, address indexed admin)
event RemovedAdmin(address indexed removedAdmin, address indexed admin)
event RemovedOperator(address indexed removedOperator, address indexed admin)

Pausability Events

event TradingPaused(address indexed pauser)
event TradingUnpaused(address indexed pauser)

Configuration Events

event ProxyFactoryUpdated(address indexed oldProxyFactory, address indexed newProxyFactory)
event SafeFactoryUpdated(address indexed oldSafeFactory, address indexed newSafeFactory)
event TokenRegistered(uint256 indexed token0, uint256 indexed token1, bytes32 indexed conditionId)

Errors

error NotAdmin()        // Caller is not an admin
error NotOperator()     // Caller is not an operator
error Paused()          // Trading is paused
error InvalidTokenId()  // Token ID is invalid or not registered
error InvalidComplement() // Complement doesn't match registered value
error AlreadyRegistered() // Token is already registered

Security Considerations

Admin keys have powerful privileges. Follow these best practices:
  • Use hardware wallets or multi-signature wallets for admin accounts
  • Maintain multiple admin addresses for redundancy
  • Regularly rotate admin keys
  • Monitor admin transactions on-chain
Operators can execute trades but cannot modify protocol state:
  • Operators should use secure key management
  • Monitor operator activity for unauthorized trades
  • Implement rate limiting at the application layer
  • Remove compromised operators immediately
In case of a security incident:
  1. Call pauseTrading() immediately
  2. Investigate the issue
  3. Remove compromised operators/admins if necessary
  4. Deploy fixes or mitigation measures
  5. Call unpauseTrading() once resolved
When updating factories:
  • Verify the new factory contract thoroughly
  • Test signature verification with the new factory
  • Coordinate with users who may be affected
  • Monitor for any signature verification failures

Security Audit

View the ChainSecurity audit report

Best Practices

Security guidelines for integrators

Build docs developers (and LLMs) love