本篇文章讲解实现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本身要收取手续费,比如流动性提供者需要得到回报,比如大资金流动性提供者会极大改变代币价格等等,这些会在后面专门去写。
