Uniswap v1#
Uniswap v1 is the first version, with relatively simple functionality and logic. It only supports eth-erc20 trading pairs with a fixed fee of 3%.
The basic functionality implemented by Uniswap v1 is the constant product market maker system, where the product of the number of eth and erc20 tokens in a single trading pair remains constant. In the AMM mechanism, each trader's counterparty is the trading pool itself.
The code of Uniswap v1 is divided into two parts: factory and exchange. The following explains its implementation mechanism and principles in reference to the code.
factory#
The factory contract is used to maintain all exchange trading pairs and does not have core trading logic. The core methods include the following:
initializeFactory
: Used to set the exchangeTemplate contract address when creating, which will be used for all subsequent exchange trading pair contracts. It can only be set once.createExchange
: Used to create a trading pair for a token.create_with_code_of
is used to copy the code of the specified contract and deploy a new contract.
In addition, the factory also records the following mappings for querying trading pairs and token information:
tokenCount
: Records the total number of trading pairs created.token_to_exchange
: Records the mapping from token to exchange trading pair address.exchange_to_token
: Records the mapping from exchange trading pair address to token.id_to_token
: Records the mapping from trading pair id to token.
@public
def initializeFactory(template: address):
assert self.exchangeTemplate == ZERO_ADDRESS
assert template != ZERO_ADDRESS
self.exchangeTemplate = template
@public
def createExchange(token: address) -> address:
assert token != ZERO_ADDRESS
assert self.exchangeTemplate != ZERO_ADDRESS
assert self.token_to_exchange[token] == ZERO_ADDRESS
exchange: address = create_with_code_of(self.exchangeTemplate)
Exchange(exchange).setup(token)
self.token_to_exchange[token] = exchange
self.exchange_to_token[exchange] = token
token_id: uint256 = self.tokenCount + 1
self.tokenCount = token_id
self.id_to_token[token_id] = token
log.NewExchange(token, exchange)
return exchange
exchange#
Interface#
The core content of each trading pair is in the exchange contract. The core methods are shown in the following interface, which can be mainly divided into the following parts:
- Liquidity management: Adding or removing liquidity, mainly using the lp object.
- Price query:
- Calculate how much eth can be obtained by selling a certain amount of tokens.
- Calculate how many tokens can be obtained by selling a certain amount of eth.
- Calculate how much eth is needed to buy a certain amount of tokens.
- Calculate how many tokens can be bought by a certain amount of eth.
- Providing ETH to exchange for tokens: Similar to the price query above, there are four transaction methods.
- Providing tokens to exchange for ETH: Similar to the price query above, there are four transaction methods.
- Token-to-token exchange: Similar to the price query above, there are four transaction methods.
interface UniswapExchangeInterface {
// Liquidity
function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256);
function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256);
// Price query
function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought);
function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold);
function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought);
function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold);
// Providing ETH to exchange for tokens
function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) external payable returns (uint256 tokens_bought);
function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) external payable returns (uint256 tokens_bought);
function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) external payable returns (uint256 eth_sold);
function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) external payable returns (uint256 eth_sold);
// Providing tokens to exchange for ETH
function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) external returns (uint256 eth_bought);
function tokenToEthTransferInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline, address recipient) external returns (uint256 eth_bought);
function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256 tokens_sold);
function tokenToEthTransferOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline, address recipient) external returns (uint256 tokens_sold);
// Token-to-token exchange
function tokenToTokenSwapInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address token_addr) external returns (uint256 tokens_bought);
function tokenToTokenTransferInput(uint256 tokens_sold, uint256 min_tokens_bought, uint256 min_eth_bought, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_bought);
function tokenToTokenSwapOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address token_addr) external returns (uint256 tokens_sold);
function tokenToTokenTransferOutput(uint256 tokens_bought, uint256 max_tokens_sold, uint256 max_eth_sold, uint256 deadline, address recipient, address token_addr) external returns (uint256 tokens_sold);
}
Core Process#
Although there are many types of transactions involved in Uniswap, and the transaction directions may seem complex, the core process of Uniswap v1 can be represented by the following two diagrams.
As two roles in using Uniswap, lp is responsible for adding and removing liquidity, while players are responsible for swapping in the trading pair.
- As an lp, whether adding or removing liquidity, it needs to be done proportionally.
- As a player, swapping in the trading pair needs to follow the constant product agreement.
Uniswap provides many sets of methods, and their names follow certain rules: A-To-B-swap/transfer-input/output.
- A and B represent the token and eth combination.
- swap transfer: choose one of the transaction types.
- input output: choose one of the transaction directions.
Token Exchange#
Although the trading pairs in Uniswap v1 are composed of token-eth combinations, tokenA-tokenB exchange is also supported.
The implementation is to complete two swaps between the two pools using eth as an intermediate bridge.
Price Conversion#
Based on the constant product agreed upon during the swap, there are two basic methods for price queries:
getInputPrice
: Calculate the output based on the input.getOutputPrice
: Calculate the input based on the output.
And based on these, several derived methods are created, in the format of A-To-B-Input/Output.
- A is the token actively traded, A-input: calculate how many B can be obtained by buying A, A-output: calculate how many B can be obtained by selling A.
Therefore, there are four price query methods for token-eth input-output combinations:
// Price query
function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought);
function getEthToTokenOutputPrice(uint256 tokens_bought) external view returns (uint256 eth_sold);
function getTokenToEthInputPrice(uint256 tokens_sold) external view returns (uint256 eth_bought);
function getTokenToEthOutputPrice(uint256 eth_bought) external view returns (uint256 tokens_sold);