Skip to main content
A pool in this context is the medium through which users can exchange 2 different assets. In this case, these 2 assets are a pair of tokens where one is the collateral ERC20 token (aka y-axis token) and the other one is the x-axis ERC20 token. The following are the technical aspects of a pool in this product: See Pool.sol.

Overview

Assumptions

  • The pool is the original source for all x-tokens: it is assumed that the pool is the only source for originally getting x-tokens. Other markets such as constant-product AMMs can be used once the tokens are in circulation, but all tokens are originally purchased from the TBC.
  • The pool has been deployed through the factory: The factory automatically grants ownership of the pool to the deployer address. It also emits an event for the monitoring services to know when a pool has been deployed through it.

Ownership

The owner of the pool is granted initially to the deployer address. The pool uses the Ownable contract from OpenZeppelin. Therefore, transfer of ownership is always allowed to the current owner of the pool.

ALTBC Price Curve

The price curve of an ALTBC pool obeys what we call an adjustable linear token bonding curve. For an individual transaction, the price is calculated according to a linear formula. After each transaction, the line that defines the price is updated. The slope of this line changes based on the amount of x-tokens out in circulation. The effective price curve (how the price evolves over time, across many swaps) will be a concave curve that tends to stabilize the more tokens are sold.
animation of spot price
animation of y liquidity
price curve of adjustable linear TBC

Access Control

Owner

The owner of the pool contract is the only one who can execute the following privileged functions:
  • enableSwaps(bool _enable)
  • setLPFee(uint16 _fee)
  • addXSupply(uint256 _amount)
  • collectLPFees()
  • withdrawRevenue()

Protocol Fee Collector

The protocol fee collector account of the pool contract is the only one who can execute the following privileged functions:
  • setProtocolFee(uint16 _fee)
  • proposeProtocolFeeCollector()
There is a transitionary role which is the proposed protocol fee collector. This role is granted by the The protocol fee collector when calling proposeProtocolFeeCollector, and the only function that this transitionary role can call is:
  • confirmProtocolFeeCollector()
In which case, the account loses this role to now become the The protocol fee collector.

Security

The pool can be paused at any point by the owner account which means that all trading is disabled.

Revenue

As a consequence of the mathematical design, the pool will generate a small revenue per trade, independently of any fees set on the pool. The size of this revenue is correlated with the size of xMin, and grows over time with each trade. This revenue can be collected by the pool owner. Note that there may be remaining dust in the pool which will only be extracted when the pool is closed.

Artificial Price Manipulation Control

The mathematical design of this pool is such that it would be possible to artificially increase the price of an asset at no cost if we allowed x to be zero. At low values of x, it is also possible to increase the price of the asset at a potentially low cost by wash trading. To mitigate this possible price manipulation, the pool initiates with a synthetic trade. The amount of this synthetic trade is defined as xMin + w and is stored in the contract as xMax, where w is the initial xTokens added to the pool expressed in a packedFloat format.

Virtual Liquidity

This synthetic trade will cause the pool to have a virtual liquidity which is stored in the contract as wActive which is the difference between w and wInactive. This synthetic trade will also generate revenue for the pool which will be accumulated as dust in the pool.

Usage

Deployment

To deploy a pool please follow the guidelines in the factory guideline.

Initialization

After deployment, it is necessary to do the following step:
  1. Add liquidity to the pool (see Managing Liquidity). Notice that initial liquidity is only necessary for the x-token since the y-token will be provided through swaps by users. The amount of liquidity provided will be equal to the totalSupply of the token.
The pool will start operating at xMin. See explanation below note: Each pool will start with a synthetic trade in order to prevent an artificial price increase. This synthetic trade at the beginning of a fresh pool will help negate a malicious user from increasing the price of xToken in the pool. This also allows for a pool owner to extract collateral dust that accrues throughout the lifetime of the pool. see the revenue equation here

Managing Fees

LP Fees

The liquidity-provider fees are a portion of the swap that would go towards the liquidity provider as a compensation for the service provided. In this pool, there can only be one liquidity provider who is the owner of the pool, and therefore, the liquidity-provider fees can be managed only by the owner of the contract. Here are the main features of the fees of the pool:
  • Fees can be updated at any time.
  • Fees can be 0.
  • Fees are expressed in basis points.
  • Fees can be 50% - protocol fees.
  • Fees are collected in the y-token.
  • Fees are kept inside the pool.
  • Fees can be collected by the owner of the NFT token ID that defines the position of the liquidity provider.
Fees can be managed through the following functions:
/**
 * @dev function to update the fees per trading
 * @param _fee percentage of the transaction that will get collected as fees (in percentage basis points:
 * 10000 -> 100.00%; 500 -> 5.00%; 1 -> 0.01%)
 */
function setLPFee(uint16 _fee) external;

/**
 * @dev collects the fees from the Pool
 */
function collectLPFees() external;

/**
 * @dev tells how much collected fees are available in the Pool
 */
function lpFee() external;

Protocol Fees

Protocol fees are a portion of each swap that will go towards the protocol as a compensation for its service provided. The management of the protocol fees is under complete control of the protocol and not of the owner of the pool. The following are the properties of the pool’s protocol fees:
  • Protocol fees can be updated at any time.
  • Protocol fees can be 0.
  • Protocol fees are expressed in basis points.
  • Protocol fees can be as high as 0.20%.
  • Protocol fees are collected in the y-token.
  • Protocol fees are kept inside the pool.
  • Protocol fees need a manual withdrawal by the protocol fee collector who then will receive the totality of the fees accumulated.

Protocol fee value

The protocol fee value can be set at any time only by the protocol fee collector account. The following functions are available to manage this value:
/**
 * @dev fee percentage for swaps for the protocol
 * @return the percentage for swaps in basis points that will go towards the protocol
 */
function protocolFee() external returns (uint16);

/**
 * @dev This is the function to update the protocol fees per trading.
 * @param _protocolFee percentage of the transaction that will get collected as fees (in percentage basis points:
 * 10000 -> 100.00%; 500 -> 5.00%; 1 -> 0.01%)
 */
function setProtocolFee(uint16 _protocolFee) external;

Protocol fee collector

The protocol fee collector account of the pool can be updated at any time. Only the protocol fee collector account can update this value. The following functions are available to manage this address:
/**
 * @dev protocol-fee collector address
 * @return the current protocolFeeCollector address
 */
function protocolFeeCollector() external returns (address);

/**
 * @dev proposed protocol-fee collector address
 * @return the current proposedProtocolFeeCollector address
 */
function proposedProtocolFeeCollector() external returns (address);

/**
 * @dev function to propose a new protocol fee collector
 * @param _protocolFeeCollector the new fee collector
 * @notice that only the current fee collector address can call this function
 */
function proposeProtocolFeeCollector(address _protocolFeeCollector) external;

/**
 * @dev function to confirm a new protocol fee collector
 * @notice that only the already proposed fee collector can call this function
 */
function confirmProtocolFeeCollector() external;
Notice that the process of assigning a new protocol fee collector is a 2-step process in order to prevent human errors from setting the wrong address:
  1. The current protocol fee collector account proposes a new protocol fee collector address through the function proposeProtocolFeeCollector.
  2. The proposed protocol fee collector account then needs to call the confirmProtocolFeeCollector function to accept/confirm this role.

Revenue

Because of the mechanism to deter artificial price increase in a fresh pool, a synthetic trade is done when deploying a new pool. This new pool will start with its x at xMin. Therefore, the outstanding liquidity will always be x - xMin. This dust is accessible through the withdrawRevenue function, callable by the owner of the NFT token ID that defines the position of the liquidity provider.
/**
 * @dev This function allows the owner of the pool to pull accrued revenue from the Pool.
 */
function withdrawRevenue(uint256 tokenId, uint256 Q) external returns (uint256 revenue);
note: The dust remaining in the pool as revenue will also be extracted when closing the pool

Managing Swaps

Swaps can be enabled and disabled. This functionality uses the standard Pausable contract found in projects like OpenZeppelin. To enable or disable swaps, use the following function:
/**
 * @dev function to activate/deactivate trading
 * @param _enable pass True to enable or False to disable.
 */
function enableSwaps(bool _enable) external;
The functions affected by a disabled-swap state are limited to:
  • swap()

Swaps

The main feature of the pool is that it allows swaps. Swaps are permissionless meaning that anybody can use this feature. A vital factor of a swap is price, for which there are three different functions that can be used. These functions will be explained in the following sections.

Spot price

In the context of a TBC AMM, spot price refers to the current price for buying 1 full token assuming a flat price for the whole token (no price difference between the first wei and the last wei of the purchased token).
/**
 * @dev This is the function to retrieve the current spot price of the x token.
 * @return sPrice the price in YToken Decimals
 */
function spotPrice() external view returns (uint256 sPrice);
Take into account that this is more of a theoretical price since in reality the price does change from the first wei to the last wei since this is a linear TBC, and therefore the price depends on the amount of x tokens sold by the pool (value of x). To know an exact price of a transaction, it is necessary to know exactly how much to swap. For this, use the functions of the coming section. 📝 WEI is being used to refer to the atomic unit of the ERC20 token. We find this terminology more intuitive since the decimals of ERC20 mimic the relationship between Ether and WEI but without a defined term for the atomic unit like we get for ETH

Swap simulations

The pool offers two ways of simulating a swap to know either how much is needed to get a certain amount of tokens out of the pool (the output is known and the input is requested), or to know how much is going to be obtained from a swap after a certain amount of tokens are provided to the pool (the input is known and the output is requested). This is useful since this lets the user know the actual cost of a swap, and it can also give the user a point of reference to tell the pool how to calculate the slippage of the swap (in reality, the frontend usually handles this last step). To simulate a swap, these two functions are available:
/**
 * @dev This is a simulation of the swap function. Useful to get marginal prices
 * @param _tokenIn the address of the token being sold
 * @param _amountIn the amount of the ERC20 _tokenIn to sell to the Pool
 * @return amountOut the amount of the token coming out of the Pool as result of the swap (main returned value)
 * @return lpFeeAmount the amount of the Y token that's being dedicated to fees for the LP
 * @return protocolFeeAmount the amount of the Y token that's being dedicated to fees for the protocol
 */
function simSwap(
    address _tokenIn,
    uint256 _amountIn
) public view returns (uint256 amountOut, uint256 lpFeeAmount, uint256 protocolFeeAmount);

/**
 * @dev This is a simulation of the swap function from the perspective of purchasing a specific amount. Useful to get marginal price.
 * @param _tokenout the address of the token being bought
 * @param _amountOut the amount of the ERC20 _tokenOut to buy from the Pool
 * @return amountIn the amount necessary of the token coming into the Pool for the desired amountOut of the swap (main returned value)
 * @return lpFeeAmount the amount of the Y token that's being dedicated to fees for the LP
 * @return protocolFeeAmount the amount of the Y token that's being dedicated to fees for the protocol
 * @notice lpFeeAmount and protocolFeeAmount are already factored in the amountIn. This is useful only to know how much of the amountIn
 * will go towards fees.
 */
function simSwapReversed(
    address _tokenout,
    uint256 _amountOut
) public view returns (uint256 amountIn, uint256 lpFeeAmount, uint256 protocolFeeAmount);
Notice that in both functions we have a lpFeeAmount and the protocolFeeAmount values which are returned alongside the amountIn/amountOut (main returned value). These values are only informational as they have already been factored in the main returned value. Therefore, there is no need to carry out any addition/subtraction to use the amountIn/amountOut in the swap function.

Slippage

Slippage is defined as an absolute deviation value between an expected outcome and the actual outcome expressed as a minimum token result from the swap. This slippage calculation is done off-chain. The slippage check is used to ensure the outcome of the swap is greater than or equal to the minimum expected. Traders can set their own slippage tolerance when initiating the swap (see next section).

Swaps

Use the following function to carry out a swap.
/**
 * @dev This is the main function of the pool to swap.
 * @param _tokenIn the address of the token being given to the pool in exchange for another token
 * @param _amountIn the amount of the ERC20 _tokenIn to exchange into the Pool
 * @param _minOut the amount of the other token in the pair minimum to be received for the
 * _amountIn of _tokenIn.
 * @return amountOut the actual amount of the token coming out of the Pool as result of the swap
 * @return lpFeeAmount the amount of the Y token that's being dedicated to fees for the LP
 * @return protocolFeeAmount the amount of the Y token that's being dedicated to fees for the protocol
 */
function swap(
    address _tokenIn,
    uint256 _amountIn,
    uint256 _minOut
) external  returns (uint256 amountOut, uint256 lpFeeAmount, uint256 protocolFeeAmount);

/**
 * @dev This function checks to verify the amount out will be greater than or equal to the minimum expected amount out.
 * @param _amountOut the actual amount being provided out by the swap
 * @param _minOut the expected amount out to compare against
 */
function _checkSlippage(uint256 _amountOut, uint256 _minOut) internal pure {
    if (_amountOut < (_minOut - 1)) revert MaxSlippageReached();
}
An important aspect to note is that the AMM expects the user to provide the amount of the token they’re selling and the expected minimum amount out. In instances where the user’s goal is to retrieve a specific amount out they can first use the ‘simSwapReversed’ function (defined above) to determine how much they’ll need to sell to the AMM to receive the desired amount.

Public Variables

The pool has many public variables that expose aspects such as the curve characterization, the pair tokens, the state of the curve, etc.:
/**
 * @dev A function to get the address of the x token of the pool.
 * @return the address of the x token of the pool
 * @notice this value is immutable
 */
function xToken() external returns (address);

/**
 * @dev A function to get the address of the Y token of the pool.
 * @return the address of the Y token of the pool
 * @notice this value is immutable
 */
function yToken() external returns (address);

/**
 * @dev tells pool yToken difference in the amount of decimals compared
 * to the pool native decimals
 * @return yDecimalDiff
 * @notice this value is immutable
 */
function yDecimalDiff() external returns (uint256);

/**
 * @dev tells the minimum x that the pool is allowed to be at
 * @return xMin expressed in xToken decimals
 * @notice this value is immutable
 */
function xMin() external returns (uint256);

/**
 * @dev tells the current values of the tbc
 * includes the parameters of the tbc
 * struct ALTBCDef {
 * packedFloat b;
 * packedFloat c;
 * packedFloat C;
 * packedFloat xMin;
 * packedFloat xMax;
 * packedFloat V;
 * packedFloat Zn;
 * }
 * @return tbc
 */
function tbc() returns (ALTBCDef);

/**
 * @dev tells the current value of x.
 * @notice Outstanding liquidity can be calculated as x - xMin.
 * @return x expressed in xToken decimals
 */
function x() external returns (uint256);

/**
 * @dev tells the lifetime claimed revenue of the pool
 * @return r expressed in the yToken native decimals
 */
function r() external returns (uint256);

Events

Please visit next page for Events.