Ticks

Ticks are a fundamental concept in f(x) protocol that represent discrete price points for debt-to-collateral ratios. They serve as a risk categorization system that groups user positions with similar risk levels together. For more details, you can check _getTick function in contracts.

// From TickMath.sol:
int24 internal constant MIN_TICK = -32767;
int24 internal constant MAX_TICK = 32767;

// Tick calculation uses 1.0015 as the base
// ratioX96 = (1.0015^tick) * 2^96

1. Mathematical Foundation

The core formula for the tick system is:

  • Ratio = (1.0015^tick) Γ— 2^96

  • This ratio represents the debts/collaterals proportion

  • Each tick represents approximately 0.15% price change (1.0015 - 1 = 0.0015)

2. Primary functions of Ticks

All user positions with similar debt ratios are grouped into the same tick, enabling:

  • Efficient batch processing: Multiple similar-risk positions can be handled simultaneously

  • Precise risk management: Positions within the same tick share similar liquidation risks

3. topTick

The system processes ticks starting from the highest risk tick (topTick) in descending risk order.

The system maintains a topTick that points to the highest-risk tick with outstanding debt. Operations always start from this point and work downward through decreasing risk levels.

4 Trace ticks

There are many ways to trace ticks. Fundamentally, they are stored in the nodes of each pool contract. For more details, please view [PoolStorage.sol][https://github.com/AladdinDAO/fx-protocol-contracts/blob/main/contracts/core/pool/PoolStorage.sol)

As for a position, you can use getPosition in each pool to get the tick of the position.

  /// @notice Return the details of the given position.
  /// @param tokenId The id of position to query.
  /// @return rawColls The amount of collateral tokens supplied in this position.
  /// @return rawDebts The amount of debt tokens borrowed in this position.
  function getPosition(uint256 tokenId) external view returns (uint256 rawColls, uint256 rawDebts);

As for a tick, you can use tickData to fetch which node it belongs to:

  /// @dev Mapping from tick to tree node id.
  mapping(int256 => uint48) public tickData;

As for a node, you can fetch the node's info through tickTreeData and tickData.

  /// @dev Mapping from tree node id to tree node data.
  mapping(uint256 => TickTreeNode) public tickTreeData;
  • tickData: tick -> node

  • tickTreeData: node -> tick

However, the tick number returned by getPosition cannot be directly used in rebalance or liquidation operations unless its node's parent is 0. If the node's parent is not 0, you must trace the parent tick number upward until you reach a node whose parent is 0 (i.e., the root node).

  /// @dev Internal function to get the root of the given tree node.
  /// @param node The id of the given tree node.
  /// @return root The root node id.
  /// @return collRatio The actual collateral ratio of the given node, multiplied by 2^60.
  /// @return debtRatio The actual debt ratio of the given node, multiplied by 2^60.
  function _getRootNode(uint256 node) internal view returns (uint256 root, uint256 collRatio, uint256 debtRatio) {
    collRatio = E60;
    debtRatio = E60;
    while (true) {
      bytes32 metadata = tickTreeData[node].metadata;
      uint256 parent = metadata.decodeUint(PARENT_OFFSET, 48);
      collRatio = (collRatio * metadata.decodeUint(COLL_RATIO_OFFSET, 64)) >> 60;
      debtRatio = (debtRatio * metadata.decodeUint(DEBT_RATIO_OFFSET, 64)) >> 60;
      if (parent == 0) break;
      node = parent;
    }
    root = node;
  }

Additionally, you can also use TickBitmap contract to track active root ticks.

Last updated