Uniswap V1(二):实现DEX的Exchange合约

本篇文章讲解实现DEX的Exchange合约。Exchange对应的是代币和ETH,所以需要存入一个代币地址(ETH是以太坊原生代币,没有地址),在构造函数里面初始化地址。

pragma solidity ^0.8.17;

contract Exchange {

address public tokenAddress;

constructor(address _token) {
require(_token != address(0), "invalid token address");

tokenAddress = _token;
}

}

上一篇文章中说过,交易所有两种角色,分别对应两大功能,一个是提供流动性,一个是进行交易

这是一个最简易的提供流动性的代码:

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Exchange {
...

function addLiquidity(uint256 _tokenAmount) public payable {
IERC20 token = IERC20(tokenAddress);
token.transferFrom(msg.sender, address(this), _tokenAmount);
}
}

默认情况下,合约不能接收ETH,这可以通过payable允许在函数中接收ETH的修饰符来修复:任何与函数调用一起发送的ETH都会添加到合约的余额中。

而存入代币是另一回事:由于代币余额存储在代币合约中,我们必须使用transferFrom函数(由ERC20标准定义)将代币从交易发送者的地址转移到合约地址。此外,交易发送者必须调用代币合约的approve函数以授权Exchange合约转移他们的代币。

这个方法传入了一定数量的ETH和代币,如果他是首个注入流动性的人,那么他就决定了代币的价格。

(这个实现addLiquidity是不完整的,只关注了转账功能,我们将在后面的部分中填补空白)

添加一个函数,返回交易所代币余额:

function getReserve() public view returns (uint256) {
return IERC20(tokenAddress).balanceOf(address(this));
}

当DEX有了流动性之后,就可以进行代币转化,那么这个转化的价格是怎么来的呢,这里就涉及了DEX的一个核心函数,恒定乘积函数:

$$X * Y = k$$

不管 \(X\) 或者 \(Y\) 有什么变化,\(k\) 都是恒定不变的。

在DEX中,每一笔交易之后,其实 \(X\) 和 \(Y\) 都在发生变化,在变化之后,它的乘积依然不变:

$$(x + \Delta x)(y – \Delta y) \equiv xy$$

所以代币的价格,就是这个公式:

$$\Delta y = \dfrac{y\Delta x}{x + \Delta x}$$

\(X\) 和 \(Y\) 的关系是这样的曲线,所以,可以看到,不管是 \(X\) 还是 \(Y\),当它的供应量变少之后,它的价格就会提升

所以,当我们使用交易代币的时候,就是通过一定数量的ETH换取一定数量的代币,或者使用一定数量的代币获取一定数量的ETH,所以通过这样的方法来获取代币的数量(这里的reserve就是代币储备):

function getAmount(
uint256 inputAmount,
uint256 inputReserve,
uint256 outputReserve
) private pure returns (uint256) {
require(inputReserve > 0 && outputReserve > 0, "invalid reserves");

return (inputAmount * outputReserve) / (inputReserve + inputAmount);
}

最终的交易方法就可以这样写:

function ethToTokenSwap(uint256 _minTokens) public payable {
uint256 tokenReserve = getReserve();
uint256 tokensBought = getAmount(
msg.value,
address(this).balance - msg.value,
tokenReserve
);

require(tokensBought >= _minTokens, "insufficient output amount");

IERC20(tokenAddress).transfer(msg.sender, tokensBought);
}

function tokenToEthSwap(uint256 _tokensSold, uint256 _minEth) public {
uint256 tokenReserve = getReserve();
uint256 ethBought = getAmount(
_tokensSold,
tokenReserve,
address(this).balance
);

require(ethBought >= _minEth, "insufficient output amount");

IERC20(tokenAddress).transferFrom(msg.sender, address(this), _tokensSold);
payable(msg.sender).transfer(ethBought);
}

在计算好交换多少代币后,就可以把代币发送给对应的钱包。

本文只涉及了其中最简单的逻辑,真正的DEX的交易,不仅要考虑到逻辑,还要考虑安全,还要考虑更多人的利益的因素,比如DEX本身要收取手续费,比如流动性提供者需要得到回报,比如大资金流动性提供者会极大改变代币价格等等,这些会在后面专门去写。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部