DEV Community

Cover image for Constant Product Market Maker (CAMM): The standard in Decentralized Finance
Rafael Abuawad
Rafael Abuawad

Posted on

Constant Product Market Maker (CAMM): The standard in Decentralized Finance

Decentralized finance (DeFi) has been gaining a lot of momentum in recent years, and Automated Market Maker (AMM) has been a key technology driving this trend. AMMs are decentralized exchanges (DEXs) that allow users to trade cryptocurrencies without the need for an intermediary. However, a breed of AMM called Constant Product Market Maker (CPMM) is the dominant implementation DeFi space.

How CPMMs are useful

CPMMs are useful for several reasons:

  • Efficient liquidity provision: CPMMs are designed to provide liquidity more efficiently than traditional AMMs. They do this by using a constant product formula which ensures that the liquidity pool is always in a balanced state, no matter how much the prices fluctuate. This results in lower slippage for users and more predictable pricing for traders.
  • Lower impermanent loss: Traditional AMMs can have a problem called impermanent loss (IL) where liquidity providers can experience a loss if the price of the asset changes. CPMMs, on the other hand, have a lower IL, as the constant product formula helps to minimize the impact of price fluctuations on liquidity providers.
  • Better token price stability: CPMMs are better suited for stablecoin markets as they are less sensitive to price fluctuations, ensuring a more stable token price.

A Code Sample

Here is an example of a simple CPMM smart contract, written in the Vyper programming language:

# @version >=0.3.3
from vyper.interfaces import ERC20

token0: public(address)
token1: public(address)

reserve0: public(uint256)
reserve1: public(uint256)

totalSupply: public(uint256)
balanceOf: HashMap[address, uint256]


@external
def __init__(_token0: address, _token1: address):
    self.token0 = _token0
    self.token1 = _token1


@internal
def _mint(_to: address, _amount: uint256):
    self.balanceOf[_to] += _amount
    self.totalSupply += _amount


@internal
def _burn(_from: address, _amount: uint256):
    self.balanceOf[_from] -= _amount
    self.totalSupply -= _amount


@internal
def _update(_reserve0: uint256, _reserve1: uint256):
    self.reserve0 = _reserve0
    self.reserve1 = _reserve1


@external
def swap(_tokenIn: address, _amountIn: uint256) -> uint256:
    assert _tokenIn == self.token0 or _tokenIn == self.token1, "invalid token" 
    assert _amountIn > 0, "amount in is zero"

    tokenIn: ERC20 = empty(ERC20)
    tokenOut: ERC20 = empty(ERC20)
    reserveIn: uint256 = 0
    reserveOut: uint256 = 0

    if _tokenIn == self.token0:
        tokenIn = ERC20(self.token0)
        tokenOut = ERC20(self.token1)
        reserveIn = self.reserve0
        reserveOut = self.reserve1
    else:
        tokenIn = ERC20(self.token1)
        tokenOut = ERC20(self.token0)
        reserveIn = self.reserve1
        reserveOut = self.reserve0

    tokenIn.transferFrom(msg.sender, self, _amountIn)

    amountInWithFee: uint256 = (_amountIn * 997) / 1000
    amountOut: uint256 = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee)

    tokenOut.transfer(msg.sender, amountOut)
    self._update(ERC20(self.token0).balanceOf(self), ERC20(self.token1).balanceOf(self))

    return amountOut


@external
def addLiquidity(_amount0: uint256, _amount1: uint256) -> uint256:
    ERC20(self.token0).transferFrom(msg.sender, self, _amount0)
    ERC20(self.token1).transferFrom(msg.sender, self, _amount1)

    if self.reserve0 > 0 or self.reserve1 > 0:
        assert self.reserve0 * _amount1 == self.reserve1 * _amount0, "x / y != dx / dy"

    shares: uint256 = 0
    if self.totalSupply == 0:
        shares = isqrt(_amount0 * _amount1)
    else:
        shares = min(
            (_amount0 * self.totalSupply) / self.reserve0,
            (_amount0 * self.totalSupply) / self.reserve0,
        )

    assert shares > 0, "shares are zero"
    self._mint(msg.sender, shares)

    return shares


@external
def removeLiquidity(_shares: uint256) -> (uint256, uint256):
    bal0: uint256 = ERC20(self.token0).balanceOf(self)
    bal1: uint256 = ERC20(self.token1).balanceOf(self)

    amount0: uint256 = (_shares * bal0) / self.totalSupply
    amount1: uint256 = (_shares * bal1) / self.totalSupply
    assert amount0 > 0 and amount1 > 0, "amount0 or amount is zero"

    self._burn(msg.sender, _shares)
    self._update(bal0 - amount0, bal1 - amount1)

    ERC20(self.token0).transfer(msg.sender, amount0)
    ERC20(self.token1).transfer(msg.sender, amount1)

    return (amount0, amount1)

Enter fullscreen mode Exit fullscreen mode

This example is quite basic, most real-world AMM smart contracts use more complex mechanisms to calculate prices and determine the amounts of assets that are exchanged in a trade.

In the code above the contract provide the functionality to deposit, withdraw and trade assets on a pool, the trading functionality uses the current state of the pool to determine the amounts of assets that are exchanged, you could use a different mechanism to price the assets like a constant product or a bonding curve.

Please keep in mind that, this is just a sample code, it should not be used in production as is, it lacks many important aspects such

Top comments (0)