DeFi programming: Uniswap. Part 3

Introduction

We continue to create a clone of Uniswap V1! Our implementation is almost ready: we have implemented all the basic mechanics of the Exchange’s smart contract, including pricing, exchange, LP token issuance and commission collection functions. It looks like our clone is complete, but we’re missing the Factory smart contract. Today we are implementing it and our Uniswap V1 clone will be completed.

What is the Factory for?

The Smart Contract of the Factory is needed to maintain a list of created Exchanges: each new deployed smart contract of the Exchange is registered in the Factory. And this is an important mechanic, as it allows you to find any Exchange by referring to the register of the Factory. And also, the presence of such a registry allows the Exchanges to find other Exchanges when the user tries to exchange the token for another token (not ether).

Factory Implementation

Factory (hereinafter — Factory) is a registry, like any registry it needs a data structure to store the list of Exchanges (hereinafter — Exchange), and for this we will use (display) addresses to addresses — this will allow you to find Exchange by token addresses (1 Exchange can exchange only 1 token, remember?).mapping

// SPDX-License-Identifier: MITpragma solidity ^0.8.0;import "./Exchange.sol";contract Factory {
mapping(address => address) public tokenToExchange;

...
}
function createExchange(address _tokenAddress) public returns (address) {
require(_tokenAddress != address(0), "invalid token address");
require(
tokenToExchange[_tokenAddress] == address(0),
"Биржа уже существует"
);
Exchange exchange = new Exchange(_tokenAddress);
tokenToExchange[_tokenAddress] = address(exchange);
return address(exchange);
}
  1. The following check ensures that the token has not yet been added to the registry. The point is that we want to exclude the creation of different Exchanges for the same token, otherwise liquidity will be scattered across several Exchanges. It is better to concentrate it on one Exchange in order to reduce slippage and provide better exchange rates.
function getExchange(address _tokenAddress) 
public view returns (address) {
return tokenToExchange[_tokenAddress];
}

Associate Exchange with Factory

Every Exchange should know the Factory address, but we won’t be stitching the Factory address into the Exchange code as this is bad practice. To associate Exchage with Factory, we need to add a new state variable to Exchage, which will store the Factory address, and then update the constructor:

contract Exchange is ERC20 {
address public tokenAddress;
address public factoryAddress; // <--- новая строка constructor(address _token) ERC20("Zuniswap-V1", "ZUNI-V1") {
require(_token != address(0), "invalid token address");
tokenAddress = _token;
factoryAddress = msg.sender; // <--- новая строка
}
...
}

Exchange of tokens for tokens

How to exchange a token for a token when we have two Exchages, the information for which is stored in the Factory registry? Maybe so:

  1. Instead of sending ether to the user, let’s find Exchage for the address of the token provided by the user.
  2. If Exchage exists, send ether to this Exhange to exchange ether for tokens.
  3. Let’s return the received tokens to the user.
// Exchange.sol
function tokenToTokenSwap(
uint256 _tokensSold,
uint256 _minTokensBought,
address _tokenAddress
) public {
...
}
address exchangeAddress = 
IFactory(factoryAddress).getExchange(_tokenAddress);
require(exchangeAddress != address(this) && exchangeAddress != address(0),
"Такой Биржи не существует");
interface IFactory {
function getExchange(address _tokenAddress) external returns (address);
}
uint256 tokenReserve = getReserve();
uint256 ethBought = getAmount(
_tokensSold,
tokenReserve,
address(this).balance);
IERC20(tokenAddress).transferFrom(
msg.sender,
address(this),
_tokensSold);
IExchange(exchangeAddress)
.ethToTokenSwap{value: ethBought}(_minTokensBought);
function ethToToken(uint256 _minTokens, address recipient) private {
uint256 tokenReserve = getReserve();
uint256 tokensBought = getAmount(
msg.value,
address(this).balance - msg.value,
tokenReserve
);
require(tokensBought >= _minTokens, "недостаточное количество вывода");
IERC20(tokenAddress).transfer(recipient, tokensBought);
}
function ethToTokenSwap(uint256 _minTokens) public payable {
ethToToken(_minTokens, msg.sender);
}
function ethToTokenTransfer(uint256 _minTokens, address _recipient)
public
payable
{
ethToToken(_minTokens, _recipient);
}
IExchange(exchangeAddress)
.ethToTokenTransfer{значение: ethBought}(_minTokensBought, msg.sender);

Conclusion

Work on our copy of Uniswap V1 is complete. If you have ideas about what would be useful to add to smart contracts — go for it! For example, in Exchange, you can add a function to calculate the output number of tokens when exchanging tokens for tokens. If you’re having trouble understanding how something works, feel free to check out the tests.

Series of articles

  1. DeFi programming: Uniswap. Part 1
  2. DeFi programming: Uniswap. Part 2
  3. DeFi programming: Uniswap. Part 3

--

--

indicator system for working according to the Volume Spread method

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store