Controller
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:
- Evaluates Risk Profiles using collateral and borrow balances from all connected chains.
- Approves and Executes borrow, remove collateral, and liquidation operations.
- Manages Cross-Chain User State, including balances, indexes, and session nonces.
- Synchronizes Cross-Chain Operations to maintain consistent execution order.
- Stores and Maintains Market Metadata like supported assets, configurations, and indexes.
- 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:
- Alice borrows on Polygon via the Ethereum Controller.
- 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.