Flow Limit in Interchain Token Service

Overview

A FlowLimit ensures the stability and security of interchain operations by setting constraints on the volume of tokens that can be transferred within a specified time frame. This prevents system abuse and overload, which is crucial in Interchain transfers, and can be used to prevent transfers of a token on a chain that is undergoing an emergency situation. Flow limits are managed by the FlowLimit contract.

The FlowLimit contract is used by the token manager contract. These token managers, use the FlowLimit functionalities to set cross-chain transfer volume going in and out of a given blockchain.

This guide provides a comprehensive understanding of how the FlowLimit contract operates within the context of Interchain token transfers.

Core Aspects

The following are core aspects of the FlowLimit contract.

Flow Limit Management

  • Uses a flow limit, or a ceiling on the net token flow (difference between flow in and out) within a defined EPOCH_TIME (currently set to 6 hours) to manage the number of tokens entering and leaving a blockchain
  • Ensures that the amount of tokens transferred in or out does not exceed the predefined limits within each epoch
  • Role-based functions in the contract, such as setFlowLimit, allow authorized users to manage and adjust the flow limits, adapting to varying network conditions and requirements
  • Uses custom errors such as FlowLimitExceeded to handle cases where a transfer exceeds the permitted flow limit

Dynamic Slot Allocation

  • Uses efficient low-level assembly for peak performance
  • Dynamically calculates storage slots for flow amounts based on the current epoch, optimizing storage use

Public View Functions

The FlowLimit contract contains the following public functions.

flowLimit()

Returns the current flow limit value.

/**
* @notice Returns the current flow limit.
* @return flowLimit_ The current flow limit value.
*/
function flowLimit() public view returns (uint256 flowLimit_) {
assembly {
flowLimit_ := sload(FLOW_LIMIT_SLOT)
}
}

flowOutAmount()

Returns the current total amount of tokens that have flowed out in the current epoch.

/**
* @notice Returns the current flow out amount.
* @return flowOutAmount_ The current flow out amount.
*/
function flowOutAmount() external view returns (uint256 flowOutAmount_) {
uint256 epoch = block.timestamp / EPOCH_TIME;
uint256 slot = _getFlowOutSlot(epoch);
assembly {
flowOutAmount_ := sload(slot)
}
}

flowInAmount()

Returns the current total amount of tokens that have flowed in the current epoch.

/**
* @notice Returns the current flow in amount.
* @return flowInAmount_ The current flow in amount.
*/
function flowInAmount() external view returns (uint256 flowInAmount_) {
uint256 epoch = block.timestamp / EPOCH_TIME;
uint256 slot = _getFlowInSlot(epoch);
assembly {
flowInAmount_ := sload(slot)
}
}

Internal Management Functions

The FlowLimit contract contains the following internal functions.

_setFlowLimit(uint256, bytes32)

Sets the flow limit for a specific token.

/**
* @notice Internal function to set the flow limit.
* @param flowLimit_ The value to set the flow limit to.
* @param tokenId The id of the token to set the flow limit for.
*/
function _setFlowLimit(uint256 flowLimit_, bytes32 tokenId) internal {
assembly {
sstore(FLOW_LIMIT_SLOT, flowLimit_)
}
emit FlowLimitSet(tokenId, msg.sender, flowLimit_);
}

_addFlowOut(uint256)

Adds to the flow out amount, ensuring it does not exceed the flow limit.

/**
* @notice Adds a flow out amount.
* @param flowOutAmount_ The flow out amount to add.
*/
function _addFlowOut(uint256 flowOutAmount_) internal {
uint256 flowLimit_ = flowLimit();
if (flowLimit_ == 0) return;
uint256 epoch = block.timestamp / EPOCH_TIME;
uint256 slotToAdd = _getFlowOutSlot(epoch);
uint256 slotToCompare = _getFlowInSlot(epoch);
_addFlow(flowLimit_, slotToAdd, slotToCompare, flowOutAmount_);
}

_addFlowIn(uint256)

Adds to the flow in amount, also ensuring adherence to the flow limit.

/**
* @notice Adds a flow in amount.
* @param flowInAmount_ The flow in amount to add.
*/
function _addFlowIn(uint256 flowInAmount_) internal {
uint256 flowLimit_ = flowLimit();
if (flowLimit_ == 0) return;
uint256 epoch = block.timestamp / EPOCH_TIME;
uint256 slotToAdd = _getFlowInSlot(epoch);
uint256 slotToCompare = _getFlowOutSlot(epoch);
_addFlow(flowLimit_, slotToAdd, slotToCompare, flowInAmount_);
}

Setting and Overriding Flow Limits in ITS

There are several options for setting and overriding flow limits in the Interchain Token Service.

Fully managed flow limits

If you want Axelar to manage the flow limits for the token and not deal with it yourself:

  • Deploy the token with the minter address set to address(0)

Partially managed flow limits

If you want to manage the flow limits together with Axelar:

  • Deploy the token with the minter address set to your Deployer account or a multi-sig wallet If you want to manage the flow limits alone:
  • Deploy the token with the minter address set to your Deployer account
  • Call tokenManager.removeFlowLimiter(itsAddress) to remove flow limit management by Axelar

Unmanaged flow limits

If you don’t want anyone to manage flow limits:

  • Deploy the token with the minter address set to your Deployer account
  • Call tokenManager.removeFlowLimiter(itsAddress) to remove flow limit management by Axelar
  • Call tokenManager.removeFlowLimiter(minter) to remove flow limit management by the minter
  • Call tokenManager.transferOperatorship(address(0)) to remove operatorship

The following is an example of how to set a flow limit using the setFlowLimits method. You might want to increase the limit to the max before removing access granted to yourself.

/**
* @notice Used to set a flow limit for a token manager that has the service as its operator.
* @param tokenIds An array of the tokenIds of the tokenManagers to set the flow limits of.
* @param flowLimits The flowLimits to set.
*/
function setFlowLimits(bytes32[] calldata tokenIds, uint256[] calldata flowLimits) external onlyRole(uint8(Roles.OPERATOR)) {
uint256 length = tokenIds.length;
if (length != flowLimits.length) revert LengthMismatch();
for (uint256 i; i < length; ++i) {
ITokenManager tokenManager = ITokenManager(validTokenManagerAddress(tokenIds[i]));
// slither-disable-next-line calls-loop
tokenManager.setFlowLimit(flowLimits[i]);
}
}

Edit on GitHub