Constant Product Market Maker (CAMM): The standard in Decentralized Finance
Rafael Abuawad
Posted on January 11, 2023
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)
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
Posted on January 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.