AMM Basics

What is an Automated Market Maker (AMM)?

An Automated Market Maker (AMM) is a type of decentralized exchange (DEX) protocol that relies on a mathematical formula to price assets. Instead of using a traditional order book, AMMs use liquidity pools. JAMM DEX uses the Constant Product Formula as its core pricing mechanism.

The Constant Product Formula

The Basic Principle

JAMM DEX uses the constant product formula: x * y = k

Where:

  • x = the reserve of token A

  • y = the reserve of token B

  • k = a constant product

This formula ensures that the product of the reserves of the two tokens in a liquidity pool remains constant (disregarding the impact of trading fees and liquidity changes).

Code Implementation

In the JAMMPair.sol contract, this principle is enforced in the swap function:

// balance{0,1}Adjusted are the balances after the swap, accounting for the fee
uint balance0Adjusted = (balance0 * 10000 - (amount0In * fee * 4) / 5);
uint balance1Adjusted = (balance1 * 10000 - (amount1In * fee * 4) / 5);

// check that the product of the new balances is not less than the product of the old reserves
require(
    balance0Adjusted * balance1Adjusted >= 
    uint(_reserve0) * uint(_reserve1) * (10000 ** 2),
    "JAMM: K"
);

Price Discovery Mechanism

Price Calculation

In an AMM system, the price of a token is determined by the ratio of the reserves:

Price of Token A = Reserve of Token B / Reserve of Token A

Price Impact

When a user trades, the ratio of the reserves changes, which in turn affects the price. The larger the trade, the more significant the price impact.

The price calculation function in JAMMLibrary.sol:

function getAmountOut(
    uint amountIn,
    uint reserveIn,
    uint reserveOut,
    uint24 fee
) internal pure returns (uint amountOut) {
    require(amountIn > 0, "JAMMLibrary: INSUFFICIENT_INPUT_AMOUNT");
    require(reserveIn > 0 && reserveOut > 0, "JAMMLibrary: INSUFFICIENT_LIQUIDITY");
    
    // calculate output amount with fee
    uint amountInWithFee = amountIn * (10000 - fee);
    uint numerator = amountInWithFee * reserveOut;
    uint denominator = reserveIn * 10000 + amountInWithFee;
    amountOut = numerator / denominator;
}

The Concept of Slippage

What is Slippage?

Slippage is the difference between the expected price of a trade and the price at which the trade is executed. In an AMM system, slippage is mainly caused by:

  1. Price Impact: A large trade changes the reserve ratio.

  2. Market Volatility: The pool's price can change due to other trades during the transaction confirmation time.

Slippage Protection

JAMM DEX provides slippage protection by checking the minimum acceptable output specified by the user in the JAMMRouter contract:

// check that the received amount is not less than the minimum amount specified by the user
require(
    amounts[amounts.length - 1] >= amountOutMin,
    "JAMMRouter: INSUFFICIENT_OUTPUT_AMOUNT"
);

Arbitrage Mechanism

The Role of Arbitrage

Arbitrageurs profit from price differences between markets, which helps to:

  1. Keep JAMM DEX's prices consistent with external markets.

  2. Provide more accurate prices for traders.

  3. Increase trading volume, generating more fee income for liquidity providers.

Arbitrage Example

If the JU/USDC price on JAMM DEX is lower than on other exchanges:

  1. An arbitrageur buys JU on JAMM DEX.

  2. They sell JU on another exchange.

  3. This process drives up the price of JU on JAMM DEX until it aligns with the external market price.

Liquidity Provision

The Importance of Liquidity

Liquidity is the core of an AMM system:

  • High Liquidity: Results in smaller price impact and a better trading experience.

  • Low Liquidity: Leads to larger price impact and potentially high slippage.

The Role of Liquidity Providers

Liquidity Providers (LPs) supply liquidity by depositing an equivalent value of two tokens into a pool, receiving LP tokens in return. The mint function in JAMMPair.sol handles this logic:

function mint(address to) external lock returns (uint liquidity) {
    (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
    uint balance0 = IERC20(token0).balanceOf(address(this));
    uint balance1 = IERC20(token1).balanceOf(address(this));
    uint amount0 = balance0 - _reserve0;
    uint amount1 = balance1 - _reserve1;

    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply;
    
    // calculate liquidity to mint
    if (_totalSupply == 0) {
        liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
        _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
    } else {
        liquidity = Math.min(
            (amount0 * _totalSupply) / _reserve0,
            (amount1 * _totalSupply) / _reserve1
        );
    }
    
    require(liquidity > 0, "JAMM: INSUFFICIENT_LIQUIDITY_MINTED");
    _mint(to, liquidity);

    _update(balance0, balance1, _reserve0, _reserve1);
    if (feeOn) kLast = uint(reserve0) * uint(reserve1);
    emit Mint(msg.sender, amount0, amount1);
}

Impermanent Loss

Definition

Impermanent Loss (IL) is the difference in value between holding assets in a liquidity pool and simply holding them in a wallet (HODLing). The greater the price divergence, the larger the impermanent loss.

How it Works

When the price of tokens in the pool changes:

  1. The AMM automatically rebalances the reserves to maintain the x*y=k constant.

  2. An LP's share of the pool remains the same, but the quantities of the two tokens represented by that share change.

Mitigation Measures

JAMM DEX helps mitigate impermanent loss through:

  1. Trading Fee Income: LPs earn trading fees as compensation, which can offset or even exceed the impermanent loss.

  2. Multi-Tier Fees: Higher fees can be set for more volatile assets to provide greater fee income.

Price Oracle

Price Accumulation Mechanism

JAMM DEX implements a Time-Weighted Average Price (TWAP) oracle. The JAMMPair.sol contract accumulates prices during the first trade of each block:

function _update(
    uint balance0,
    uint balance1,
    uint112 _reserve0,
    uint112 _reserve1
) private {
    // ...
    uint32 timeElapsed = blockTimestamp - blockTimestampLast;
    if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
        // accumulate prices
        price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
        price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
    }
    // ...
}

Use Cases for the Oracle

  1. External Contract Integration: Other DeFi protocols can query historical average prices for lending, derivatives, etc.

  2. Arbitrage Detection: Helps identify price deviation opportunities.

  3. Risk Management: Assess the price volatility risk of assets.

Minimum Liquidity Lock

Permanent Lock Mechanism

To prevent a liquidity pool from being completely drained, which would make price calculations impossible, JAMM DEX permanently locks a minimum amount of LP tokens to the zero address during the first liquidity provision.

// From JAMMPair.sol
uint public constant MINIMUM_LIQUIDITY = 10**3;

// Inside the mint function
if (_totalSupply == 0) {
    liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
    _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first tokens
}

This ensures that:

  1. The pool is never completely empty.

  2. Price calculations are always valid.

  3. Certain types of malicious manipulation are prevented.

Summary

The AMM mechanism provides an elegant solution for decentralized trading:

  • No Order Book: Prices are determined automatically by a mathematical formula.

  • Continuous Liquidity: Trades can always be executed as long as there are reserves in the pool.

  • Decentralization: No need for centralized market makers.

  • Transparency: All logic is executed publicly on the blockchain.

Understanding these fundamental concepts is crucial for using JAMM DEX effectively. In the next section, we will explore Liquidity Pools in more detail.