Loading
Finalization
Launching
Soul Docs
Contracts
Controller

Controller

Last updated: 22 / 04 / 2025

The Controller contract is the central authority for evaluating risk, managing market configurations, and coordinating lending-related operations in the Soul Protocol. It plays a pivotal role in enabling cross-chain borrowing, collateral management, and liquidations, while ensuring consistency, synchronization, and safety across all chains.

Core Responsibilities

The Controller contract:

  1. Evaluates Risk Profiles using collateral and borrow balances from all connected chains.
  2. Approves and Executes borrow, remove collateral, and liquidation operations.
  3. Manages Cross-Chain User State, including balances, indexes, and session nonces.
  4. Synchronizes Cross-Chain Operations to maintain consistent execution order.
  5. Stores and Maintains Market Metadata like supported assets, configurations, and indexes.
  6. Interacts with the Router to receive cross-chain updates and track external chains.

Key Functions

borrow, removeCollateral, and liquidate

All user-facing lending operations begin at the Controller:

  • Evaluate user’s cross-chain position and market risk
  • Verify protocol rules and asset configurations
  • Route the message to the correct market or chain

Users must trigger these functions at the Controller that manages their position. If using a remote chain, they may use the Invoker contract instead.

connectToChain

Links a user’s positions from one chain to another, consolidating balances into a unified cross-chain account.

function connectToChain(uint96 chainId) external;

This operation triggers a cross-chain message to transfer the user's current balances and indexes into the selected Controller’s storage.

disconnectChain

Performs the reverse operation of connectToChain, detaching a chain from a user's cross-chain cluster.

function disconnectChain(uint96 chainId) external;

This removes chainId network from the Controller's cluster.

updateMarketsIndexesAtRemoteController

Updates market indexes (exchange rates and borrow indexes) at a remote Controller.

function updateMarketsIndexesAtRemoteController(
    uint96 remoteChainId,
    ISToken[] calldata sTokens,
    bool[] calldata accrueInterests,
    RouterDataStructs.AdapterParams calldata adapterParams
) external payable

Anyone can trigger this function permissionlessly. It ensures risk evaluations are based on current market data, even across chains.

Risk Assessment and Market Duplication

When a user connects to a Controller, that Controller duplicates the user’s balances and relevant indexes from other chains. This includes:

  • collateral and borrowNormalized balances

  • Market indexes (exchange rate and borrow index)

This data is stored locally within the Controller so that future operations can evaluate the user’s risk without needing a cross-chain read.

💡 This duplication is passive and only updated when operations are triggered.

Synchronization Logic

The Controller ensures sequential, atomic processing of dependent operations. For example:

  1. Alice borrows on Polygon via the Ethereum Controller.
  2. Before the Polygon transaction finalizes, Alice removes collateral on Avalanche.

The Avalanche request is evaluated using the updated balances from step 1, even if the final state change hasn’t propagated yet.

This prevents inconsistencies and ensures safety in fast-moving environments.

Market and Chain Metadata

Each Controller keeps track of:

  • Supported chains and their corresponding Controllers

  • All listed markets (by chainId + SToken)

  • Collateral factors, liquidation thresholds, and market caps

  • Asset prices, retrieved from PriceOracle smart contract

This local metadata allows Controllers to independently compute accurate risk profiles and perform validations.

Market Tracking

Each user’s portfolio is tracked using a list of markets (with chainId and SToken), stored in _userMarkets mapping.

struct Market {
  uint96 chainId;
  ISToken sToken;
}
 
mapping(address => Market[]) internal _userMarkets;

This is managed internally using:

function _enterMarket(uint96 chainId, ISToken sToken, address user) internal;
function _exitMarket(uint96 chainId, ISToken sToken, address user) internal;

These lists are used to compute the risk profile during borrow, remove collateral and liquidate operations.

Cross-Chain State and Session IDs

Each Controller tracks:

  • Which remote chains are connected to it
  • Which remote Controller it is connected to
  • The current session ID (nonce) of each connection, used to synchronize all cross-chain operations.

These session IDs ensure messages are processed in the correct order and under the correct state assumptions.

Summary

The Controller contract is the brain of the Soul Protocol’s cross-chain lending architecture. It enables:

  • Global risk computation across chains

  • Cross-chain borrowing and collateral management

  • Efficient, low-latency validations through duplicated market and user data

  • Seamless session management for multi-chain users

All core lending flows—whether initiated locally or remotely—route through a Controller to ensure security, consistency, and proper market evaluation.